2 # GObject-Introspection - a framework for introspecting GObject libraries
3 # Copyright (C) 2008-2010 Johan Dahlin
4 # Copyright (C) 2012 Dieter Verfaillie <dieterv@optionexplicit.be>
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 # AnnotationParser - extract annotations from GTK-Doc comment blocks
29 from .odict import odict
32 # GTK-Doc comment block parts
33 PART_IDENTIFIER = 'identifier'
34 PART_PARAMETERS = 'parameters'
35 PART_DESCRIPTION = 'description'
39 IDENTIFIER_SECTION = 'section'
40 IDENTIFIER_SYMBOL = 'symbol'
41 IDENTIFIER_PROPERTY = 'property'
42 IDENTIFIER_SIGNAL = 'signal'
44 # Tags - annotations applied to comment blocks
47 TAG_STABILITY = 'stability'
48 TAG_DEPRECATED = 'deprecated'
49 TAG_RETURNS = 'returns'
50 TAG_RETURNVALUE = 'return value'
51 TAG_DESCRIPTION = 'description'
52 TAG_ATTRIBUTES = 'attributes'
53 TAG_RENAME_TO = 'rename to'
55 TAG_UNREF_FUNC = 'unref func'
56 TAG_REF_FUNC = 'ref func'
57 TAG_SET_VALUE_FUNC = 'set value func'
58 TAG_GET_VALUE_FUNC = 'get value func'
59 TAG_TRANSFER = 'transfer'
61 _ALL_TAGS = [TAG_VFUNC,
78 # Options - annotations for parameters and return values
79 OPT_ALLOW_NONE = 'allow-none'
81 OPT_ATTRIBUTE = 'attribute'
82 OPT_CLOSURE = 'closure'
83 OPT_DESTROY = 'destroy'
84 OPT_ELEMENT_TYPE = 'element-type'
85 OPT_FOREIGN = 'foreign'
88 OPT_INOUT_ALT = 'in-out'
91 OPT_TRANSFER = 'transfer'
94 OPT_CONSTRUCTOR = 'constructor'
116 # Array options - array specific annotations
117 OPT_ARRAY_FIXED_SIZE = 'fixed-size'
118 OPT_ARRAY_LENGTH = 'length'
119 OPT_ARRAY_ZERO_TERMINATED = 'zero-terminated'
122 OPT_OUT_CALLER_ALLOCATES = 'caller-allocates'
123 OPT_OUT_CALLEE_ALLOCATES = 'callee-allocates'
126 OPT_SCOPE_ASYNC = 'async'
127 OPT_SCOPE_CALL = 'call'
128 OPT_SCOPE_NOTIFIED = 'notified'
131 OPT_TRANSFER_NONE = 'none'
132 OPT_TRANSFER_CONTAINER = 'container'
133 OPT_TRANSFER_FULL = 'full'
134 OPT_TRANSFER_FLOATING = 'floating'
137 #The following regular expression programs are built to:
138 # - match (or substitute) a single comment block line at a time;
139 # - support (but remains untested) LOCALE and UNICODE modes.
141 # Program matching the start of a comment block.
143 # Results in 0 symbolic groups.
144 COMMENT_START_RE = re.compile(r'''
146 [^\S\n\r]* # 0 or more whitespace characters
147 / # 1 forward slash character
148 \*{2} # exactly 2 asterisk characters
149 [^\S\n\r]* # 0 or more whitespace characters
154 # Program matching the end of a comment block. We need to take care
155 # of comment ends that aren't on their own line for legacy support
156 # reasons. See https://bugzilla.gnome.org/show_bug.cgi?id=689354
158 # Results in 1 symbolic group:
159 # - group 1 = description
160 COMMENT_END_RE = re.compile(r'''
162 [^\S\n\r]* # 0 or more whitespace characters
163 (?P<description>.*?) # description text
164 [^\S\n\r]* # 0 or more whitespace characters
165 \*+ # 1 or more asterisk characters
166 / # 1 forward slash character
167 [^\S\n\r]* # 0 or more whitespace characters
172 # Program matching the ' * ' at the beginning of every
173 # line inside a comment block.
175 # Results in 0 symbolic groups.
176 COMMENT_ASTERISK_RE = re.compile(r'''
178 [^\S\n\r]* # 0 or more whitespace characters
179 \* # 1 asterisk character
180 [^\S\n\r]? # 0 or 1 whitespace characters. Careful,
181 # removing more than 1 whitespace
182 # character would break embedded
183 # example program indentation
187 # Program matching the indentation at the beginning of every
188 # line (stripped from the ' * ') inside a comment block.
190 # Results in 1 symbolic group:
191 # - group 1 = indentation
192 COMMENT_INDENTATION_RE = re.compile(r'''
194 (?P<indentation>[^\S\n\r]*) # 0 or more whitespace characters
200 # Program matching an empty line.
202 # Results in 0 symbolic groups.
203 EMPTY_LINE_RE = re.compile(r'''
205 [^\S\n\r]* # 0 or more whitespace characters
210 # Program matching SECTION identifiers.
212 # Results in 2 symbolic groups:
214 # - group 2 = section_name
215 SECTION_RE = re.compile(r'''
217 [^\S\n\r]* # 0 or more whitespace characters
219 [^\S\n\r]* # 0 or more whitespace characters
220 (?P<colon>:?) # colon
221 [^\S\n\r]* # 0 or more whitespace characters
222 (?P<section_name>\w\S+)? # section name
223 [^\S\n\r]* # 0 or more whitespace characters
228 # Program matching symbol (function, constant, struct and enum) identifiers.
230 # Results in 3 symbolic groups:
231 # - group 1 = symbol_name
233 # - group 3 = annotations
234 SYMBOL_RE = re.compile(r'''
236 [^\S\n\r]* # 0 or more whitespace characters
237 (?P<symbol_name>[\w-]*\w) # symbol name
238 [^\S\n\r]* # 0 or more whitespace characters
239 (?P<colon>:?) # colon
240 [^\S\n\r]* # 0 or more whitespace characters
241 (?P<annotations>(?:\(.*?\)[^\S\n\r]*)*) # annotations
242 [^\S\n\r]* # 0 or more whitespace characters
247 # Program matching property identifiers.
249 # Results in 4 symbolic groups:
250 # - group 1 = class_name
251 # - group 2 = property_name
253 # - group 4 = annotations
254 PROPERTY_RE = re.compile(r'''
256 [^\S\n\r]* # 0 or more whitespace characters
257 (?P<class_name>[\w]+) # class name
258 [^\S\n\r]* # 0 or more whitespace characters
259 :{1} # required colon
260 [^\S\n\r]* # 0 or more whitespace characters
261 (?P<property_name>[\w-]*\w) # property name
262 [^\S\n\r]* # 0 or more whitespace characters
263 (?P<colon>:?) # colon
264 [^\S\n\r]* # 0 or more whitespace characters
265 (?P<annotations>(?:\(.*?\)[^\S\n\r]*)*) # annotations
266 [^\S\n\r]* # 0 or more whitespace characters
271 # Program matching signal identifiers.
273 # Results in 4 symbolic groups:
274 # - group 1 = class_name
275 # - group 2 = signal_name
277 # - group 4 = annotations
278 SIGNAL_RE = re.compile(r'''
280 [^\S\n\r]* # 0 or more whitespace characters
281 (?P<class_name>[\w]+) # class name
282 [^\S\n\r]* # 0 or more whitespace characters
283 :{2} # 2 required colons
284 [^\S\n\r]* # 0 or more whitespace characters
285 (?P<signal_name>[\w-]*\w) # signal name
286 [^\S\n\r]* # 0 or more whitespace characters
287 (?P<colon>:?) # colon
288 [^\S\n\r]* # 0 or more whitespace characters
289 (?P<annotations>(?:\(.*?\)[^\S\n\r]*)*) # annotations
290 [^\S\n\r]* # 0 or more whitespace characters
295 # Program matching parameters.
297 # Results in 4 symbolic groups:
298 # - group 1 = parameter_name
299 # - group 2 = annotations
301 # - group 4 = description
302 PARAMETER_RE = re.compile(r'''
304 [^\S\n\r]* # 0 or more whitespace characters
306 (?P<parameter_name>[\w-]*\w|\.\.\.) # parameter name
307 [^\S\n\r]* # 0 or more whitespace characters
308 :{1} # required colon
309 [^\S\n\r]* # 0 or more whitespace characters
310 (?P<annotations>(?:\(.*?\)[^\S\n\r]*)*) # annotations
311 (?P<colon>:?) # colon
312 [^\S\n\r]* # 0 or more whitespace characters
313 (?P<description>.*?) # description
314 [^\S\n\r]* # 0 or more whitespace characters
319 # Program matching tags.
321 # Results in 4 symbolic groups:
322 # - group 1 = tag_name
323 # - group 2 = annotations
325 # - group 4 = description
326 _all_tags = '|'.join(_ALL_TAGS).replace(' ', '\\ ')
327 TAG_RE = re.compile(r'''
329 [^\S\n\r]* # 0 or more whitespace characters
330 (?P<tag_name>''' + _all_tags + r''') # tag name
331 [^\S\n\r]* # 0 or more whitespace characters
332 :{1} # required colon
333 [^\S\n\r]* # 0 or more whitespace characters
334 (?P<annotations>(?:\(.*?\)[^\S\n\r]*)*) # annotations
335 (?P<colon>:?) # colon
336 [^\S\n\r]* # 0 or more whitespace characters
337 (?P<description>.*?) # description
338 [^\S\n\r]* # 0 or more whitespace characters
341 re.VERBOSE | re.IGNORECASE)
343 # Program matching multiline annotation continuations.
344 # This is used on multiline parameters and tags (but not on the first line) to
345 # generate warnings about invalid annotations spanning multiple lines.
347 # Results in 3 symbolic groups:
348 # - group 2 = annotations
350 # - group 4 = description
351 MULTILINE_ANNOTATION_CONTINUATION_RE = re.compile(r'''
353 [^\S\n\r]* # 0 or more whitespace characters
354 (?P<annotations>(?:\(.*?\)[^\S\n\r]*)*) # annotations
356 [^\S\n\r]* # 0 or more whitespace characters
357 (?P<description>.*?) # description
358 [^\S\n\r]* # 0 or more whitespace characters
364 class DocBlock(object):
366 def __init__(self, name):
368 self.options = DocOptions()
372 self.params = odict()
375 def __cmp__(self, other):
376 return cmp(self.name, other.name)
379 return '<DocBlock %r %r>' % (self.name, self.options)
381 def get_tag(self, name):
382 return self.tags.get(name)
384 def get_param(self, name):
385 return self.params.get(name)
387 def to_gtk_doc(self):
391 options += ' '.join('(%s)' % o for o in self.options)
393 if 'SECTION' not in self.name:
396 for param in self.params.values():
397 lines.append(param.to_gtk_doc_param())
400 for l in self.comment.split('\n'):
404 for tag in self.tags.values():
405 lines.append(tag.to_gtk_doc_tag())
412 comment += ' * %s\n' % (line, )
419 for param in self.params.values():
422 for tag in self.tags.values():
426 class DocTag(object):
428 def __init__(self, block, name):
431 self.options = DocOptions()
437 return '<DocTag %r %r>' % (self.name, self.options)
439 def _validate_option(self, name, value, required=False,
440 n_params=None, choices=None):
441 if required and value is None:
442 message.warn('%s annotation needs a value' % (
443 name, ), self.position)
446 if n_params is not None:
452 s = '%d values' % (n_params, )
453 if ((n_params > 0 and (value is None or value.length() != n_params)) or
454 n_params == 0 and value is not None):
458 length = value.length()
459 message.warn('%s annotation needs %s, not %d' % (
460 name, s, length), self.position)
463 if choices is not None:
464 valuestr = value.one()
465 if valuestr not in choices:
466 message.warn('invalid %s annotation value: %r' % (
467 name, valuestr, ), self.position)
470 def _validate_array(self, option, value):
474 for name, v in value.all().items():
475 if name in [OPT_ARRAY_ZERO_TERMINATED, OPT_ARRAY_FIXED_SIZE]:
478 except (TypeError, ValueError):
481 'array option %s needs a value' % (
483 positions=self.position)
486 'invalid array %s option value %r, '
487 'must be an integer' % (name, v, ),
488 positions=self.position)
489 elif name == OPT_ARRAY_LENGTH:
492 'array option length needs a value',
493 positions=self.position)
496 'invalid array annotation value: %r' % (
497 name, ), self.position)
499 def _validate_closure(self, option, value):
500 if value is not None and value.length() > 1:
502 'closure takes at most 1 value, %d given' % (
503 value.length()), self.position)
505 def _validate_element_type(self, option, value):
506 self._validate_option(option, value, required=True)
509 'element-type takes at least one value, none given',
512 if value.length() > 2:
514 'element-type takes at most 2 values, %d given' % (
515 value.length()), self.position)
518 def _validate_out(self, option, value):
521 if value.length() > 1:
523 'out annotation takes at most 1 value, %d given' % (
524 value.length()), self.position)
526 value_str = value.one()
527 if value_str not in [OPT_OUT_CALLEE_ALLOCATES,
528 OPT_OUT_CALLER_ALLOCATES]:
529 message.warn("out annotation value is invalid: %r" % (
530 value_str), self.position)
533 def _get_gtk_doc_value(self):
534 def serialize_one(option, value, fmt, fmt2):
536 if type(value) != str:
537 value = ' '.join((serialize_one(k, v, '%s=%s', '%s')
538 for k, v in value.all().items()))
539 return fmt % (option, value)
541 return fmt2 % (option, )
543 for option, value in self.options.items():
545 serialize_one(option, value, '(%s %s)', '(%s)'))
547 return ' '.join(annotations) + ': '
551 def to_gtk_doc_param(self):
552 return '@%s: %s%s' % (self.name, self._get_gtk_doc_value(), self.comment)
554 def to_gtk_doc_tag(self):
555 return '%s: %s%s' % (self.name.capitalize(),
556 self._get_gtk_doc_value(),
560 if self.name == TAG_ATTRIBUTES:
561 # The 'Attributes:' tag allows free form annotations so the
562 # validation below is most certainly going to fail.
565 for option, value in self.options.items():
566 if option == OPT_ALLOW_NONE:
567 self._validate_option(option, value, n_params=0)
568 elif option == OPT_ARRAY:
569 self._validate_array(option, value)
570 elif option == OPT_ATTRIBUTE:
571 self._validate_option(option, value, n_params=2)
572 elif option == OPT_CLOSURE:
573 self._validate_closure(option, value)
574 elif option == OPT_DESTROY:
575 self._validate_option(option, value, n_params=1)
576 elif option == OPT_ELEMENT_TYPE:
577 self._validate_element_type(option, value)
578 elif option == OPT_FOREIGN:
579 self._validate_option(option, value, n_params=0)
580 elif option == OPT_IN:
581 self._validate_option(option, value, n_params=0)
582 elif option in [OPT_INOUT, OPT_INOUT_ALT]:
583 self._validate_option(option, value, n_params=0)
584 elif option == OPT_OUT:
585 self._validate_out(option, value)
586 elif option == OPT_SCOPE:
587 self._validate_option(
588 option, value, required=True,
590 choices=[OPT_SCOPE_ASYNC,
593 elif option == OPT_SKIP:
594 self._validate_option(option, value, n_params=0)
595 elif option == OPT_TRANSFER:
596 self._validate_option(
597 option, value, required=True,
599 choices=[OPT_TRANSFER_FULL,
600 OPT_TRANSFER_CONTAINER,
602 OPT_TRANSFER_FLOATING])
603 elif option == OPT_TYPE:
604 self._validate_option(option, value, required=True,
606 elif option == OPT_CONSTRUCTOR:
607 self._validate_option(option, value, n_params=0)
608 elif option == OPT_METHOD:
609 self._validate_option(option, value, n_params=0)
611 message.warn('invalid annotation option: %s' % (option, ),
615 class DocOptions(object):
621 return '<DocOptions %r>' % (self.values, )
623 def __getitem__(self, item):
624 for key, value in self.values:
629 def __nonzero__(self):
630 return bool(self.values)
633 return (k for k, v in self.values)
635 def add(self, name, value):
636 self.values.append((name, value))
638 def get(self, item, default=None):
639 for key, value in self.values:
644 def getall(self, item):
645 for key, value in self.values:
650 return iter(self.values)
653 class DocOption(object):
655 def __init__(self, tag, option):
659 # (annotation option1=value1 option2=value2) etc
660 for p in option.split(' '):
662 name, value = p.split('=', 1)
666 self._dict[name] = value
668 self._array.append(name)
670 self._array.append((name, value))
673 return '<DocOption %r>' % (self._array, )
676 return len(self._array)
679 assert len(self._array) == 1
680 return self._array[0]
689 class AnnotationParser(object):
691 GTK-Doc comment block parser.
693 Parses GTK-Doc comment blocks into a parse tree built out of :class:`DockBlock`,
694 :class:`DocTag`, :class:`DocOptions` and :class:`DocOption` objects. This
695 parser tries to accept malformed input whenever possible and does not emit
696 syntax errors. However, it does emit warnings at the slightest indication
697 of malformed input when possible. It is usually a good idea to heed these
698 warnings as malformed input is known to result in invalid GTK-Doc output.
700 A GTK-Doc comment block can be constructed out of multiple parts that can
701 be combined to write different types of documentation.
702 See `GTK-Doc's documentation`_ to learn more about possible valid combinations.
703 Each part can be further divided into fields which are separated by `:` characters.
705 Possible parts and the fields they are constructed from look like the
706 following (optional fields are enclosed in square brackets):
710 * identifier_name [:annotations]
711 * @parameter_name [:annotations] [:description]
713 * comment_block_description
714 * tag_name [:annotations] [:description]
717 The order in which the different parts have to be specified is important::
719 - There has to be exactly 1 `identifier` part on the first line of the
720 comment block which consists of:
721 * an `identifier_name` field
722 * an optional `annotations` field
723 - Followed by 0 or more `parameters` parts, each consisting of:
724 * a `parameter_name` field
725 * an optional `annotations` field
726 * an optional `description` field
727 - Followed by at least 1 empty line signaling the beginning of
728 the `comment_block_description` part
729 - Followed by an optional `comment block description` part.
730 - Followed by 0 or more `tag` parts, each consisting of:
732 * an optional `annotations` field
733 * an optional `description` field
735 Additionally, the following restrictions are in effect::
737 - Parts can optionally be separated by an empty line, except between
738 the `parameter` parts and the `comment block description` part where
739 an empty line is required (see above).
740 - Parts and fields cannot span multiple lines, except for
741 `parameter descriptions`, `tag descriptions` and the
742 `comment_block_description` fields.
743 - `parameter descriptions` fields can not span multiple paragraphs.
744 - `tag descriptions` and `comment block description` fields can
745 span multiple paragraphs.
747 .. NOTE:: :class:`AnnotationParser` functionality is heavily based on gtkdoc-mkdb's
748 `ScanSourceFile()`_ function and is currently in sync with GTK-Doc
751 .. _GTK-Doc's documentation:
752 http://developer.gnome.org/gtk-doc-manual/1.18/documenting.html.en
753 .. _ScanSourceFile():
754 http://git.gnome.org/browse/gtk-doc/tree/gtkdoc-mkdb.in#n3722
755 .. _47abcd5: 47abcd53b8489ebceec9e394676512a181c1f1f6
758 def parse(self, comments):
760 Parses multiple GTK-Doc comment blocks.
762 :param comments: a list of (comment, filename, lineno) tuples
763 :returns: a dictionary mapping identifier names to :class:`DocBlock` objects
768 for comment in comments:
770 comment_block = self.parse_comment_block(comment)
772 message.warn('unrecoverable parse error, please file a GObject-Introspection '
773 'bug report including the complete comment block at the '
774 'indicated location.', message.Position(comment[1], comment[2]))
777 if comment_block is not None:
778 # Note: previous versions of this parser did not check
779 # if an identifier was already stored in comment_blocks,
780 # so when multiple comment blocks where encountered documenting
781 # the same identifier the last one seen "wins".
782 # Keep this behavior for backwards compatibility, but
784 if comment_block.name in comment_blocks:
785 message.warn("multiple comment blocks documenting '%s:' identifier." %
786 (comment_block.name),
787 comment_block.position)
789 comment_blocks[comment_block.name] = comment_block
791 return comment_blocks
793 def parse_comment_block(self, comment):
795 Parses a single GTK-Doc comment block.
797 :param comment: a (comment, filename, lineno) tuple
798 :returns: a :class:`DocBlock` object or ``None``
801 comment, filename, lineno = comment
803 # Assign line numbers to each line of the comment block,
804 # which will later be used as the offset to calculate the
805 # real line number in the source file
806 comment_lines = list(enumerate(comment.split('\n')))
808 # Check for the start the comment block.
809 if COMMENT_START_RE.match(comment_lines[0][1]):
812 # Not a GTK-Doc comment block.
815 # Check for the end the comment block.
816 line_offset, line = comment_lines[-1]
817 result = COMMENT_END_RE.match(line)
819 description = result.group('description')
821 comment_lines[-1] = (line_offset, description)
822 position = message.Position(filename, lineno + line_offset)
823 marker = ' '*result.end('description') + '^'
824 message.warn("Comments should end with */ on a new line:\n%s\n%s" %
828 del comment_lines[-1]
830 # Not a GTK-Doc comment block.
833 # If we get this far, we are inside a GTK-Doc comment block.
834 return self._parse_comment_block(comment_lines, filename, lineno)
836 def _parse_comment_block(self, comment_lines, filename, lineno):
838 Parses a single GTK-Doc comment block already stripped from its
839 comment start (/**) and comment end (*/) marker lines.
841 :param comment_lines: list of (line_offset, line) tuples representing a
842 GTK-Doc comment block already stripped from it's
843 start (/**) and end (*/) marker lines
844 :param filename: source file name where the comment block originated from
845 :param lineno: line in the source file where the comment block starts
846 :returns: a :class:`DocBlock` object or ``None``
848 .. NOTE:: If you are tempted to refactor this method and split it
849 further up (for example into _parse_identifier(), _parse_parameters(),
850 _parse_description(), _parse_tags() methods) then please resist the
851 urge. It is considered important that this method should be more or
852 less easily comparable with gtkdoc-mkdb's `ScanSourceFile()`_ function.
854 The different parsing steps are marked with a comment surrounded
855 by `#` characters in an attempt to make it clear what is going on.
857 .. _ScanSourceFile():
858 http://git.gnome.org/browse/gtk-doc/tree/gtkdoc-mkdb.in#n3722
869 for line_offset, line in comment_lines:
870 position = message.Position(filename, line_offset + lineno)
872 # Store the original line (without \n) and column offset
873 # so we can generate meaningful warnings later on.
877 # Get rid of ' * ' at start of the line.
878 result = COMMENT_ASTERISK_RE.match(line)
880 column_offset = result.end(0)
881 line = line[result.end(0):]
883 # Store indentation level of the line.
884 result = COMMENT_INDENTATION_RE.match(line)
885 line_indent = len(result.group('indentation').replace('\t', ' '))
887 ####################################################################
888 # Check for GTK-Doc comment block identifier.
889 ####################################################################
890 if not comment_block:
892 result = SECTION_RE.match(line)
894 identifier = IDENTIFIER_SECTION
895 identifier_name = 'SECTION:%s' % (result.group('section_name'))
896 column = result.start('section_name') + column_offset
899 result = SYMBOL_RE.match(line)
901 identifier = IDENTIFIER_SYMBOL
902 identifier_name = '%s' % (result.group('symbol_name'))
903 column = result.start('symbol_name') + column_offset
906 result = PROPERTY_RE.match(line)
908 identifier = IDENTIFIER_PROPERTY
909 identifier_name = '%s:%s' % (result.group('class_name'),
910 result.group('property_name'))
911 column = result.start('property_name') + column_offset
914 result = SIGNAL_RE.match(line)
916 identifier = IDENTIFIER_SIGNAL
917 identifier_name = '%s::%s' % (result.group('class_name'),
918 result.group('signal_name'))
919 column = result.start('signal_name') + column_offset
922 in_part = PART_IDENTIFIER
923 part_indent = line_indent
925 comment_block = DocBlock(identifier_name)
926 comment_block.position = position
928 if 'colon' in result.groupdict() and result.group('colon') != ':':
929 colon_start = result.start('colon')
930 colon_column = column_offset + colon_start
931 marker = ' '*colon_column + '^'
932 message.warn("missing ':' at column %s:\n%s\n%s" %
933 (colon_column + 1, original_line, marker),
936 if 'annotations' in result.groupdict():
937 comment_block.options = self.parse_options(comment_block,
938 result.group('annotations'))
942 # If we get here, the identifier was not recognized, so
943 # ignore the rest of the block just like the old annotation
944 # parser did. Doing this is a bit more strict than
945 # gtkdoc-mkdb (which continues to search for the identifier
946 # until either it is found or the end of the block is
947 # reached). In practice, however, ignoring the block is the
948 # right thing to do because sooner or later some long
949 # descriptions will contain something matching an identifier
950 # pattern by accident.
951 marker = ' '*column_offset + '^'
952 message.warn('ignoring unrecognized GTK-Doc comment block, identifier not '
953 'found:\n%s\n%s' % (original_line, marker),
958 ####################################################################
959 # Check for comment block parameters.
960 ####################################################################
961 result = PARAMETER_RE.match(line)
963 param_name = result.group('parameter_name')
964 param_annotations = result.group('annotations')
965 param_description = result.group('description')
967 if in_part == PART_IDENTIFIER:
968 in_part = PART_PARAMETERS
970 part_indent = line_indent
972 if in_part != PART_PARAMETERS:
973 column = result.start('parameter_name') + column_offset
974 marker = ' '*column + '^'
975 message.warn("'@%s' parameter unexpected at this location:\n%s\n%s" %
976 (param_name, original_line, marker),
979 # Old style GTK-Doc allowed return values to be specified as
980 # parameters instead of tags.
981 if param_name.lower() == TAG_RETURNS:
982 param_name = TAG_RETURNS
987 message.warn("encountered multiple 'Returns' parameters or tags for "
988 "'%s'." % (comment_block.name),
990 elif param_name in comment_block.params.keys():
991 column = result.start('parameter_name') + column_offset
992 marker = ' '*column + '^'
993 message.warn("multiple '@%s' parameters for identifier '%s':\n%s\n%s" %
994 (param_name, comment_block.name, original_line, marker),
997 tag = DocTag(comment_block, param_name)
998 tag.position = position
999 tag.comment = param_description
1000 if param_annotations:
1001 tag.options = self.parse_options(tag, param_annotations)
1002 if param_name == TAG_RETURNS:
1003 comment_block.tags[param_name] = tag
1005 comment_block.params[param_name] = tag
1009 ####################################################################
1010 # Check for comment block description.
1012 # When we are parsing comment block parameters or the comment block
1013 # identifier (when there are no parameters) and encounter an empty
1014 # line, we must be parsing the comment block description.
1015 ####################################################################
1016 if (EMPTY_LINE_RE.match(line)
1017 and in_part in [PART_IDENTIFIER, PART_PARAMETERS]):
1018 in_part = PART_DESCRIPTION
1019 part_indent = line_indent
1022 ####################################################################
1023 # Check for GTK-Doc comment block tags.
1024 ####################################################################
1025 result = TAG_RE.match(line)
1026 if result and line_indent <= part_indent:
1027 tag_name = result.group('tag_name')
1028 tag_annotations = result.group('annotations')
1029 tag_description = result.group('description')
1031 marker = ' '*(result.start('tag_name') + column_offset) + '^'
1033 # Deprecated GTK-Doc Description: tag
1034 if tag_name.lower() == TAG_DESCRIPTION:
1035 message.warn("GTK-Doc tag \"Description:\" has been deprecated:\n%s\n%s" %
1036 (original_line, marker),
1039 in_part = PART_DESCRIPTION
1040 part_indent = line_indent
1042 if not comment_block.comment:
1043 comment_block.comment = tag_description
1045 comment_block.comment += '\n' + tag_description
1048 # Now that the deprecated stuff is out of the way, continue parsing real tags
1049 if in_part == PART_DESCRIPTION:
1052 part_indent = line_indent
1054 if in_part != PART_TAGS:
1055 column = result.start('tag_name') + column_offset
1056 marker = ' '*column + '^'
1057 message.warn("'%s:' tag unexpected at this location:\n%s\n%s" %
1058 (tag_name, original_line, marker),
1061 if tag_name.lower() in [TAG_RETURNS, TAG_RETURNVALUE]:
1062 if not returns_seen:
1065 message.warn("encountered multiple 'Returns' parameters or tags for "
1066 "'%s'." % (comment_block.name),
1069 tag = DocTag(comment_block, TAG_RETURNS)
1070 tag.position = position
1071 tag.comment = tag_description
1073 tag.options = self.parse_options(tag, tag_annotations)
1074 comment_block.tags[TAG_RETURNS] = tag
1078 if tag_name.lower() in comment_block.tags.keys():
1079 column = result.start('tag_name') + column_offset
1080 marker = ' '*column + '^'
1081 message.warn("multiple '%s:' tags for identifier '%s':\n%s\n%s" %
1082 (tag_name, comment_block.name, original_line, marker),
1085 tag = DocTag(comment_block, tag_name.lower())
1086 tag.position = position
1087 tag.value = tag_description
1089 if tag_name.lower() == TAG_ATTRIBUTES:
1090 tag.options = self.parse_options(tag, tag_annotations)
1092 message.warn("annotations not supported for tag '%s:'." %
1095 comment_block.tags[tag_name.lower()] = tag
1099 ####################################################################
1100 # If we get here, we must be in the middle of a multiline
1101 # comment block, parameter or tag description.
1102 ####################################################################
1103 if in_part in [PART_IDENTIFIER, PART_DESCRIPTION]:
1104 if not comment_block.comment:
1105 comment_block.comment = line
1107 comment_block.comment += '\n' + line
1109 elif in_part == PART_PARAMETERS:
1110 self._validate_multiline_annotation_continuation(line, original_line,
1111 column_offset, position)
1112 # Append to parameter description.
1113 current_param.comment += ' ' + line.strip()
1115 elif in_part == PART_TAGS:
1116 self._validate_multiline_annotation_continuation(line, original_line,
1117 column_offset, position)
1118 # Append to tag description.
1119 if current_tag.name.lower() in [TAG_RETURNS, TAG_RETURNVALUE]:
1120 current_tag.comment += ' ' + line.strip()
1122 current_tag.value += ' ' + line.strip()
1125 ########################################################################
1126 # Finished parsing this comment block.
1127 ########################################################################
1129 # We have picked up a couple of \n characters that where not
1130 # intended. Strip those.
1131 if comment_block.comment:
1132 comment_block.comment = comment_block.comment.strip()
1134 for tag in comment_block.tags.values():
1135 self._clean_comment_block_part(tag)
1137 for param in comment_block.params.values():
1138 self._clean_comment_block_part(param)
1140 # Validate and store block.
1141 comment_block.validate()
1142 return comment_block
1146 def _clean_comment_block_part(self, part):
1148 part.comment = part.comment.strip()
1153 part.value = part.value.strip()
1157 def _validate_multiline_annotation_continuation(self, line, original_line,
1158 column_offset, position):
1160 Validate parameters and tags (except the first line) and generate
1161 warnings about invalid annotations spanning multiple lines.
1163 :param line: line to validate, stripped from ' * ' at start of the line.
1164 :param original_line: original line to validate (used in warning messages)
1165 :param column_offset: column width of ' * ' at the time it was stripped from `line`
1166 :param position: position of `line` in the source file
1169 result = MULTILINE_ANNOTATION_CONTINUATION_RE.match(line)
1171 column = result.start('annotations') + column_offset
1172 marker = ' '*column + '^'
1173 message.warn('ignoring invalid multiline annotation continuation:\n'
1174 '%s\n%s' % (original_line, marker),
1178 def parse_options(cls, tag, value):
1180 # (annotation opt1 opt2 ...)
1181 # (annotation opt1=value1 opt2=value2 ...)
1183 options = DocOptions()
1184 options.position = tag.position
1186 for i, c in enumerate(value):
1187 if c == '(' and opened == -1:
1189 if c == ')' and opened != -1:
1190 segment = value[opened:i]
1191 parts = segment.split(' ', 1)
1193 name, option = parts
1194 elif len(parts) == 1:
1198 raise AssertionError
1199 if option is not None:
1200 option = DocOption(tag, option)
1201 options.add(name, option)