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.
156 # Results in 0 symbolic groups.
157 COMMENT_END_RE = re.compile(r'''
159 [^\S\n\r]* # 0 or more whitespace characters
160 \*+ # 1 or more asterisk characters
161 / # 1 forward slash character
166 # Program matching the ' * ' at the beginning of every
167 # line inside a comment block.
169 # Results in 0 symbolic groups.
170 COMMENT_ASTERISK_RE = re.compile(r'''
172 [^\S\n\r]* # 0 or more whitespace characters
173 \* # 1 asterisk character
174 [^\S\n\r]? # 0 or 1 whitespace characters. Careful,
175 # removing more than 1 whitespace
176 # character would break embedded
177 # example program indentation
181 # Program matching the indentation at the beginning of every
182 # line (stripped from the ' * ') inside a comment block.
184 # Results in 1 symbolic group:
185 # - group 1 = indentation
186 COMMENT_INDENTATION_RE = re.compile(r'''
188 (?P<indentation>[^\S\n\r]*) # 0 or more whitespace characters
194 # Program matching an empty line.
196 # Results in 0 symbolic groups.
197 EMPTY_LINE_RE = re.compile(r'''
199 [^\S\n\r]* # 0 or more whitespace characters
204 # Program matching SECTION identifiers.
206 # Results in 2 symbolic groups:
208 # - group 2 = section_name
209 SECTION_RE = re.compile(r'''
211 [^\S\n\r]* # 0 or more whitespace characters
213 [^\S\n\r]* # 0 or more whitespace characters
214 (?P<colon>:?) # colon
215 [^\S\n\r]* # 0 or more whitespace characters
216 (?P<section_name>\w\S+)? # section name
217 [^\S\n\r]* # 0 or more whitespace characters
222 # Program matching symbol (function, constant, struct and enum) identifiers.
224 # Results in 3 symbolic groups:
225 # - group 1 = symbol_name
227 # - group 3 = annotations
228 SYMBOL_RE = re.compile(r'''
230 [^\S\n\r]* # 0 or more whitespace characters
231 (?P<symbol_name>[\w-]*\w) # symbol name
232 [^\S\n\r]* # 0 or more whitespace characters
233 (?P<colon>:?) # colon
234 [^\S\n\r]* # 0 or more whitespace characters
235 (?P<annotations>(?:\(.*?\)[^\S\n\r]*)*) # annotations
236 [^\S\n\r]* # 0 or more whitespace characters
241 # Program matching property identifiers.
243 # Results in 4 symbolic groups:
244 # - group 1 = class_name
245 # - group 2 = property_name
247 # - group 4 = annotations
248 PROPERTY_RE = re.compile(r'''
250 [^\S\n\r]* # 0 or more whitespace characters
251 (?P<class_name>[\w]+) # class name
252 [^\S\n\r]* # 0 or more whitespace characters
253 :{1} # required colon
254 [^\S\n\r]* # 0 or more whitespace characters
255 (?P<property_name>[\w-]*\w) # property name
256 [^\S\n\r]* # 0 or more whitespace characters
257 (?P<colon>:?) # colon
258 [^\S\n\r]* # 0 or more whitespace characters
259 (?P<annotations>(?:\(.*?\)[^\S\n\r]*)*) # annotations
260 [^\S\n\r]* # 0 or more whitespace characters
265 # Program matching signal identifiers.
267 # Results in 4 symbolic groups:
268 # - group 1 = class_name
269 # - group 2 = signal_name
271 # - group 4 = annotations
272 SIGNAL_RE = re.compile(r'''
274 [^\S\n\r]* # 0 or more whitespace characters
275 (?P<class_name>[\w]+) # class name
276 [^\S\n\r]* # 0 or more whitespace characters
277 :{2} # 2 required colons
278 [^\S\n\r]* # 0 or more whitespace characters
279 (?P<signal_name>[\w-]*\w) # signal name
280 [^\S\n\r]* # 0 or more whitespace characters
281 (?P<colon>:?) # colon
282 [^\S\n\r]* # 0 or more whitespace characters
283 (?P<annotations>(?:\(.*?\)[^\S\n\r]*)*) # annotations
284 [^\S\n\r]* # 0 or more whitespace characters
289 # Program matching parameters.
291 # Results in 4 symbolic groups:
292 # - group 1 = parameter_name
293 # - group 2 = annotations
295 # - group 4 = description
296 PARAMETER_RE = re.compile(r'''
298 [^\S\n\r]* # 0 or more whitespace characters
300 (?P<parameter_name>[\w-]*\w|\.\.\.) # parameter name
301 [^\S\n\r]* # 0 or more whitespace characters
302 :{1} # required colon
303 [^\S\n\r]* # 0 or more whitespace characters
304 (?P<annotations>(?:\(.*?\)[^\S\n\r]*)*) # annotations
305 (?P<colon>:?) # colon
306 [^\S\n\r]* # 0 or more whitespace characters
307 (?P<description>.*?) # description
308 [^\S\n\r]* # 0 or more whitespace characters
313 # Program matching tags.
315 # Results in 4 symbolic groups:
316 # - group 1 = tag_name
317 # - group 2 = annotations
319 # - group 4 = description
320 _all_tags = '|'.join(_ALL_TAGS).replace(' ', '\\ ')
321 TAG_RE = re.compile(r'''
323 [^\S\n\r]* # 0 or more whitespace characters
324 (?P<tag_name>''' + _all_tags + r''') # tag name
325 [^\S\n\r]* # 0 or more whitespace characters
326 :{1} # required colon
327 [^\S\n\r]* # 0 or more whitespace characters
328 (?P<annotations>(?:\(.*?\)[^\S\n\r]*)*) # annotations
329 (?P<colon>:?) # colon
330 [^\S\n\r]* # 0 or more whitespace characters
331 (?P<description>.*?) # description
332 [^\S\n\r]* # 0 or more whitespace characters
335 re.VERBOSE | re.IGNORECASE)
337 # Program matching multiline annotation continuations.
338 # This is used on multiline parameters and tags (but not on the first line) to
339 # generate warnings about invalid annotations spanning multiple lines.
341 # Results in 3 symbolic groups:
342 # - group 2 = annotations
344 # - group 4 = description
345 MULTILINE_ANNOTATION_CONTINUATION_RE = re.compile(r'''
347 [^\S\n\r]* # 0 or more whitespace characters
348 (?P<annotations>(?:\(.*?\)[^\S\n\r]*)*) # annotations
350 [^\S\n\r]* # 0 or more whitespace characters
351 (?P<description>.*?) # description
352 [^\S\n\r]* # 0 or more whitespace characters
358 class DocBlock(object):
360 def __init__(self, name):
362 self.options = DocOptions()
366 self.params = odict()
369 def __cmp__(self, other):
370 return cmp(self.name, other.name)
373 return '<DocBlock %r %r>' % (self.name, self.options)
375 def get_tag(self, name):
376 return self.tags.get(name)
378 def get_param(self, name):
379 return self.params.get(name)
381 def to_gtk_doc(self):
385 options += ' '.join('(%s)' % o for o in self.options)
387 if 'SECTION' not in self.name:
390 for param in self.params.values():
391 lines.append(param.to_gtk_doc_param())
394 for l in self.comment.split('\n'):
398 for tag in self.tags.values():
399 lines.append(tag.to_gtk_doc_tag())
406 comment += ' * %s\n' % (line, )
413 for param in self.params.values():
416 for tag in self.tags.values():
420 class DocTag(object):
422 def __init__(self, block, name):
425 self.options = DocOptions()
431 return '<DocTag %r %r>' % (self.name, self.options)
433 def _validate_option(self, name, value, required=False,
434 n_params=None, choices=None):
435 if required and value is None:
436 message.warn('%s annotation needs a value' % (
437 name, ), self.position)
440 if n_params is not None:
446 s = '%d values' % (n_params, )
447 if ((n_params > 0 and (value is None or value.length() != n_params)) or
448 n_params == 0 and value is not None):
452 length = value.length()
453 message.warn('%s annotation needs %s, not %d' % (
454 name, s, length), self.position)
457 if choices is not None:
458 valuestr = value.one()
459 if valuestr not in choices:
460 message.warn('invalid %s annotation value: %r' % (
461 name, valuestr, ), self.position)
464 def _validate_array(self, option, value):
468 for name, v in value.all().items():
469 if name in [OPT_ARRAY_ZERO_TERMINATED, OPT_ARRAY_FIXED_SIZE]:
472 except (TypeError, ValueError):
475 'array option %s needs a value' % (
477 positions=self.position)
480 'invalid array %s option value %r, '
481 'must be an integer' % (name, v, ),
482 positions=self.position)
483 elif name == OPT_ARRAY_LENGTH:
486 'array option length needs a value',
487 positions=self.position)
490 'invalid array annotation value: %r' % (
491 name, ), self.position)
493 def _validate_closure(self, option, value):
494 if value is not None and value.length() > 1:
496 'closure takes at most 1 value, %d given' % (
497 value.length()), self.position)
499 def _validate_element_type(self, option, value):
500 self._validate_option(option, value, required=True)
503 'element-type takes at least one value, none given',
506 if value.length() > 2:
508 'element-type takes at most 2 values, %d given' % (
509 value.length()), self.position)
512 def _validate_out(self, option, value):
515 if value.length() > 1:
517 'out annotation takes at most 1 value, %d given' % (
518 value.length()), self.position)
520 value_str = value.one()
521 if value_str not in [OPT_OUT_CALLEE_ALLOCATES,
522 OPT_OUT_CALLER_ALLOCATES]:
523 message.warn("out annotation value is invalid: %r" % (
524 value_str), self.position)
527 def _get_gtk_doc_value(self):
528 def serialize_one(option, value, fmt, fmt2):
530 if type(value) != str:
531 value = ' '.join((serialize_one(k, v, '%s=%s', '%s')
532 for k, v in value.all().items()))
533 return fmt % (option, value)
535 return fmt2 % (option, )
537 for option, value in self.options.items():
539 serialize_one(option, value, '(%s %s)', '(%s)'))
541 return ' '.join(annotations) + ': '
545 def to_gtk_doc_param(self):
546 return '@%s: %s%s' % (self.name, self._get_gtk_doc_value(), self.comment)
548 def to_gtk_doc_tag(self):
549 return '%s: %s%s' % (self.name.capitalize(),
550 self._get_gtk_doc_value(),
554 if self.name == TAG_ATTRIBUTES:
555 # The 'Attributes:' tag allows free form annotations so the
556 # validation below is most certainly going to fail.
559 for option, value in self.options.items():
560 if option == OPT_ALLOW_NONE:
561 self._validate_option(option, value, n_params=0)
562 elif option == OPT_ARRAY:
563 self._validate_array(option, value)
564 elif option == OPT_ATTRIBUTE:
565 self._validate_option(option, value, n_params=2)
566 elif option == OPT_CLOSURE:
567 self._validate_closure(option, value)
568 elif option == OPT_DESTROY:
569 self._validate_option(option, value, n_params=1)
570 elif option == OPT_ELEMENT_TYPE:
571 self._validate_element_type(option, value)
572 elif option == OPT_FOREIGN:
573 self._validate_option(option, value, n_params=0)
574 elif option == OPT_IN:
575 self._validate_option(option, value, n_params=0)
576 elif option in [OPT_INOUT, OPT_INOUT_ALT]:
577 self._validate_option(option, value, n_params=0)
578 elif option == OPT_OUT:
579 self._validate_out(option, value)
580 elif option == OPT_SCOPE:
581 self._validate_option(
582 option, value, required=True,
584 choices=[OPT_SCOPE_ASYNC,
587 elif option == OPT_SKIP:
588 self._validate_option(option, value, n_params=0)
589 elif option == OPT_TRANSFER:
590 self._validate_option(
591 option, value, required=True,
593 choices=[OPT_TRANSFER_FULL,
594 OPT_TRANSFER_CONTAINER,
596 OPT_TRANSFER_FLOATING])
597 elif option == OPT_TYPE:
598 self._validate_option(option, value, required=True,
600 elif option == OPT_CONSTRUCTOR:
601 self._validate_option(option, value, n_params=0)
602 elif option == OPT_METHOD:
603 self._validate_option(option, value, n_params=0)
605 message.warn('invalid annotation option: %s' % (option, ),
609 class DocOptions(object):
615 return '<DocOptions %r>' % (self.values, )
617 def __getitem__(self, item):
618 for key, value in self.values:
623 def __nonzero__(self):
624 return bool(self.values)
627 return (k for k, v in self.values)
629 def add(self, name, value):
630 self.values.append((name, value))
632 def get(self, item, default=None):
633 for key, value in self.values:
638 def getall(self, item):
639 for key, value in self.values:
644 return iter(self.values)
647 class DocOption(object):
649 def __init__(self, tag, option):
653 # (annotation option1=value1 option2=value2) etc
654 for p in option.split(' '):
656 name, value = p.split('=', 1)
660 self._dict[name] = value
662 self._array.append(name)
664 self._array.append((name, value))
667 return '<DocOption %r>' % (self._array, )
670 return len(self._array)
673 assert len(self._array) == 1
674 return self._array[0]
683 class AnnotationParser(object):
685 GTK-Doc comment block parser.
687 Parses GTK-Doc comment blocks into a parse tree built out of :class:`DockBlock`,
688 :class:`DocTag`, :class:`DocOptions` and :class:`DocOption` objects. This
689 parser tries to accept malformed input whenever possible and does not emit
690 syntax errors. However, it does emit warnings at the slightest indication
691 of malformed input when possible. It is usually a good idea to heed these
692 warnings as malformed input is known to result in invalid GTK-Doc output.
694 A GTK-Doc comment block can be constructed out of multiple parts that can
695 be combined to write different types of documentation.
696 See `GTK-Doc's documentation`_ to learn more about possible valid combinations.
697 Each part can be further divided into fields which are separated by `:` characters.
699 Possible parts and the fields they are constructed from look like the
700 following (optional fields are enclosed in square brackets):
704 * identifier_name [:annotations]
705 * @parameter_name [:annotations] [:description]
707 * comment_block_description
708 * tag_name [:annotations] [:description]
711 The order in which the different parts have to be specified is important::
713 - There has to be exactly 1 `identifier` part on the first line of the
714 comment block which consists of:
715 * an `identifier_name` field
716 * an optional `annotations` field
717 - Followed by 0 or more `parameters` parts, each consisting of:
718 * a `parameter_name` field
719 * an optional `annotations` field
720 * an optional `description` field
721 - Followed by at least 1 empty line signaling the beginning of
722 the `comment_block_description` part
723 - Followed by an optional `comment block description` part.
724 - Followed by 0 or more `tag` parts, each consisting of:
726 * an optional `annotations` field
727 * an optional `description` field
729 Additionally, the following restrictions are in effect::
731 - Parts can optionally be separated by an empty line, except between
732 the `parameter` parts and the `comment block description` part where
733 an empty line is required (see above).
734 - Parts and fields cannot span multiple lines, except for
735 `parameter descriptions`, `tag descriptions` and the
736 `comment_block_description` fields.
737 - `parameter descriptions` fields can not span multiple paragraphs.
738 - `tag descriptions` and `comment block description` fields can
739 span multiple paragraphs.
741 .. NOTE:: :class:`AnnotationParser` functionality is heavily based on gtkdoc-mkdb's
742 `ScanSourceFile()`_ function and is currently in sync with GTK-Doc
745 .. _GTK-Doc's documentation:
746 http://developer.gnome.org/gtk-doc-manual/1.18/documenting.html.en
747 .. _ScanSourceFile():
748 http://git.gnome.org/browse/gtk-doc/tree/gtkdoc-mkdb.in#n3722
749 .. _47abcd5: 47abcd53b8489ebceec9e394676512a181c1f1f6
752 def parse(self, comments):
754 Parses multiple GTK-Doc comment blocks.
756 :param comments: a list of (comment, filename, lineno) tuples
757 :returns: a dictionary mapping identifier names to :class:`DocBlock` objects
762 for comment in comments:
763 comment_block = self.parse_comment_block(comment)
765 if comment_block is not None:
766 # Note: previous versions of this parser did not check
767 # if an identifier was already stored in comment_blocks,
768 # so when multiple comment blocks where encountered documenting
769 # the same identifier the last one seen "wins".
770 # Keep this behavior for backwards compatibility, but
772 if comment_block.name in comment_blocks:
773 message.warn("multiple comment blocks documenting '%s:' identifier." %
774 (comment_block.name),
775 comment_block.position)
777 comment_blocks[comment_block.name] = comment_block
779 return comment_blocks
781 def parse_comment_block(self, comment):
783 Parses a single GTK-Doc comment block.
785 :param comment: a (comment, filename, lineno) tuple
786 :returns: a :class:`DocBlock` object or ``None``
789 comment, filename, lineno = comment
791 # Assign line numbers to each line of the comment block,
792 # which will later be used as the offset to calculate the
793 # real line number in the source file
794 comment_lines = list(enumerate(comment.split('\n')))
796 # Check for the start the comment block.
797 if COMMENT_START_RE.match(comment_lines[0][1]):
800 # Not a GTK-Doc comment block.
803 # Check for the end the comment block.
804 if COMMENT_END_RE.match(comment_lines[-1][1]):
805 del comment_lines[-1]
807 # Not a GTK-Doc comment block.
810 # If we get this far, we are inside a GTK-Doc comment block.
811 return self._parse_comment_block(comment_lines, filename, lineno)
813 def _parse_comment_block(self, comment_lines, filename, lineno):
815 Parses a single GTK-Doc comment block already stripped from its
816 comment start (/**) and comment end (*/) marker lines.
818 :param comment_lines: list of (line_offset, line) tuples representing a
819 GTK-Doc comment block already stripped from it's
820 start (/**) and end (*/) marker lines
821 :param filename: source file name where the comment block originated from
822 :param lineno: line in the source file where the comment block starts
823 :returns: a :class:`DocBlock` object or ``None``
825 .. NOTE:: If you are tempted to refactor this method and split it
826 further up (for example into _parse_identifier(), _parse_parameters(),
827 _parse_description(), _parse_tags() methods) then please resist the
828 urge. It is considered important that this method should be more or
829 less easily comparable with gtkdoc-mkdb's `ScanSourceFile()`_ function.
831 The different parsing steps are marked with a comment surrounded
832 by `#` characters in an attempt to make it clear what is going on.
834 .. _ScanSourceFile():
835 http://git.gnome.org/browse/gtk-doc/tree/gtkdoc-mkdb.in#n3722
846 for line_offset, line in comment_lines:
847 position = message.Position(filename, line_offset + lineno)
849 # Store the original line (without \n) and column offset
850 # so we can generate meaningful warnings later on.
854 # Get rid of ' * ' at start of the line.
855 result = COMMENT_ASTERISK_RE.match(line)
857 column_offset = result.end(0)
858 line = line[result.end(0):]
860 # Store indentation level of the line.
861 result = COMMENT_INDENTATION_RE.match(line)
862 line_indent = len(result.group('indentation').replace('\t', ' '))
864 ####################################################################
865 # Check for GTK-Doc comment block identifier.
866 ####################################################################
867 if not comment_block:
869 result = SECTION_RE.match(line)
871 identifier = IDENTIFIER_SECTION
872 identifier_name = 'SECTION:%s' % (result.group('section_name'))
873 column = result.start('section_name') + column_offset
876 result = SYMBOL_RE.match(line)
878 identifier = IDENTIFIER_SYMBOL
879 identifier_name = '%s' % (result.group('symbol_name'))
880 column = result.start('symbol_name') + column_offset
883 result = PROPERTY_RE.match(line)
885 identifier = IDENTIFIER_PROPERTY
886 identifier_name = '%s:%s' % (result.group('class_name'),
887 result.group('property_name'))
888 column = result.start('property_name') + column_offset
891 result = SIGNAL_RE.match(line)
893 identifier = IDENTIFIER_SIGNAL
894 identifier_name = '%s::%s' % (result.group('class_name'),
895 result.group('signal_name'))
896 column = result.start('signal_name') + column_offset
899 in_part = PART_IDENTIFIER
900 part_indent = line_indent
902 comment_block = DocBlock(identifier_name)
903 comment_block.position = position
905 if 'colon' in result.groupdict() and result.group('colon') != ':':
906 colon_start = result.start('colon')
907 colon_column = column_offset + colon_start
908 marker = ' '*colon_column + '^'
909 message.warn("missing ':' at column %s:\n%s\n%s" %
910 (colon_column + 1, original_line, marker),
913 if 'annotations' in result.groupdict():
914 comment_block.options = self.parse_options(comment_block,
915 result.group('annotations'))
919 # If we get here, the identifier was not recognized, so
920 # ignore the rest of the block just like the old annotation
921 # parser did. Doing this is a bit more strict than
922 # gtkdoc-mkdb (which continues to search for the identifier
923 # until either it is found or the end of the block is
924 # reached). In practice, however, ignoring the block is the
925 # right thing to do because sooner or later some long
926 # descriptions will contain something matching an identifier
927 # pattern by accident.
928 marker = ' '*column_offset + '^'
929 message.warn('ignoring unrecognized GTK-Doc comment block, identifier not '
930 'found:\n%s\n%s' % (original_line, marker),
935 ####################################################################
936 # Check for comment block parameters.
937 ####################################################################
938 result = PARAMETER_RE.match(line)
940 param_name = result.group('parameter_name')
941 param_annotations = result.group('annotations')
942 param_description = result.group('description')
944 if in_part == PART_IDENTIFIER:
945 in_part = PART_PARAMETERS
947 part_indent = line_indent
949 if in_part != PART_PARAMETERS:
950 column = result.start('parameter_name') + column_offset
951 marker = ' '*column + '^'
952 message.warn("'@%s' parameter unexpected at this location:\n%s\n%s" %
953 (param_name, original_line, marker),
956 # Old style GTK-Doc allowed return values to be specified as
957 # parameters instead of tags.
958 if param_name.lower() == TAG_RETURNS:
959 param_name = TAG_RETURNS
964 message.warn("encountered multiple 'Returns' parameters or tags for "
965 "'%s'." % (comment_block.name),
967 elif param_name in comment_block.params.keys():
968 column = result.start('parameter_name') + column_offset
969 marker = ' '*column + '^'
970 message.warn("multiple '@%s' parameters for identifier '%s':\n%s\n%s" %
971 (param_name, comment_block.name, original_line, marker),
974 tag = DocTag(comment_block, param_name)
975 tag.position = position
976 tag.comment = param_description
977 if param_annotations:
978 tag.options = self.parse_options(tag, param_annotations)
979 if param_name == TAG_RETURNS:
980 comment_block.tags[param_name] = tag
982 comment_block.params[param_name] = tag
986 ####################################################################
987 # Check for comment block description.
989 # When we are parsing comment block parameters or the comment block
990 # identifier (when there are no parameters) and encounter an empty
991 # line, we must be parsing the comment block description.
992 ####################################################################
993 if (EMPTY_LINE_RE.match(line)
994 and in_part in [PART_IDENTIFIER, PART_PARAMETERS]):
995 in_part = PART_DESCRIPTION
996 part_indent = line_indent
999 ####################################################################
1000 # Check for GTK-Doc comment block tags.
1001 ####################################################################
1002 result = TAG_RE.match(line)
1003 if result and line_indent <= part_indent:
1004 tag_name = result.group('tag_name')
1005 tag_annotations = result.group('annotations')
1006 tag_description = result.group('description')
1008 marker = ' '*(result.start('tag_name') + column_offset) + '^'
1010 # Deprecated GTK-Doc Description: tag
1011 if tag_name.lower() == TAG_DESCRIPTION:
1012 message.warn("GTK-Doc tag \"Description:\" has been deprecated:\n%s\n%s" %
1013 (original_line, marker),
1016 in_part = PART_DESCRIPTION
1017 part_indent = line_indent
1019 if not comment_block.comment:
1020 comment_block.comment = tag_description
1022 comment_block.comment += '\n' + tag_description
1025 # Now that the deprecated stuff is out of the way, continue parsing real tags
1026 if in_part == PART_DESCRIPTION:
1029 part_indent = line_indent
1031 if in_part != PART_TAGS:
1032 column = result.start('tag_name') + column_offset
1033 marker = ' '*column + '^'
1034 message.warn("'%s:' tag unexpected at this location:\n%s\n%s" %
1035 (tag_name, original_line, marker),
1038 if tag_name.lower() in [TAG_RETURNS, TAG_RETURNVALUE]:
1039 if not returns_seen:
1042 message.warn("encountered multiple 'Returns' parameters or tags for "
1043 "'%s'." % (comment_block.name),
1046 tag = DocTag(comment_block, TAG_RETURNS)
1047 tag.position = position
1048 tag.comment = tag_description
1050 tag.options = self.parse_options(tag, tag_annotations)
1051 comment_block.tags[TAG_RETURNS] = tag
1055 if tag_name.lower() in comment_block.tags.keys():
1056 column = result.start('tag_name') + column_offset
1057 marker = ' '*column + '^'
1058 message.warn("multiple '%s:' tags for identifier '%s':\n%s\n%s" %
1059 (tag_name, comment_block.name, original_line, marker),
1062 tag = DocTag(comment_block, tag_name.lower())
1063 tag.position = position
1064 tag.value = tag_description
1066 if tag_name.lower() == TAG_ATTRIBUTES:
1067 tag.options = self.parse_options(tag, tag_annotations)
1069 message.warn("annotations not supported for tag '%s:'." %
1072 comment_block.tags[tag_name.lower()] = tag
1076 ####################################################################
1077 # If we get here, we must be in the middle of a multiline
1078 # comment block, parameter or tag description.
1079 ####################################################################
1080 if in_part in [PART_IDENTIFIER, PART_DESCRIPTION]:
1081 if not comment_block.comment:
1082 comment_block.comment = line
1084 comment_block.comment += '\n' + line
1086 elif in_part == PART_PARAMETERS:
1087 self._validate_multiline_annotation_continuation(line, original_line,
1088 column_offset, position)
1090 # Append to parameter description.
1091 current_param.comment += ' ' + line.strip()
1092 elif in_part == PART_TAGS:
1093 self._validate_multiline_annotation_continuation(line, original_line,
1094 column_offset, position)
1096 # Append to tag description.
1097 if current_tag.name.lower() in [TAG_RETURNS, TAG_RETURNVALUE]:
1098 current_tag.comment += ' ' + line.strip()
1100 current_tag.value += ' ' + line.strip()
1102 ########################################################################
1103 # Finished parsing this comment block.
1104 ########################################################################
1105 # We have picked up a couple of \n characters that where not
1106 # intended. Strip those.
1107 if comment_block.comment:
1108 comment_block.comment = comment_block.comment.strip()
1110 for tag in comment_block.tags.values():
1111 self._clean_comment_block_part(tag)
1113 for param in comment_block.params.values():
1114 self._clean_comment_block_part(param)
1116 # Validate and store block.
1117 comment_block.validate()
1118 return comment_block
1120 def _clean_comment_block_part(self, part):
1122 part.comment = part.comment.strip()
1127 part.value = part.value.strip()
1131 def _validate_multiline_annotation_continuation(self, line, original_line,
1132 column_offset, position):
1134 Validate parameters and tags (except the first line) and generate
1135 warnings about invalid annotations spanning multiple lines.
1137 :param line: line to validate, stripped from ' * ' at start of the line.
1138 :param original_line: original line to validate (used in warning messages)
1139 :param column_offset: column width of ' * ' at the time it was stripped from `line`
1140 :param position: position of `line` in the source file
1143 result = MULTILINE_ANNOTATION_CONTINUATION_RE.match(line)
1145 column = result.start('annotations') + column_offset
1146 marker = ' '*column + '^'
1147 message.warn('ignoring invalid multiline annotation continuation:\n'
1148 '%s\n%s' % (original_line, marker),
1152 def parse_options(cls, tag, value):
1154 # (annotation opt1 opt2 ...)
1155 # (annotation opt1=value1 opt2=value2 ...)
1157 options = DocOptions()
1158 options.position = tag.position
1160 for i, c in enumerate(value):
1161 if c == '(' and opened == -1:
1163 if c == ')' and opened != -1:
1164 segment = value[opened:i]
1165 parts = segment.split(' ', 1)
1167 name, option = parts
1168 elif len(parts) == 1:
1172 raise AssertionError
1173 if option is not None:
1174 option = DocOption(tag, option)
1175 options.add(name, option)