97704801383e5e2c7f43a489fae25a5ec514df05
[platform/upstream/gobject-introspection.git] / giscanner / annotationparser.py
1 # -*- Mode: Python -*-
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>
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
20 #
21
22
23 # AnnotationParser - extract annotations from GTK-Doc comment blocks
24
25
26 import re
27
28 from . import message
29 from .annotationpatterns import (COMMENT_START_RE, COMMENT_END_RE,
30                                  COMMENT_ASTERISK_RE, EMPTY_LINE_RE,
31                                  SECTION_RE, SYMBOL_RE, PROPERTY_RE, SIGNAL_RE,
32                                  PARAMETER_RE, DESCRIPTION_TAG_RE, TAG_RE,
33                                  MULTILINE_ANNOTATION_CONTINUATION_RE)
34 from .odict import odict
35
36
37 # GTK-Doc comment block parts
38 PART_IDENTIFIER = 'identifier'
39 PART_PARAMETERS = 'parameters'
40 PART_DESCRIPTION = 'description'
41 PART_TAGS = 'tags'
42
43 # Identifiers
44 IDENTIFIER_SECTION = 'section'
45 IDENTIFIER_SYMBOL = 'symbol'
46 IDENTIFIER_PROPERTY = 'property'
47 IDENTIFIER_SIGNAL = 'signal'
48
49 # Tags - annotations applied to comment blocks
50 TAG_VFUNC = 'virtual'
51 TAG_SINCE = 'since'
52 TAG_STABILITY = 'stability'
53 TAG_DEPRECATED = 'deprecated'
54 TAG_RETURNS = 'returns'
55 TAG_RETURNVALUE = 'return value'
56 TAG_ATTRIBUTES = 'attributes'
57 TAG_RENAME_TO = 'rename to'
58 TAG_TYPE = 'type'
59 TAG_UNREF_FUNC = 'unref func'
60 TAG_REF_FUNC = 'ref func'
61 TAG_SET_VALUE_FUNC = 'set value func'
62 TAG_GET_VALUE_FUNC = 'get value func'
63 TAG_TRANSFER = 'transfer'
64 TAG_VALUE = 'value'
65 _ALL_TAGS = [TAG_VFUNC,
66              TAG_SINCE,
67              TAG_STABILITY,
68              TAG_DEPRECATED,
69              TAG_RETURNS,
70              TAG_RETURNVALUE,
71              TAG_ATTRIBUTES,
72              TAG_RENAME_TO,
73              TAG_TYPE,
74              TAG_UNREF_FUNC,
75              TAG_REF_FUNC,
76              TAG_SET_VALUE_FUNC,
77              TAG_GET_VALUE_FUNC,
78              TAG_TRANSFER,
79              TAG_VALUE]
80
81 # Options - annotations for parameters and return values
82 OPT_ALLOW_NONE = 'allow-none'
83 OPT_ARRAY = 'array'
84 OPT_ATTRIBUTE = 'attribute'
85 OPT_CLOSURE = 'closure'
86 OPT_DESTROY = 'destroy'
87 OPT_ELEMENT_TYPE = 'element-type'
88 OPT_FOREIGN = 'foreign'
89 OPT_IN = 'in'
90 OPT_INOUT = 'inout'
91 OPT_INOUT_ALT = 'in-out'
92 OPT_OUT = 'out'
93 OPT_SCOPE = 'scope'
94 OPT_TRANSFER = 'transfer'
95 OPT_TYPE = 'type'
96 OPT_SKIP = 'skip'
97 OPT_CONSTRUCTOR = 'constructor'
98 OPT_METHOD = 'method'
99
100 ALL_OPTIONS = [
101     OPT_ALLOW_NONE,
102     OPT_ARRAY,
103     OPT_ATTRIBUTE,
104     OPT_CLOSURE,
105     OPT_DESTROY,
106     OPT_ELEMENT_TYPE,
107     OPT_FOREIGN,
108     OPT_IN,
109     OPT_INOUT,
110     OPT_INOUT_ALT,
111     OPT_OUT,
112     OPT_SCOPE,
113     OPT_TRANSFER,
114     OPT_TYPE,
115     OPT_SKIP,
116     OPT_CONSTRUCTOR,
117     OPT_METHOD]
118
119 # Array options - array specific annotations
120 OPT_ARRAY_FIXED_SIZE = 'fixed-size'
121 OPT_ARRAY_LENGTH = 'length'
122 OPT_ARRAY_ZERO_TERMINATED = 'zero-terminated'
123
124 # Out options
125 OPT_OUT_CALLER_ALLOCATES = 'caller-allocates'
126 OPT_OUT_CALLEE_ALLOCATES = 'callee-allocates'
127
128 # Scope options
129 OPT_SCOPE_ASYNC = 'async'
130 OPT_SCOPE_CALL = 'call'
131 OPT_SCOPE_NOTIFIED = 'notified'
132
133 # Transfer options
134 OPT_TRANSFER_NONE = 'none'
135 OPT_TRANSFER_CONTAINER = 'container'
136 OPT_TRANSFER_FULL = 'full'
137 OPT_TRANSFER_FLOATING = 'floating'
138
139
140 class DocBlock(object):
141
142     def __init__(self, name):
143         self.name = name
144         self.options = DocOptions()
145         self.value = None
146         self.tags = odict()
147         self.comment = None
148         self.params = odict()
149         self.position = None
150
151     def __cmp__(self, other):
152         return cmp(self.name, other.name)
153
154     def __repr__(self):
155         return '<DocBlock %r %r>' % (self.name, self.options)
156
157     def set_position(self, position):
158         self.position = position
159         self.options.position = position
160
161     def get_tag(self, name):
162         return self.tags.get(name)
163
164     def get_param(self, name):
165         return self.params.get(name)
166
167     def to_gtk_doc(self):
168         options = ''
169         if self.options:
170             options += ' '
171             options += ' '.join('(%s)' % o for o in self.options)
172         lines = [self.name]
173         if 'SECTION' not in self.name:
174             lines[0] += ':'
175         lines[0] += options
176         for param in self.params.values():
177             lines.append(param.to_gtk_doc_param())
178         lines.append('')
179         for l in self.comment.split('\n'):
180             lines.append(l)
181         if self.tags:
182             lines.append('')
183             for tag in self.tags.values():
184                 lines.append(tag.to_gtk_doc_tag())
185
186         comment = ''
187         comment += '/**\n'
188         for line in lines:
189             line = line.rstrip()
190             if line:
191                 comment += ' * %s\n' % (line, )
192             else:
193                 comment += ' *\n'
194         comment += ' */\n'
195         return comment
196
197     def validate(self):
198         for param in self.params.values():
199             param.validate()
200
201         for tag in self.tags.values():
202             tag.validate()
203
204
205 class DocTag(object):
206
207     def __init__(self, block, name):
208         self.block = block
209         self.name = name
210         self.options = DocOptions()
211         self.comment = None
212         self.value = ''
213         self.position = None
214
215     def __repr__(self):
216         return '<DocTag %r %r>' % (self.name, self.options)
217
218     def _validate_option(self, name, value, required=False,
219                          n_params=None, choices=None):
220         if required and value is None:
221             message.warn('%s annotation needs a value' % (
222                 name, ), self.position)
223             return
224
225         if n_params is not None:
226             if n_params == 0:
227                 s = 'no value'
228             elif n_params == 1:
229                 s = 'one value'
230             else:
231                 s = '%d values' % (n_params, )
232             if ((n_params > 0 and (value is None or value.length() != n_params)) or
233                 n_params == 0 and value is not None):
234                 if value is None:
235                     length = 0
236                 else:
237                     length = value.length()
238                 message.warn('%s annotation needs %s, not %d' % (
239                     name, s, length), self.position)
240                 return
241
242         if choices is not None:
243             valuestr = value.one()
244             if valuestr not in choices:
245                 message.warn('invalid %s annotation value: %r' % (
246                     name, valuestr, ), self.position)
247                 return
248
249     def _validate_array(self, option, value):
250         if value is None:
251             return
252
253         for name, v in value.all().items():
254             if name in [OPT_ARRAY_ZERO_TERMINATED, OPT_ARRAY_FIXED_SIZE]:
255                 try:
256                     int(v)
257                 except (TypeError, ValueError):
258                     if v is None:
259                         message.warn(
260                             'array option %s needs a value' % (
261                             name, ),
262                             positions=self.position)
263                     else:
264                         message.warn(
265                             'invalid array %s option value %r, '
266                             'must be an integer' % (name, v, ),
267                             positions=self.position)
268             elif name == OPT_ARRAY_LENGTH:
269                 if v is None:
270                     message.warn(
271                         'array option length needs a value',
272                         positions=self.position)
273             else:
274                 message.warn(
275                     'invalid array annotation value: %r' % (
276                     name, ), self.position)
277
278     def _validate_closure(self, option, value):
279         if value is not None and value.length() > 1:
280             message.warn(
281                 'closure takes at most 1 value, %d given' % (
282                 value.length()), self.position)
283
284     def _validate_element_type(self, option, value):
285         self._validate_option(option, value, required=True)
286         if value is None:
287             message.warn(
288                 'element-type takes at least one value, none given',
289                 self.position)
290             return
291         if value.length() > 2:
292             message.warn(
293                 'element-type takes at most 2 values, %d given' % (
294                 value.length()), self.position)
295             return
296
297     def _validate_out(self, option, value):
298         if value is None:
299             return
300         if value.length() > 1:
301             message.warn(
302                 'out annotation takes at most 1 value, %d given' % (
303                 value.length()), self.position)
304             return
305         value_str = value.one()
306         if value_str not in [OPT_OUT_CALLEE_ALLOCATES,
307                              OPT_OUT_CALLER_ALLOCATES]:
308             message.warn("out annotation value is invalid: %r" % (
309                 value_str), self.position)
310             return
311
312     def set_position(self, position):
313         self.position = position
314         self.options.position = position
315
316     def _get_gtk_doc_value(self):
317         def serialize_one(option, value, fmt, fmt2):
318             if value:
319                 if type(value) != str:
320                     value = ' '.join((serialize_one(k, v, '%s=%s', '%s')
321                                       for k, v in value.all().items()))
322                 return fmt % (option, value)
323             else:
324                 return fmt2 % (option, )
325         annotations = []
326         for option, value in self.options.items():
327             annotations.append(
328                 serialize_one(option, value, '(%s %s)', '(%s)'))
329         if annotations:
330             return ' '.join(annotations) + ': '
331         else:
332             return self.value
333
334     def to_gtk_doc_param(self):
335         return '@%s: %s%s' % (self.name, self._get_gtk_doc_value(), self.comment)
336
337     def to_gtk_doc_tag(self):
338         return '%s: %s%s' % (self.name.capitalize(),
339                              self._get_gtk_doc_value(),
340                              self.comment or '')
341
342     def validate(self):
343         if self.name == TAG_ATTRIBUTES:
344             # The 'Attributes:' tag allows free form annotations so the
345             # validation below is most certainly going to fail.
346             return
347
348         for option, value in self.options.items():
349             if option == OPT_ALLOW_NONE:
350                 self._validate_option(option, value, n_params=0)
351             elif option == OPT_ARRAY:
352                 self._validate_array(option, value)
353             elif option == OPT_ATTRIBUTE:
354                 self._validate_option(option, value, n_params=2)
355             elif option == OPT_CLOSURE:
356                 self._validate_closure(option, value)
357             elif option == OPT_DESTROY:
358                 self._validate_option(option, value, n_params=1)
359             elif option == OPT_ELEMENT_TYPE:
360                 self._validate_element_type(option, value)
361             elif option == OPT_FOREIGN:
362                 self._validate_option(option, value, n_params=0)
363             elif option == OPT_IN:
364                 self._validate_option(option, value, n_params=0)
365             elif option in [OPT_INOUT, OPT_INOUT_ALT]:
366                 self._validate_option(option, value, n_params=0)
367             elif option == OPT_OUT:
368                 self._validate_out(option, value)
369             elif option == OPT_SCOPE:
370                 self._validate_option(
371                     option, value, required=True,
372                     n_params=1,
373                     choices=[OPT_SCOPE_ASYNC,
374                              OPT_SCOPE_CALL,
375                              OPT_SCOPE_NOTIFIED])
376             elif option == OPT_SKIP:
377                 self._validate_option(option, value, n_params=0)
378             elif option == OPT_TRANSFER:
379                 self._validate_option(
380                     option, value, required=True,
381                     n_params=1,
382                     choices=[OPT_TRANSFER_FULL,
383                              OPT_TRANSFER_CONTAINER,
384                              OPT_TRANSFER_NONE,
385                              OPT_TRANSFER_FLOATING])
386             elif option == OPT_TYPE:
387                 self._validate_option(option, value, required=True,
388                                       n_params=1)
389             elif option == OPT_CONSTRUCTOR:
390                 self._validate_option(option, value, n_params=0)
391             elif option == OPT_METHOD:
392                 self._validate_option(option, value, n_params=0)
393             else:
394                 message.warn('invalid annotation option: %s' % (option, ),
395                              self.position)
396
397
398 class DocOptions(object):
399     def __init__(self):
400         self.values = []
401
402     def __repr__(self):
403         return '<DocOptions %r>' % (self.values, )
404
405     def __getitem__(self, item):
406         for key, value in self.values:
407             if key == item:
408                 return value
409         raise KeyError
410
411     def __nonzero__(self):
412         return bool(self.values)
413
414     def __iter__(self):
415         return (k for k, v in self.values)
416
417     def add(self, name, value):
418         self.values.append((name, value))
419
420     def get(self, item, default=None):
421         for key, value in self.values:
422             if key == item:
423                 return value
424         return default
425
426     def getall(self, item):
427         for key, value in self.values:
428             if key == item:
429                 yield value
430
431     def items(self):
432         return iter(self.values)
433
434
435 class DocOption(object):
436
437     def __init__(self, tag, option):
438         self.tag = tag
439         self._array = []
440         self._dict = {}
441         # (annotation option1=value1 option2=value2) etc
442         for p in option.split(' '):
443             if '=' in p:
444                 name, value = p.split('=', 1)
445             else:
446                 name = p
447                 value = None
448             self._dict[name] = value
449             if value is None:
450                 self._array.append(name)
451             else:
452                 self._array.append((name, value))
453
454     def __repr__(self):
455         return '<DocOption %r>' % (self._array, )
456
457     def length(self):
458         return len(self._array)
459
460     def one(self):
461         assert len(self._array) == 1
462         return self._array[0]
463
464     def flat(self):
465         return self._array
466
467     def all(self):
468         return self._dict
469
470
471 class AnnotationParser(object):
472     """
473     GTK-Doc comment block parser.
474
475     Parses GTK-Doc comment blocks into a parse tree built out of :class:`DockBlock`,
476     :class:`DocTag`, :class:`DocOptions` and :class:`DocOption` objects. This
477     parser tries to accept malformed input whenever possible and does not emit
478     syntax errors. However, it does emit warnings at the slightest indication
479     of malformed input when possible. It is usually a good idea to heed these
480     warnings as malformed input is known to result in invalid GTK-Doc output.
481
482     A GTK-Doc comment block can be constructed out of multiple parts that can
483     be combined to write different types of documentation.
484     See `GTK-Doc's documentation`_ to learn more about possible valid combinations.
485     Each part can be further divided into fields which are separated by `:` characters.
486
487     Possible parts and the fields they are constructed from look like the
488     following (optional fields are enclosed in square brackets):
489
490     .. code-block:: c
491         /**
492          * identifier_name [:annotations]
493          * @parameter_name [:annotations] [:description]
494          *
495          * comment_block_description
496          * tag_name [:annotations] [:description]
497          */
498
499     The order in which the different parts have to be specified is important::
500
501         - There has to be exactly 1 `identifier` part on the first line of the
502           comment block which consists of:
503               * an `identifier_name` field
504               * an optional `annotations` field
505         - Followed by 0 or more `parameters` parts, each consisting of:
506               * a `parameter_name` field
507               * an optional `annotations` field
508               * an optional `description` field
509         - Followed by at least 1 empty line signaling the beginning of
510           the `comment_block_description` part
511         - Followed by an optional `comment block description` part.
512         - Followed by 0 or more `tag` parts, each consisting of:
513               * a `tag_name` field
514               * an optional `annotations` field
515               * an optional `description` field
516
517     Additionally, the following restrictions are in effect::
518
519         - Parts can optionally be separated by an empty line, except between
520           the `parameter` parts and the `comment block description` part where
521           an empty line is required (see above).
522         - Parts and fields cannot span multiple lines, except for
523           `parameter descriptions`, `tag descriptions` and the
524           `comment_block_description` fields.
525         - `parameter descriptions` fields can not span multiple paragraphs.
526         - `tag descriptions` and `comment block description` fields can
527           span multiple paragraphs.
528
529     .. NOTE:: :class:`AnnotationParser` functionality is heavily based on gtkdoc-mkdb's
530         `ScanSourceFile()`_ function and is currently in sync with GTK-Doc
531         commit `b41641b`_.
532
533     .. _GTK-Doc's documentation:
534             http://developer.gnome.org/gtk-doc-manual/1.18/documenting.html.en
535     .. _ScanSourceFile():
536             http://git.gnome.org/browse/gtk-doc/tree/gtkdoc-mkdb.in#n3722
537     .. _b41641b: b41641bd75f870afff7561ceed8a08456da57565
538     """
539
540     def parse(self, comments):
541         """
542         Parses multiple GTK-Doc comment blocks.
543
544         :param comments: a list of (comment, filename, lineno) tuples
545         :returns: a dictionary mapping identifier names to :class:`DocBlock` objects
546         """
547
548         comment_blocks = {}
549
550         for comment in comments:
551             comment_block = self.parse_comment_block(comment)
552
553             if comment_block is not None:
554                 # Note: previous versions of this parser did not check
555                 # if an identifier was already stored in comment_blocks,
556                 # so when multiple comment blocks where encountered documenting
557                 # the same identifier the last one seen "wins".
558                 # Keep this behavior for backwards compatibility, but
559                 # emit a warning.
560                 if comment_block.name in comment_blocks:
561                     message.warn("multiple comment blocks documenting '%s:' identifier." %
562                                  (comment_block.name),
563                                  comment_block.position)
564
565                 comment_blocks[comment_block.name] = comment_block
566
567         return comment_blocks
568
569     def parse_comment_block(self, comment):
570         """
571         Parses a single GTK-Doc comment block.
572
573         :param comment: a (comment, filename, lineno) tuple
574         :returns: a :class:`DocBlock` object or ``None``
575         """
576
577         comment, filename, lineno = comment
578
579         # Assign line numbers to each line of the comment block,
580         # which will later be used as the offset to calculate the
581         # real line number in the source file
582         comment_lines = list(enumerate(comment.split('\n')))
583
584         # Check for the start the comment block.
585         if COMMENT_START_RE.search(comment_lines[0][1]):
586             del comment_lines[0]
587         else:
588             # Not a GTK-Doc comment block.
589             return None
590
591         # Check for the end the comment block.
592         if COMMENT_END_RE.search(comment_lines[-1][1]):
593             del comment_lines[-1]
594
595         # If we get this far, we are inside a GTK-Doc comment block.
596         return self._parse_comment_block(comment_lines, filename, lineno)
597
598     def _parse_comment_block(self, comment_lines, filename, lineno):
599         """
600         Parses a single GTK-Doc comment block already stripped from its
601         comment start (/**) and comment end (*/) marker lines.
602
603         :param comment_lines: list of (line_offset, line) tuples representing a
604                               GTK-Doc comment block already stripped from it's
605                               start (/**) and end (*/) marker lines
606         :param filename: source file name where the comment block originated from
607         :param lineno:  line in the source file where the comment block starts
608         :returns: a :class:`DocBlock` object or ``None``
609
610         .. NOTE:: If you are tempted to refactor this method and split it
611             further up (for example into _parse_identifier(), _parse_parameters(),
612             _parse_description(), _parse_tags() methods) then please resist the
613             urge. It is considered important that this method should be more or
614             less easily comparable with gtkdoc-mkdb's `ScanSourceFile()`_ function.
615
616             The different parsing steps are marked with a comment surrounded
617             by `#` characters in an attempt to make it clear what is going on.
618
619         .. _ScanSourceFile():
620                 http://git.gnome.org/browse/gtk-doc/tree/gtkdoc-mkdb.in#n3722
621         """
622         comment_block = None
623         in_part = None
624         identifier = None
625         current_param = None
626         current_tag = None
627         returns_seen = False
628
629         for line_offset, line in comment_lines:
630             position = message.Position(filename, line_offset + lineno)
631
632             # Store the original line (without \n) and column offset
633             # so we can generate meaningful warnings later on.
634             original_line = line
635             column_offset = 0
636
637             # Get rid of ' * ' at start of the line.
638             result = COMMENT_ASTERISK_RE.match(line)
639             if result:
640                 column_offset = result.end(0)
641                 line = line[result.end(0):]
642
643             ####################################################################
644             # Check for GTK-Doc comment block identifier.
645             ####################################################################
646             if not comment_block:
647                 # The correct identifier name would have the colon at the end
648                 # but maintransformer.py does not expect us to do that. So
649                 # make sure to compute an identifier_name without the colon and
650                 # a real_identifier_name with the colon.
651
652                 if not identifier:
653                     result = SECTION_RE.search(line)
654                     if result:
655                         identifier = IDENTIFIER_SECTION
656                         real_identifier_name = 'SECTION:%s' % (result.group('section_name'))
657                         identifier_name = real_identifier_name
658                         column = result.start('section_name') + column_offset
659
660                 if not identifier:
661                     result = SYMBOL_RE.search(line)
662                     if result:
663                         identifier = IDENTIFIER_SYMBOL
664                         real_identifier_name = '%s:' % (result.group('symbol_name'))
665                         identifier_name = '%s' % (result.group('symbol_name'))
666                         column = result.start('symbol_name') + column_offset
667
668                 if not identifier:
669                     result = PROPERTY_RE.search(line)
670                     if result:
671                         identifier = IDENTIFIER_PROPERTY
672                         real_identifier_name = '%s:%s:' % (result.group('class_name'),
673                                                            result.group('property_name'))
674                         identifier_name = '%s:%s' % (result.group('class_name'),
675                                                      result.group('property_name'))
676                         column = result.start('property_name') + column_offset
677
678                 if not identifier:
679                     result = SIGNAL_RE.search(line)
680                     if result:
681                         identifier = IDENTIFIER_SIGNAL
682                         real_identifier_name = '%s::%s:' % (result.group('class_name'),
683                                                             result.group('signal_name'))
684                         identifier_name = '%s::%s' % (result.group('class_name'),
685                                                       result.group('signal_name'))
686                         column = result.start('signal_name') + column_offset
687
688                 if identifier:
689                     in_part = PART_IDENTIFIER
690
691                     comment_block = DocBlock(identifier_name)
692                     comment_block.set_position(position)
693
694                     if 'colon' in result.groupdict() and result.group('colon') != ':':
695                         colon_start = result.start('colon')
696                         colon_column = column_offset + colon_start
697                         marker = ' '*colon_column + '^'
698                         message.warn("missing ':' at column %s:\n%s\n%s" %
699                                      (colon_column + 1, original_line, marker),
700                                      position)
701
702                     if 'annotations' in result.groupdict():
703                         comment_block.options = self.parse_options(comment_block,
704                                                                    result.group('annotations'))
705
706                     continue
707                 else:
708                     # If we get here, the identifier was not recognized, so
709                     # ignore the rest of the block just like the old annotation
710                     # parser did. Doing this is a bit more strict than
711                     # gtkdoc-mkdb (which continues to search for the identifier
712                     # until either it is found or the end of the block is
713                     # reached). In practice, however, ignoring the block is the
714                     # right thing to do because sooner or later some long
715                     # descriptions will contain something matching an identifier
716                     # pattern by accident.
717                     marker = ' '*column_offset + '^'
718                     message.warn('ignoring unrecognized GTK-Doc comment block, identifier not '
719                                  'found:\n%s\n%s' % (original_line, marker),
720                                  position)
721
722                     return None
723
724             ####################################################################
725             # Check for comment block parameters.
726             ####################################################################
727             result = PARAMETER_RE.search(line)
728             if result:
729                 param_name = result.group('parameter_name')
730                 param_annotations = result.group('annotations')
731                 param_description = result.group('description')
732
733                 if in_part == PART_IDENTIFIER:
734                     in_part = PART_PARAMETERS
735
736                 if in_part != PART_PARAMETERS:
737                     column = result.start('parameter_name') + column_offset
738                     marker = ' '*column + '^'
739                     message.warn("'@%s' parameter unexpected at this location:\n%s\n%s" %
740                                  (param_name, original_line, marker),
741                                  position)
742
743                 # Old style GTK-Doc allowed return values to be specified as
744                 # parameters instead of tags.
745                 if param_name.lower() == TAG_RETURNS:
746                     param_name = TAG_RETURNS
747
748                     if not returns_seen:
749                         returns_seen = True
750                     else:
751                         message.warn("encountered multiple 'Returns' parameters or tags for "
752                                      "'%s'." % (comment_block.name),
753                                      position)
754                 elif param_name in comment_block.params.keys():
755                     column = result.start('parameter_name') + column_offset
756                     marker = ' '*column + '^'
757                     message.warn("multiple '@%s' parameters for identifier '%s':\n%s\n%s" %
758                                  (param_name, comment_block.name, original_line, marker),
759                                  position)
760
761                 tag = DocTag(comment_block, param_name)
762                 tag.set_position(position)
763                 tag.comment = param_description
764                 if param_annotations:
765                     tag.options = self.parse_options(tag, param_annotations)
766                 if param_name == TAG_RETURNS:
767                     comment_block.tags[param_name] = tag
768                 else:
769                     comment_block.params[param_name] = tag
770                 current_param = tag
771                 continue
772
773             ####################################################################
774             # Check for comment block description.
775             #
776             # When we are parsing comment block parameters or the comment block
777             # identifier (when there are no parameters) and encounter an empty
778             # line, we must be parsing the comment block description.
779             ####################################################################
780             if (EMPTY_LINE_RE.search(line)
781             and in_part in [PART_IDENTIFIER, PART_PARAMETERS]):
782                 in_part = PART_DESCRIPTION
783                 continue
784
785             ####################################################################
786             # Check for GTK-Doc comment block tags.
787             ####################################################################
788             result = TAG_RE.search(line)
789             if result:
790                 tag_name = result.group('tag_name')
791                 tag_annotations = result.group('annotations')
792                 tag_description = result.group('description')
793
794                 if in_part == PART_DESCRIPTION:
795                     in_part = PART_TAGS
796
797                 if in_part != PART_TAGS:
798                     column = result.start('tag_name') + column_offset
799                     marker = ' '*column + '^'
800                     message.warn("'%s:' tag unexpected at this location:\n%s\n%s" %
801                                  (tag_name, original_line, marker),
802                                  position)
803
804                 if tag_name.lower() in [TAG_RETURNS, TAG_RETURNVALUE]:
805                     if not returns_seen:
806                         returns_seen = True
807                     else:
808                         message.warn("encountered multiple 'Returns' parameters or tags for "
809                                      "'%s'." % (comment_block.name),
810                                      position)
811
812                     tag = DocTag(comment_block, TAG_RETURNS)
813                     tag.position = position
814                     tag.comment = tag_description
815                     if tag_annotations:
816                         tag.options = self.parse_options(tag, tag_annotations)
817                     comment_block.tags[TAG_RETURNS] = tag
818                     current_tag = tag
819                     continue
820                 else:
821                     if tag_name.lower() in comment_block.tags.keys():
822                         column = result.start('tag_name') + column_offset
823                         marker = ' '*column + '^'
824                         message.warn("multiple '%s:' tags for identifier '%s':\n%s\n%s" %
825                                      (tag_name, comment_block.name, original_line, marker),
826                                      position)
827
828                     tag = DocTag(comment_block, tag_name.lower())
829                     tag.position = position
830                     tag.value = tag_description
831                     if tag_annotations:
832                         if tag_name.lower() == TAG_ATTRIBUTES:
833                             tag.options = self.parse_options(tag, tag_annotations)
834                         else:
835                             message.warn("annotations not supported for tag '%s:'." %
836                                          (tag_name),
837                                          position)
838                     comment_block.tags[tag_name.lower()] = tag
839                     current_tag = tag
840                     continue
841
842             ####################################################################
843             # If we get here, we must be in the middle of a multiline
844             # comment block, parameter or tag description.
845             ####################################################################
846             if in_part in [PART_IDENTIFIER, PART_DESCRIPTION]:
847                 if not comment_block.comment:
848                     # Backwards compatibility with old style GTK-Doc
849                     # comment blocks where Description used to be a comment
850                     # block tag. Simply get rid of 'Description:'.
851                     line = re.sub(DESCRIPTION_TAG_RE, '', line)
852                     comment_block.comment = line
853                 else:
854                     comment_block.comment += '\n' + line
855                 continue
856             elif in_part == PART_PARAMETERS:
857                 self._validate_multiline_annotation_continuation(line, original_line,
858                                                                  column_offset, position)
859
860                 # Append to parameter description.
861                 current_param.comment += ' ' + line.strip()
862             elif in_part == PART_TAGS:
863                 self._validate_multiline_annotation_continuation(line, original_line,
864                                                                  column_offset, position)
865
866                 # Append to tag description.
867                 if current_tag.name.lower() in [TAG_RETURNS, TAG_RETURNVALUE]:
868                     current_tag.comment += ' ' + line.strip()
869                 else:
870                     current_tag.value += ' ' + line.strip()
871
872         ########################################################################
873         # Finished parsing this comment block.
874         ########################################################################
875         # We have picked up a couple of \n characters that where not
876         # intended. Strip those.
877         if comment_block.comment:
878             comment_block.comment = comment_block.comment.strip()
879         else:
880             comment_block.comment = ''
881
882         for tag in comment_block.tags.itervalues():
883             self._clean_comment_block_part(tag)
884
885         for param in comment_block.params.itervalues():
886             self._clean_comment_block_part(param)
887
888         # Validate and store block.
889         comment_block.validate()
890         return comment_block
891
892     def _clean_comment_block_part(self, part):
893         if part.comment:
894             part.comment = part.comment.strip()
895         else:
896             part.comment = None
897
898         if part.value:
899             part.value = part.value.strip()
900         else:
901             part.value = ''
902
903     def _validate_multiline_annotation_continuation(self, line, original_line,
904                                                           column_offset, position):
905         '''
906         Validate parameters and tags (except the first line) and generate
907         warnings about invalid annotations spanning multiple lines.
908
909         :param line: line to validate, stripped from ' * ' at start of the line.
910         :param original_line: original line to validate (used in warning messages)
911         :param column_offset: column width of ' * ' at the time it was stripped from `line`
912         :param position: position of `line` in the source file
913         '''
914
915         result = MULTILINE_ANNOTATION_CONTINUATION_RE.search(line)
916         if result:
917             line = result.group('description')
918             column = result.start('annotations') + column_offset
919             marker = ' '*column + '^'
920             message.warn('ignoring invalid multiline annotation continuation:\n'
921                          '%s\n%s' % (original_line, marker),
922                          position)
923
924     @classmethod
925     def parse_options(cls, tag, value):
926         # (annotation)
927         # (annotation opt1 opt2 ...)
928         # (annotation opt1=value1 opt2=value2 ...)
929         opened = -1
930         options = DocOptions()
931         options.position = tag.position
932
933         for i, c in enumerate(value):
934             if c == '(' and opened == -1:
935                 opened = i+1
936             if c == ')' and opened != -1:
937                 segment = value[opened:i]
938                 parts = segment.split(' ', 1)
939                 if len(parts) == 2:
940                     name, option = parts
941                 elif len(parts) == 1:
942                     name = parts[0]
943                     option = None
944                 else:
945                     raise AssertionError
946                 if option is not None:
947                     option = DocOption(tag, option)
948                 options.add(name, option)
949                 opened = -1
950
951         return options