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())
393 for l in self.comment.split('\n'):
397 for tag in self.tags.values():
398 lines.append(tag.to_gtk_doc_tag())
405 comment += ' * %s\n' % (line, )
412 for param in self.params.values():
415 for tag in self.tags.values():
419 class DocTag(object):
421 def __init__(self, block, name):
424 self.options = DocOptions()
430 return '<DocTag %r %r>' % (self.name, self.options)
432 def _validate_option(self, name, value, required=False,
433 n_params=None, choices=None):
434 if required and value is None:
435 message.warn('%s annotation needs a value' % (
436 name, ), self.position)
439 if n_params is not None:
445 s = '%d values' % (n_params, )
446 if ((n_params > 0 and (value is None or value.length() != n_params)) or
447 n_params == 0 and value is not None):
451 length = value.length()
452 message.warn('%s annotation needs %s, not %d' % (
453 name, s, length), self.position)
456 if choices is not None:
457 valuestr = value.one()
458 if valuestr not in choices:
459 message.warn('invalid %s annotation value: %r' % (
460 name, valuestr, ), self.position)
463 def _validate_array(self, option, value):
467 for name, v in value.all().items():
468 if name in [OPT_ARRAY_ZERO_TERMINATED, OPT_ARRAY_FIXED_SIZE]:
471 except (TypeError, ValueError):
474 'array option %s needs a value' % (
476 positions=self.position)
479 'invalid array %s option value %r, '
480 'must be an integer' % (name, v, ),
481 positions=self.position)
482 elif name == OPT_ARRAY_LENGTH:
485 'array option length needs a value',
486 positions=self.position)
489 'invalid array annotation value: %r' % (
490 name, ), self.position)
492 def _validate_closure(self, option, value):
493 if value is not None and value.length() > 1:
495 'closure takes at most 1 value, %d given' % (
496 value.length()), self.position)
498 def _validate_element_type(self, option, value):
499 self._validate_option(option, value, required=True)
502 'element-type takes at least one value, none given',
505 if value.length() > 2:
507 'element-type takes at most 2 values, %d given' % (
508 value.length()), self.position)
511 def _validate_out(self, option, value):
514 if value.length() > 1:
516 'out annotation takes at most 1 value, %d given' % (
517 value.length()), self.position)
519 value_str = value.one()
520 if value_str not in [OPT_OUT_CALLEE_ALLOCATES,
521 OPT_OUT_CALLER_ALLOCATES]:
522 message.warn("out annotation value is invalid: %r" % (
523 value_str), self.position)
526 def _get_gtk_doc_value(self):
527 def serialize_one(option, value, fmt, fmt2):
529 if type(value) != str:
530 value = ' '.join((serialize_one(k, v, '%s=%s', '%s')
531 for k, v in value.all().items()))
532 return fmt % (option, value)
534 return fmt2 % (option, )
536 for option, value in self.options.items():
538 serialize_one(option, value, '(%s %s)', '(%s)'))
540 return ' '.join(annotations) + ': '
544 def to_gtk_doc_param(self):
545 return '@%s: %s%s' % (self.name, self._get_gtk_doc_value(), self.comment)
547 def to_gtk_doc_tag(self):
548 return '%s: %s%s' % (self.name.capitalize(),
549 self._get_gtk_doc_value(),
553 if self.name == TAG_ATTRIBUTES:
554 # The 'Attributes:' tag allows free form annotations so the
555 # validation below is most certainly going to fail.
558 for option, value in self.options.items():
559 if option == OPT_ALLOW_NONE:
560 self._validate_option(option, value, n_params=0)
561 elif option == OPT_ARRAY:
562 self._validate_array(option, value)
563 elif option == OPT_ATTRIBUTE:
564 self._validate_option(option, value, n_params=2)
565 elif option == OPT_CLOSURE:
566 self._validate_closure(option, value)
567 elif option == OPT_DESTROY:
568 self._validate_option(option, value, n_params=1)
569 elif option == OPT_ELEMENT_TYPE:
570 self._validate_element_type(option, value)
571 elif option == OPT_FOREIGN:
572 self._validate_option(option, value, n_params=0)
573 elif option == OPT_IN:
574 self._validate_option(option, value, n_params=0)
575 elif option in [OPT_INOUT, OPT_INOUT_ALT]:
576 self._validate_option(option, value, n_params=0)
577 elif option == OPT_OUT:
578 self._validate_out(option, value)
579 elif option == OPT_SCOPE:
580 self._validate_option(
581 option, value, required=True,
583 choices=[OPT_SCOPE_ASYNC,
586 elif option == OPT_SKIP:
587 self._validate_option(option, value, n_params=0)
588 elif option == OPT_TRANSFER:
589 self._validate_option(
590 option, value, required=True,
592 choices=[OPT_TRANSFER_FULL,
593 OPT_TRANSFER_CONTAINER,
595 OPT_TRANSFER_FLOATING])
596 elif option == OPT_TYPE:
597 self._validate_option(option, value, required=True,
599 elif option == OPT_CONSTRUCTOR:
600 self._validate_option(option, value, n_params=0)
601 elif option == OPT_METHOD:
602 self._validate_option(option, value, n_params=0)
604 message.warn('invalid annotation option: %s' % (option, ),
608 class DocOptions(object):
614 return '<DocOptions %r>' % (self.values, )
616 def __getitem__(self, item):
617 for key, value in self.values:
622 def __nonzero__(self):
623 return bool(self.values)
626 return (k for k, v in self.values)
628 def add(self, name, value):
629 self.values.append((name, value))
631 def get(self, item, default=None):
632 for key, value in self.values:
637 def getall(self, item):
638 for key, value in self.values:
643 return iter(self.values)
646 class DocOption(object):
648 def __init__(self, tag, option):
652 # (annotation option1=value1 option2=value2) etc
653 for p in option.split(' '):
655 name, value = p.split('=', 1)
659 self._dict[name] = value
661 self._array.append(name)
663 self._array.append((name, value))
666 return '<DocOption %r>' % (self._array, )
669 return len(self._array)
672 assert len(self._array) == 1
673 return self._array[0]
682 class AnnotationParser(object):
684 GTK-Doc comment block parser.
686 Parses GTK-Doc comment blocks into a parse tree built out of :class:`DockBlock`,
687 :class:`DocTag`, :class:`DocOptions` and :class:`DocOption` objects. This
688 parser tries to accept malformed input whenever possible and does not emit
689 syntax errors. However, it does emit warnings at the slightest indication
690 of malformed input when possible. It is usually a good idea to heed these
691 warnings as malformed input is known to result in invalid GTK-Doc output.
693 A GTK-Doc comment block can be constructed out of multiple parts that can
694 be combined to write different types of documentation.
695 See `GTK-Doc's documentation`_ to learn more about possible valid combinations.
696 Each part can be further divided into fields which are separated by `:` characters.
698 Possible parts and the fields they are constructed from look like the
699 following (optional fields are enclosed in square brackets):
703 * identifier_name [:annotations]
704 * @parameter_name [:annotations] [:description]
706 * comment_block_description
707 * tag_name [:annotations] [:description]
710 The order in which the different parts have to be specified is important::
712 - There has to be exactly 1 `identifier` part on the first line of the
713 comment block which consists of:
714 * an `identifier_name` field
715 * an optional `annotations` field
716 - Followed by 0 or more `parameters` parts, each consisting of:
717 * a `parameter_name` field
718 * an optional `annotations` field
719 * an optional `description` field
720 - Followed by at least 1 empty line signaling the beginning of
721 the `comment_block_description` part
722 - Followed by an optional `comment block description` part.
723 - Followed by 0 or more `tag` parts, each consisting of:
725 * an optional `annotations` field
726 * an optional `description` field
728 Additionally, the following restrictions are in effect::
730 - Parts can optionally be separated by an empty line, except between
731 the `parameter` parts and the `comment block description` part where
732 an empty line is required (see above).
733 - Parts and fields cannot span multiple lines, except for
734 `parameter descriptions`, `tag descriptions` and the
735 `comment_block_description` fields.
736 - `parameter descriptions` fields can not span multiple paragraphs.
737 - `tag descriptions` and `comment block description` fields can
738 span multiple paragraphs.
740 .. NOTE:: :class:`AnnotationParser` functionality is heavily based on gtkdoc-mkdb's
741 `ScanSourceFile()`_ function and is currently in sync with GTK-Doc
744 .. _GTK-Doc's documentation:
745 http://developer.gnome.org/gtk-doc-manual/1.18/documenting.html.en
746 .. _ScanSourceFile():
747 http://git.gnome.org/browse/gtk-doc/tree/gtkdoc-mkdb.in#n3722
748 .. _47abcd5: 47abcd53b8489ebceec9e394676512a181c1f1f6
751 def parse(self, comments):
753 Parses multiple GTK-Doc comment blocks.
755 :param comments: a list of (comment, filename, lineno) tuples
756 :returns: a dictionary mapping identifier names to :class:`DocBlock` objects
761 for comment in comments:
762 comment_block = self.parse_comment_block(comment)
764 if comment_block is not None:
765 # Note: previous versions of this parser did not check
766 # if an identifier was already stored in comment_blocks,
767 # so when multiple comment blocks where encountered documenting
768 # the same identifier the last one seen "wins".
769 # Keep this behavior for backwards compatibility, but
771 if comment_block.name in comment_blocks:
772 message.warn("multiple comment blocks documenting '%s:' identifier." %
773 (comment_block.name),
774 comment_block.position)
776 comment_blocks[comment_block.name] = comment_block
778 return comment_blocks
780 def parse_comment_block(self, comment):
782 Parses a single GTK-Doc comment block.
784 :param comment: a (comment, filename, lineno) tuple
785 :returns: a :class:`DocBlock` object or ``None``
788 comment, filename, lineno = comment
790 # Assign line numbers to each line of the comment block,
791 # which will later be used as the offset to calculate the
792 # real line number in the source file
793 comment_lines = list(enumerate(comment.split('\n')))
795 # Check for the start the comment block.
796 if COMMENT_START_RE.match(comment_lines[0][1]):
799 # Not a GTK-Doc comment block.
802 # Check for the end the comment block.
803 if COMMENT_END_RE.match(comment_lines[-1][1]):
804 del comment_lines[-1]
806 # Not a GTK-Doc comment block.
809 # If we get this far, we are inside a GTK-Doc comment block.
810 return self._parse_comment_block(comment_lines, filename, lineno)
812 def _parse_comment_block(self, comment_lines, filename, lineno):
814 Parses a single GTK-Doc comment block already stripped from its
815 comment start (/**) and comment end (*/) marker lines.
817 :param comment_lines: list of (line_offset, line) tuples representing a
818 GTK-Doc comment block already stripped from it's
819 start (/**) and end (*/) marker lines
820 :param filename: source file name where the comment block originated from
821 :param lineno: line in the source file where the comment block starts
822 :returns: a :class:`DocBlock` object or ``None``
824 .. NOTE:: If you are tempted to refactor this method and split it
825 further up (for example into _parse_identifier(), _parse_parameters(),
826 _parse_description(), _parse_tags() methods) then please resist the
827 urge. It is considered important that this method should be more or
828 less easily comparable with gtkdoc-mkdb's `ScanSourceFile()`_ function.
830 The different parsing steps are marked with a comment surrounded
831 by `#` characters in an attempt to make it clear what is going on.
833 .. _ScanSourceFile():
834 http://git.gnome.org/browse/gtk-doc/tree/gtkdoc-mkdb.in#n3722
845 for line_offset, line in comment_lines:
846 position = message.Position(filename, line_offset + lineno)
848 # Store the original line (without \n) and column offset
849 # so we can generate meaningful warnings later on.
853 # Get rid of ' * ' at start of the line.
854 result = COMMENT_ASTERISK_RE.match(line)
856 column_offset = result.end(0)
857 line = line[result.end(0):]
859 # Store indentation level of the line.
860 result = COMMENT_INDENTATION_RE.match(line)
861 line_indent = len(result.group('indentation').replace('\t', ' '))
863 ####################################################################
864 # Check for GTK-Doc comment block identifier.
865 ####################################################################
866 if not comment_block:
868 result = SECTION_RE.match(line)
870 identifier = IDENTIFIER_SECTION
871 identifier_name = 'SECTION:%s' % (result.group('section_name'))
872 column = result.start('section_name') + column_offset
875 result = SYMBOL_RE.match(line)
877 identifier = IDENTIFIER_SYMBOL
878 identifier_name = '%s' % (result.group('symbol_name'))
879 column = result.start('symbol_name') + column_offset
882 result = PROPERTY_RE.match(line)
884 identifier = IDENTIFIER_PROPERTY
885 identifier_name = '%s:%s' % (result.group('class_name'),
886 result.group('property_name'))
887 column = result.start('property_name') + column_offset
890 result = SIGNAL_RE.match(line)
892 identifier = IDENTIFIER_SIGNAL
893 identifier_name = '%s::%s' % (result.group('class_name'),
894 result.group('signal_name'))
895 column = result.start('signal_name') + column_offset
898 in_part = PART_IDENTIFIER
899 part_indent = line_indent
901 comment_block = DocBlock(identifier_name)
902 comment_block.position = position
904 if 'colon' in result.groupdict() and result.group('colon') != ':':
905 colon_start = result.start('colon')
906 colon_column = column_offset + colon_start
907 marker = ' '*colon_column + '^'
908 message.warn("missing ':' at column %s:\n%s\n%s" %
909 (colon_column + 1, original_line, marker),
912 if 'annotations' in result.groupdict():
913 comment_block.options = self.parse_options(comment_block,
914 result.group('annotations'))
918 # If we get here, the identifier was not recognized, so
919 # ignore the rest of the block just like the old annotation
920 # parser did. Doing this is a bit more strict than
921 # gtkdoc-mkdb (which continues to search for the identifier
922 # until either it is found or the end of the block is
923 # reached). In practice, however, ignoring the block is the
924 # right thing to do because sooner or later some long
925 # descriptions will contain something matching an identifier
926 # pattern by accident.
927 marker = ' '*column_offset + '^'
928 message.warn('ignoring unrecognized GTK-Doc comment block, identifier not '
929 'found:\n%s\n%s' % (original_line, marker),
934 ####################################################################
935 # Check for comment block parameters.
936 ####################################################################
937 result = PARAMETER_RE.match(line)
939 param_name = result.group('parameter_name')
940 param_annotations = result.group('annotations')
941 param_description = result.group('description')
943 if in_part == PART_IDENTIFIER:
944 in_part = PART_PARAMETERS
946 part_indent = line_indent
948 if in_part != PART_PARAMETERS:
949 column = result.start('parameter_name') + column_offset
950 marker = ' '*column + '^'
951 message.warn("'@%s' parameter unexpected at this location:\n%s\n%s" %
952 (param_name, original_line, marker),
955 # Old style GTK-Doc allowed return values to be specified as
956 # parameters instead of tags.
957 if param_name.lower() == TAG_RETURNS:
958 param_name = TAG_RETURNS
963 message.warn("encountered multiple 'Returns' parameters or tags for "
964 "'%s'." % (comment_block.name),
966 elif param_name in comment_block.params.keys():
967 column = result.start('parameter_name') + column_offset
968 marker = ' '*column + '^'
969 message.warn("multiple '@%s' parameters for identifier '%s':\n%s\n%s" %
970 (param_name, comment_block.name, original_line, marker),
973 tag = DocTag(comment_block, param_name)
974 tag.position = position
975 tag.comment = param_description
976 if param_annotations:
977 tag.options = self.parse_options(tag, param_annotations)
978 if param_name == TAG_RETURNS:
979 comment_block.tags[param_name] = tag
981 comment_block.params[param_name] = tag
985 ####################################################################
986 # Check for comment block description.
988 # When we are parsing comment block parameters or the comment block
989 # identifier (when there are no parameters) and encounter an empty
990 # line, we must be parsing the comment block description.
991 ####################################################################
992 if (EMPTY_LINE_RE.match(line)
993 and in_part in [PART_IDENTIFIER, PART_PARAMETERS]):
994 in_part = PART_DESCRIPTION
995 part_indent = line_indent
998 ####################################################################
999 # Check for GTK-Doc comment block tags.
1000 ####################################################################
1001 result = TAG_RE.match(line)
1002 if result and line_indent <= part_indent:
1003 tag_name = result.group('tag_name')
1004 tag_annotations = result.group('annotations')
1005 tag_description = result.group('description')
1007 marker = ' '*(result.start('tag_name') + column_offset) + '^'
1009 # Deprecated GTK-Doc Description: tag
1010 if tag_name.lower() == TAG_DESCRIPTION:
1011 message.warn("GTK-Doc tag \"Description:\" has been deprecated:\n%s\n%s" %
1012 (original_line, marker),
1015 in_part = PART_DESCRIPTION
1016 part_indent = line_indent
1018 if not comment_block.comment:
1019 comment_block.comment = tag_description
1021 comment_block.comment += '\n' + tag_description
1024 # Now that the deprecated stuff is out of the way, continue parsing real tags
1025 if in_part == PART_DESCRIPTION:
1028 part_indent = line_indent
1030 if in_part != PART_TAGS:
1031 column = result.start('tag_name') + column_offset
1032 marker = ' '*column + '^'
1033 message.warn("'%s:' tag unexpected at this location:\n%s\n%s" %
1034 (tag_name, original_line, marker),
1037 if tag_name.lower() in [TAG_RETURNS, TAG_RETURNVALUE]:
1038 if not returns_seen:
1041 message.warn("encountered multiple 'Returns' parameters or tags for "
1042 "'%s'." % (comment_block.name),
1045 tag = DocTag(comment_block, TAG_RETURNS)
1046 tag.position = position
1047 tag.comment = tag_description
1049 tag.options = self.parse_options(tag, tag_annotations)
1050 comment_block.tags[TAG_RETURNS] = tag
1054 if tag_name.lower() in comment_block.tags.keys():
1055 column = result.start('tag_name') + column_offset
1056 marker = ' '*column + '^'
1057 message.warn("multiple '%s:' tags for identifier '%s':\n%s\n%s" %
1058 (tag_name, comment_block.name, original_line, marker),
1061 tag = DocTag(comment_block, tag_name.lower())
1062 tag.position = position
1063 tag.value = tag_description
1065 if tag_name.lower() == TAG_ATTRIBUTES:
1066 tag.options = self.parse_options(tag, tag_annotations)
1068 message.warn("annotations not supported for tag '%s:'." %
1071 comment_block.tags[tag_name.lower()] = tag
1075 ####################################################################
1076 # If we get here, we must be in the middle of a multiline
1077 # comment block, parameter or tag description.
1078 ####################################################################
1079 if in_part in [PART_IDENTIFIER, PART_DESCRIPTION]:
1080 if not comment_block.comment:
1081 comment_block.comment = line
1083 comment_block.comment += '\n' + line
1085 elif in_part == PART_PARAMETERS:
1086 self._validate_multiline_annotation_continuation(line, original_line,
1087 column_offset, position)
1089 # Append to parameter description.
1090 current_param.comment += ' ' + line.strip()
1091 elif in_part == PART_TAGS:
1092 self._validate_multiline_annotation_continuation(line, original_line,
1093 column_offset, position)
1095 # Append to tag description.
1096 if current_tag.name.lower() in [TAG_RETURNS, TAG_RETURNVALUE]:
1097 current_tag.comment += ' ' + line.strip()
1099 current_tag.value += ' ' + line.strip()
1101 ########################################################################
1102 # Finished parsing this comment block.
1103 ########################################################################
1104 # We have picked up a couple of \n characters that where not
1105 # intended. Strip those.
1106 if comment_block.comment:
1107 comment_block.comment = comment_block.comment.strip()
1109 comment_block.comment = ''
1111 for tag in comment_block.tags.values():
1112 self._clean_comment_block_part(tag)
1114 for param in comment_block.params.values():
1115 self._clean_comment_block_part(param)
1117 # Validate and store block.
1118 comment_block.validate()
1119 return comment_block
1121 def _clean_comment_block_part(self, part):
1123 part.comment = part.comment.strip()
1128 part.value = part.value.strip()
1132 def _validate_multiline_annotation_continuation(self, line, original_line,
1133 column_offset, position):
1135 Validate parameters and tags (except the first line) and generate
1136 warnings about invalid annotations spanning multiple lines.
1138 :param line: line to validate, stripped from ' * ' at start of the line.
1139 :param original_line: original line to validate (used in warning messages)
1140 :param column_offset: column width of ' * ' at the time it was stripped from `line`
1141 :param position: position of `line` in the source file
1144 result = MULTILINE_ANNOTATION_CONTINUATION_RE.match(line)
1146 column = result.start('annotations') + column_offset
1147 marker = ' '*column + '^'
1148 message.warn('ignoring invalid multiline annotation continuation:\n'
1149 '%s\n%s' % (original_line, marker),
1153 def parse_options(cls, tag, value):
1155 # (annotation opt1 opt2 ...)
1156 # (annotation opt1=value1 opt2=value2 ...)
1158 options = DocOptions()
1159 options.position = tag.position
1161 for i, c in enumerate(value):
1162 if c == '(' and opened == -1:
1164 if c == ')' and opened != -1:
1165 segment = value[opened:i]
1166 parts = segment.split(' ', 1)
1168 name, option = parts
1169 elif len(parts) == 1:
1173 raise AssertionError
1174 if option is not None:
1175 option = DocOption(tag, option)
1176 options.add(name, option)