Imported Upstream version 1.39.3
[platform/upstream/gobject-introspection.git] / giscanner / annotationparser.py
1 # -*- coding: utf-8 -*-
2 # -*- Mode: Python -*-
3
4 # GObject-Introspection - a framework for introspecting GObject libraries
5 # Copyright (C) 2008-2010 Johan Dahlin
6 # Copyright (C) 2012-2013 Dieter Verfaillie <dieterv@optionexplicit.be>
7 #
8 # This program is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU General Public License
10 # as published by the Free Software Foundation; either version 2
11 # of the License, or (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 # 02110-1301, USA.
22 #
23
24
25 '''
26 GTK-Doc comment block format
27 ----------------------------
28
29 A GTK-Doc comment block is built out of multiple parts. Each part can be further
30 divided into fields which are separated by a colon ("``:``") delimiter.
31
32 Known parts and the fields they are constructed from look like the following
33 (optional fields are enclosed in square brackets)::
34
35     ┌───────────────────────────────────────────────────────────┐
36     │ /**                                                       │ ─▷ start token
37     ├────────────────────┬──────────────────────────────────────┤
38     │  * identifier_name │ [: annotations]                      │ ─▷ identifier part
39     ├────────────────────┼─────────────────┬────────────────────┤
40     │  * @parameter_name │ [: annotations] │ : description      │ ─▷ parameter part
41     ├────────────────────┴─────────────────┴────────────────────┤
42     │  *                                                        │ ─▷ comment block description
43     │  * comment_block_description                              │
44     ├─────────────┬─────────────────┬───────────┬───────────────┤
45     │  * tag_name │ [: annotations] │ [: value] │ : description │ ─▷ tag part
46     ├─────────────┴─────────────────┴───────────┴───────────────┤
47     │  */                                                       │ ─▷ end token
48     └───────────────────────────────────────────────────────────┘
49
50 There are two conditions that must be met before a comment block is recognized
51 as a GTK-Doc comment block:
52
53 #. The comment block is opened with a GTK-Doc start token ("``/**``")
54 #. The first line following the start token contains a valid identifier part
55
56 Once a GTK-Doc comment block has been identified as such and has been stripped
57 from its start and end tokens the remaining parts have to be written in a
58 specific order:
59
60 #. There must be exactly 1 `identifier` part on the first line of the
61    comment block which consists of:
62
63    * a required `identifier_name` field
64    * an optional `annotations` field
65
66 #. Zero or more `parameter` parts, each consisting of:
67
68    * a required `parameter_name` field
69    * an optional `annotations` field
70    * a required `description` field (can be the empty string)
71
72 #. One optional `comment block description` part which must begin with at
73    least 1 empty line signaling the start of this part.
74
75 #. Zero or more `tag` parts, each consisting of:
76
77    * a required `tag_name` field
78    * an optional `annotations` field
79    * an optional `value` field
80    * a required `description` field (can be the empty string)
81
82 Additionally, the following restrictions are in effect:
83
84 #. Separating parts with an empty line:
85
86    * `identifier` and `parameter` parts cannot be separated from each other by
87      an empty line as this would signal the start of the
88      `comment block description` part (see above).
89    * it is required to separate the `comment block description` part from the
90      `identifier` or `parameter` parts with an empty line (see above)
91    * `comment block description` and `tag` parts can optionally be separated
92      by an empty line
93
94 #. Parts and fields cannot span multiple lines, except for:
95
96    * the `comment_block_description` part
97    * `parameter description` and `tag description` fields
98
99 #. Taking the above restrictions into account, spanning multiple paragraphs is
100    limited to the `comment block description` part and `tag description` fields.
101
102 Refer to the `GTK-Doc manual`_ for more detailed usage information.
103
104 .. _GTK-Doc manual:
105         http://developer.gnome.org/gtk-doc-manual/1.18/documenting.html.en
106 '''
107
108
109 from __future__ import absolute_import
110
111 import os
112 import re
113
114 from collections import namedtuple
115 from operator import ne, gt, lt
116
117 from .collections import Counter, OrderedDict
118 from .message import Position, warn, error
119
120
121 # GTK-Doc comment block parts
122 PART_IDENTIFIER = 0
123 PART_PARAMETERS = 1
124 PART_DESCRIPTION = 2
125 PART_TAGS = 3
126
127 # GTK-Doc comment block tags
128 #   1) Basic GTK-Doc tags.
129 #      Note: This list cannot be extended unless the GTK-Doc project defines new tags.
130 TAG_DEPRECATED = 'deprecated'
131 TAG_RETURNS = 'returns'
132 TAG_SINCE = 'since'
133 TAG_STABILITY = 'stability'
134
135 GTKDOC_TAGS = [TAG_DEPRECATED,
136                TAG_RETURNS,
137                TAG_SINCE,
138                TAG_STABILITY]
139
140 #   2) Deprecated basic GTK-Doc tags.
141 #      Note: This list cannot be extended unless the GTK-Doc project defines new deprecated tags.
142 TAG_DESCRIPTION = 'description'
143 TAG_RETURN_VALUE = 'return value'
144
145 DEPRECATED_GTKDOC_TAGS = [TAG_DESCRIPTION,
146                           TAG_RETURN_VALUE]
147
148 #   3) Deprecated GObject-Introspection tags.
149 #      Unfortunately, these where accepted by old versions of this module.
150 TAG_RETURN = 'return'
151 TAG_RETURNS_VALUE = 'returns value'
152
153 DEPRECATED_GI_TAGS = [TAG_RETURN,
154                       TAG_RETURNS_VALUE]
155
156 #   4) Deprecated GObject-Introspection annotation tags.
157 #      Accepted by old versions of this module while they should have been
158 #      annotations on the identifier part instead.
159 #      Note: This list can not be extended ever again. The GObject-Introspection project is not
160 #            allowed to invent GTK-Doc tags. Please create new annotations instead.
161 TAG_ATTRIBUTES = 'attributes'
162 TAG_GET_VALUE_FUNC = 'get value func'
163 TAG_REF_FUNC = 'ref func'
164 TAG_RENAME_TO = 'rename to'
165 TAG_SET_VALUE_FUNC = 'set value func'
166 TAG_TRANSFER = 'transfer'
167 TAG_TYPE = 'type'
168 TAG_UNREF_FUNC = 'unref func'
169 TAG_VALUE = 'value'
170 TAG_VFUNC = 'virtual'
171
172 DEPRECATED_GI_ANN_TAGS = [TAG_ATTRIBUTES,
173                           TAG_GET_VALUE_FUNC,
174                           TAG_REF_FUNC,
175                           TAG_RENAME_TO,
176                           TAG_SET_VALUE_FUNC,
177                           TAG_TRANSFER,
178                           TAG_TYPE,
179                           TAG_UNREF_FUNC,
180                           TAG_VALUE,
181                           TAG_VFUNC]
182
183 ALL_TAGS = GTKDOC_TAGS + DEPRECATED_GTKDOC_TAGS + DEPRECATED_GI_TAGS + DEPRECATED_GI_ANN_TAGS
184
185 # GObject-Introspection annotation start/end tokens
186 ANN_LPAR = '('
187 ANN_RPAR = ')'
188
189 # GObject-Introspection annotations
190 #   1) Supported annotations
191 #      Note: when adding new annotations, GTK-Doc project's gtkdoc-mkdb needs to be modified too!
192 ANN_ALLOW_NONE = 'allow-none'
193 ANN_ARRAY = 'array'
194 ANN_ATTRIBUTES = 'attributes'
195 ANN_CLOSURE = 'closure'
196 ANN_CONSTRUCTOR = 'constructor'
197 ANN_DESTROY = 'destroy'
198 ANN_ELEMENT_TYPE = 'element-type'
199 ANN_FOREIGN = 'foreign'
200 ANN_GET_VALUE_FUNC = 'get-value-func'
201 ANN_IN = 'in'
202 ANN_INOUT = 'inout'
203 ANN_METHOD = 'method'
204 ANN_OUT = 'out'
205 ANN_REF_FUNC = 'ref-func'
206 ANN_RENAME_TO = 'rename-to'
207 ANN_SCOPE = 'scope'
208 ANN_SET_VALUE_FUNC = 'set-value-func'
209 ANN_SKIP = 'skip'
210 ANN_TRANSFER = 'transfer'
211 ANN_TYPE = 'type'
212 ANN_UNREF_FUNC = 'unref-func'
213 ANN_VFUNC = 'virtual'
214 ANN_VALUE = 'value'
215
216 GI_ANNS = [ANN_ALLOW_NONE,
217            ANN_ARRAY,
218            ANN_ATTRIBUTES,
219            ANN_CLOSURE,
220            ANN_CONSTRUCTOR,
221            ANN_DESTROY,
222            ANN_ELEMENT_TYPE,
223            ANN_FOREIGN,
224            ANN_GET_VALUE_FUNC,
225            ANN_IN,
226            ANN_INOUT,
227            ANN_METHOD,
228            ANN_OUT,
229            ANN_REF_FUNC,
230            ANN_RENAME_TO,
231            ANN_SCOPE,
232            ANN_SET_VALUE_FUNC,
233            ANN_SKIP,
234            ANN_TRANSFER,
235            ANN_TYPE,
236            ANN_UNREF_FUNC,
237            ANN_VFUNC,
238            ANN_VALUE]
239
240 #   2) Deprecated GObject-Introspection annotations
241 ANN_ATTRIBUTE = 'attribute'
242 ANN_INOUT_ALT = 'in-out'
243
244 DEPRECATED_GI_ANNS = [ANN_ATTRIBUTE,
245                       ANN_INOUT_ALT]
246
247 ALL_ANNOTATIONS = GI_ANNS + DEPRECATED_GI_ANNS
248 DICT_ANNOTATIONS = [ANN_ARRAY, ANN_ATTRIBUTES]
249 LIST_ANNOTATIONS = [ann for ann in ALL_ANNOTATIONS if ann not in DICT_ANNOTATIONS]
250
251 # (array) annotation options
252 OPT_ARRAY_FIXED_SIZE = 'fixed-size'
253 OPT_ARRAY_LENGTH = 'length'
254 OPT_ARRAY_ZERO_TERMINATED = 'zero-terminated'
255
256 ARRAY_OPTIONS = [OPT_ARRAY_FIXED_SIZE,
257                  OPT_ARRAY_LENGTH,
258                  OPT_ARRAY_ZERO_TERMINATED]
259
260 # (out) annotation options
261 OPT_OUT_CALLEE_ALLOCATES = 'callee-allocates'
262 OPT_OUT_CALLER_ALLOCATES = 'caller-allocates'
263
264 OUT_OPTIONS = [OPT_OUT_CALLEE_ALLOCATES,
265                OPT_OUT_CALLER_ALLOCATES]
266
267 # (scope) annotation options
268 OPT_SCOPE_ASYNC = 'async'
269 OPT_SCOPE_CALL = 'call'
270 OPT_SCOPE_NOTIFIED = 'notified'
271
272 SCOPE_OPTIONS = [OPT_SCOPE_ASYNC,
273                  OPT_SCOPE_CALL,
274                  OPT_SCOPE_NOTIFIED]
275
276 # (transfer) annotation options
277 OPT_TRANSFER_CONTAINER = 'container'
278 OPT_TRANSFER_FLOATING = 'floating'
279 OPT_TRANSFER_FULL = 'full'
280 OPT_TRANSFER_NONE = 'none'
281
282 TRANSFER_OPTIONS = [OPT_TRANSFER_CONTAINER,
283                     OPT_TRANSFER_FLOATING,
284                     OPT_TRANSFER_FULL,
285                     OPT_TRANSFER_NONE]
286
287
288 # Pattern used to normalize different types of line endings
289 LINE_BREAK_RE = re.compile(r'\r\n|\r|\n', re.UNICODE)
290
291 # Pattern matching the start token of a comment block.
292 COMMENT_BLOCK_START_RE = re.compile(
293     r'''
294     ^                                                    # start
295     (?P<code>.*?)                                        # whitespace, code, ...
296     \s*                                                  # 0 or more whitespace characters
297     (?P<token>/\*{2}(?![\*/]))                           # 1 forward slash character followed
298                                                          #   by exactly 2 asterisk characters
299                                                          #   and not followed by a slash character
300     \s*                                                  # 0 or more whitespace characters
301     (?P<comment>.*?)                                     # GTK-Doc comment text
302     \s*                                                  # 0 or more whitespace characters
303     $                                                    # end
304     ''',
305     re.UNICODE | re.VERBOSE)
306
307 # Pattern matching the end token of a comment block.
308 COMMENT_BLOCK_END_RE = re.compile(
309     r'''
310     ^                                                    # start
311     \s*                                                  # 0 or more whitespace characters
312     (?P<comment>.*?)                                     # GTK-Doc comment text
313     \s*                                                  # 0 or more whitespace characters
314     (?P<token>\*+/)                                      # 1 or more asterisk characters followed
315                                                          #   by exactly 1 forward slash character
316     (?P<code>.*?)                                        # whitespace, code, ...
317     \s*                                                  # 0 or more whitespace characters
318     $                                                    # end
319     ''',
320     re.UNICODE | re.VERBOSE)
321
322 # Pattern matching the ' * ' at the beginning of every
323 # line inside a comment block.
324 COMMENT_ASTERISK_RE = re.compile(
325     r'''
326     ^                                                    # start
327     \s*                                                  # 0 or more whitespace characters
328     (?P<comment>.*?)                                     # invalid comment text
329     \s*                                                  # 0 or more whitespace characters
330     \*                                                   # 1 asterisk character
331     \s?                                                  # 0 or 1 whitespace characters
332                                                          #   WARNING: removing more than 1
333                                                          #   whitespace character breaks
334                                                          #   embedded example program indentation
335     ''',
336     re.UNICODE | re.VERBOSE)
337
338 # Pattern matching the indentation level of a line (used
339 # to get the indentation before and after the ' * ').
340 INDENTATION_RE = re.compile(
341     r'''
342     ^
343     (?P<indentation>\s*)                                 # 0 or more whitespace characters
344     .*
345     $
346     ''',
347     re.UNICODE | re.VERBOSE)
348
349 # Pattern matching an empty line.
350 EMPTY_LINE_RE = re.compile(
351     r'''
352     ^                                                    # start
353     \s*                                                  # 0 or more whitespace characters
354     $                                                    # end
355     ''',
356     re.UNICODE | re.VERBOSE)
357
358 # Pattern matching SECTION identifiers.
359 SECTION_RE = re.compile(
360     r'''
361     ^                                                    # start
362     \s*                                                  # 0 or more whitespace characters
363     SECTION                                              # SECTION
364     \s*                                                  # 0 or more whitespace characters
365     (?P<delimiter>:?)                                    # delimiter
366     \s*                                                  # 0 or more whitespace characters
367     (?P<section_name>\w\S+?)                             # section name
368     \s*                                                  # 0 or more whitespace characters
369     :?                                                   # invalid delimiter
370     \s*                                                  # 0 or more whitespace characters
371     $
372     ''',
373     re.UNICODE | re.VERBOSE)
374
375 # Pattern matching symbol (function, constant, struct and enum) identifiers.
376 SYMBOL_RE = re.compile(
377     r'''
378     ^                                                    # start
379     \s*                                                  # 0 or more whitespace characters
380     (?P<symbol_name>[\w-]*\w)                            # symbol name
381     \s*                                                  # 0 or more whitespace characters
382     (?P<delimiter>:?)                                    # delimiter
383     \s*                                                  # 0 or more whitespace characters
384     (?P<fields>.*?)                                      # annotations + description
385     \s*                                                  # 0 or more whitespace characters
386     :?                                                   # invalid delimiter
387     \s*                                                  # 0 or more whitespace characters
388     $                                                    # end
389     ''',
390     re.UNICODE | re.VERBOSE)
391
392 # Pattern matching property identifiers.
393 PROPERTY_RE = re.compile(
394     r'''
395     ^                                                    # start
396     \s*                                                  # 0 or more whitespace characters
397     (?P<class_name>[\w]+)                                # class name
398     \s*                                                  # 0 or more whitespace characters
399     :{1}                                                 # 1 required colon
400     \s*                                                  # 0 or more whitespace characters
401     (?P<property_name>[\w-]*\w)                          # property name
402     \s*                                                  # 0 or more whitespace characters
403     (?P<delimiter>:?)                                    # delimiter
404     \s*                                                  # 0 or more whitespace characters
405     (?P<fields>.*?)                                      # annotations + description
406     \s*                                                  # 0 or more whitespace characters
407     :?                                                   # invalid delimiter
408     \s*                                                  # 0 or more whitespace characters
409     $                                                    # end
410     ''',
411     re.UNICODE | re.VERBOSE)
412
413 # Pattern matching signal identifiers.
414 SIGNAL_RE = re.compile(
415     r'''
416     ^                                                    # start
417     \s*                                                  # 0 or more whitespace characters
418     (?P<class_name>[\w]+)                                # class name
419     \s*                                                  # 0 or more whitespace characters
420     :{2}                                                 # 2 required colons
421     \s*                                                  # 0 or more whitespace characters
422     (?P<signal_name>[\w-]*\w)                            # signal name
423     \s*                                                  # 0 or more whitespace characters
424     (?P<delimiter>:?)                                    # delimiter
425     \s*                                                  # 0 or more whitespace characters
426     (?P<fields>.*?)                                      # annotations + description
427     \s*                                                  # 0 or more whitespace characters
428     :?                                                   # invalid delimiter
429     \s*                                                  # 0 or more whitespace characters
430     $                                                    # end
431     ''',
432     re.UNICODE | re.VERBOSE)
433
434 # Pattern matching parameters.
435 PARAMETER_RE = re.compile(
436     r'''
437     ^                                                    # start
438     \s*                                                  # 0 or more whitespace characters
439     @                                                    # @ character
440     (?P<parameter_name>[\w-]*\w|.*?\.\.\.)               # parameter name
441     \s*                                                  # 0 or more whitespace characters
442     :{1}                                                 # 1 required delimiter
443     \s*                                                  # 0 or more whitespace characters
444     (?P<fields>.*?)                                      # annotations + description
445     \s*                                                  # 0 or more whitespace characters
446     $                                                    # end
447     ''',
448     re.UNICODE | re.VERBOSE)
449
450 # Pattern matching tags.
451 _all_tags = '|'.join(ALL_TAGS).replace(' ', r'\s')
452 TAG_RE = re.compile(
453     r'''
454     ^                                                    # start
455     \s*                                                  # 0 or more whitespace characters
456     (?P<tag_name>''' + _all_tags + r''')                 # tag name
457     \s*                                                  # 0 or more whitespace characters
458     :{1}                                                 # 1 required delimiter
459     \s*                                                  # 0 or more whitespace characters
460     (?P<fields>.*?)                                      # annotations + value + description
461     \s*                                                  # 0 or more whitespace characters
462     $                                                    # end
463     ''',
464     re.UNICODE | re.VERBOSE | re.IGNORECASE)
465
466 # Pattern matching value and description fields for TAG_DEPRECATED & TAG_SINCE tags.
467 TAG_VALUE_VERSION_RE = re.compile(
468     r'''
469     ^                                                    # start
470     \s*                                                  # 0 or more whitespace characters
471     (?P<value>([0-9\.])*)                                # value
472     \s*                                                  # 0 or more whitespace characters
473     (?P<delimiter>:?)                                    # delimiter
474     \s*                                                  # 0 or more whitespace characters
475     (?P<description>.*?)                                 # description
476     \s*                                                  # 0 or more whitespace characters
477     $                                                    # end
478     ''',
479     re.UNICODE | re.VERBOSE)
480
481 # Pattern matching value and description fields for TAG_STABILITY tags.
482 TAG_VALUE_STABILITY_RE = re.compile(
483     r'''
484     ^                                                    # start
485     \s*                                                  # 0 or more whitespace characters
486     (?P<value>(stable|unstable|private|internal)?)       # value
487     \s*                                                  # 0 or more whitespace characters
488     (?P<delimiter>:?)                                    # delimiter
489     \s*                                                  # 0 or more whitespace characters
490     (?P<description>.*?)                                 # description
491     \s*                                                  # 0 or more whitespace characters
492     $                                                    # end
493     ''',
494     re.UNICODE | re.VERBOSE | re.IGNORECASE)
495
496
497 class GtkDocAnnotations(OrderedDict):
498     '''
499     An ordered dictionary mapping annotation names to annotation options (if any). Annotation
500     options can be either a :class:`list`, a :class:`giscanner.collections.OrderedDict`
501     (depending on the annotation name)or :const:`None`.
502     '''
503
504     __slots__ = ('position')
505
506     def __init__(self, position=None):
507         OrderedDict.__init__(self)
508
509         #: A :class:`giscanner.message.Position` instance specifying the location of the
510         #: annotations in the source file or :const:`None`.
511         self.position = position
512
513
514 class GtkDocAnnotatable(object):
515     '''
516     Base class for GTK-Doc comment block parts that can be annotated.
517     '''
518
519     __slots__ = ('position', 'annotations')
520
521     #: A :class:`tuple` of annotation name constants that are valid for this object. Annotation
522     #: names not in this :class:`tuple` will be reported as *unknown* by :func:`validate`. The
523     #: :attr:`valid_annotations` class attribute should be overridden by subclasses.
524     valid_annotations = ()
525
526     def __init__(self, position=None):
527         #: A :class:`giscanner.message.Position` instance specifying the location of the
528         #: annotatable comment block part in the source file or :const:`None`.
529         self.position = position
530
531         #: A :class:`GtkDocAnnotations` instance representing the annotations
532         #: applied to this :class:`GtkDocAnnotatable` instance.
533         self.annotations = GtkDocAnnotations()
534
535     def __repr__(self):
536         return '<GtkDocAnnotatable %r %r>' % (self.annotations, )
537
538     def validate(self):
539         '''
540         Validate annotations stored by the :class:`GtkDocAnnotatable` instance, if any.
541         '''
542
543         if self.annotations:
544             position = self.annotations.position
545
546             for ann_name, options in self.annotations.items():
547                 if ann_name in self.valid_annotations:
548                     validate = getattr(self, '_do_validate_' + ann_name.replace('-', '_'))
549                     validate(position, ann_name, options)
550                 elif ann_name in ALL_ANNOTATIONS:
551                     # Not error() as ann_name might be valid in some newer
552                     # GObject-Instrospection version.
553                     warn('unexpected annotation: %s' % (ann_name, ), position)
554                 else:
555                     # Not error() as ann_name might be valid in some newer
556                     # GObject-Instrospection version.
557                     warn('unknown annotation: %s' % (ann_name, ), position)
558
559     def _validate_options(self, position, ann_name, n_options, expected_n_options, operator,
560                           message):
561         '''
562         Validate the number of options held by an annotation according to the test
563         ``operator(n_options, expected_n_options)``.
564
565         :param position: :class:`giscanner.message.Position` of the line in the source file
566                          containing the annotation to be validated
567         :param ann_name: name of the annotation holding the options to validate
568         :param n_options: number of options held by the annotation
569         :param expected_n_options: number of expected options
570         :param operator: an operator function from python's :mod:`operator` module, for example
571                          :func:`operator.ne` or :func:`operator.lt`
572         :param message: warning message used when the test
573                         ``operator(n_options, expected_n_options)`` fails.
574         '''
575
576         if n_options == 0:
577             t = 'none'
578         else:
579             t = '%d' % (n_options, )
580
581         if expected_n_options == 0:
582             s = 'no options'
583         elif expected_n_options == 1:
584             s = 'one option'
585         else:
586             s = '%d options' % (expected_n_options, )
587
588         if operator(n_options, expected_n_options):
589             warn('"%s" annotation %s %s, %s given' % (ann_name, message, s, t), position)
590
591     def _validate_annotation(self, position, ann_name, options, choices=None,
592                              exact_n_options=None, min_n_options=None, max_n_options=None):
593         '''
594         Validate an annotation.
595
596         :param position: :class:`giscanner.message.Position` of the line in the source file
597                          containing the annotation to be validated
598         :param ann_name: name of the annotation holding the options to validate
599         :param options: annotation options to be validated
600         :param choices: an iterable of allowed option names or :const:`None` to skip this test
601         :param exact_n_options: exact number of expected options or :const:`None` to skip this test
602         :param min_n_options: minimum number of expected options or :const:`None` to skip this test
603         :param max_n_options: maximum number of expected options or :const:`None` to skip this test
604         '''
605
606         n_options = len(options)
607
608         if exact_n_options is not None:
609             self._validate_options(position,
610                                    ann_name, n_options, exact_n_options, ne, 'needs')
611
612         if min_n_options is not None:
613             self._validate_options(position,
614                                    ann_name, n_options, min_n_options, lt, 'takes at least')
615
616         if max_n_options is not None:
617             self._validate_options(position,
618                                    ann_name, n_options, max_n_options, gt, 'takes at most')
619
620         if options and choices is not None:
621             option = options[0]
622             if option not in choices:
623                 warn('invalid "%s" annotation option: "%s"' % (ann_name, option), position)
624
625     def _do_validate_allow_none(self, position, ann_name, options):
626         '''
627         Validate the ``(allow-none)`` annotation.
628
629         :param position: :class:`giscanner.message.Position` of the line in the source file
630                          containing the annotation to be validated
631         :param ann_name: name of the annotation holding the options to validate
632         :param options: annotation options held by the annotation
633         '''
634
635         self._validate_annotation(position, ann_name, options, exact_n_options=0)
636
637     def _do_validate_array(self, position, ann_name, options):
638         '''
639         Validate the ``(array)`` annotation.
640
641         :param position: :class:`giscanner.message.Position` of the line in the source file
642                          containing the annotation to be validated
643         :param ann_name: name of the annotation holding the options to validate
644         :param options: annotation options held by the annotation
645         '''
646
647         if len(options) == 0:
648             return
649
650         for option, value in options.items():
651             if option in [OPT_ARRAY_ZERO_TERMINATED, OPT_ARRAY_FIXED_SIZE]:
652                 try:
653                     int(value)
654                 except (TypeError, ValueError):
655                     if value is None:
656                         warn('"%s" annotation option "%s" needs a value' % (ann_name, option),
657                              position)
658                     else:
659                         warn('invalid "%s" annotation option "%s" value "%s", must be an integer' %
660                              (ann_name, option, value),
661                              position)
662             elif option == OPT_ARRAY_LENGTH:
663                 if value is None:
664                     warn('"%s" annotation option "length" needs a value' % (ann_name, ),
665                          position)
666             else:
667                 warn('invalid "%s" annotation option: "%s"' % (ann_name, option),
668                      position)
669
670     def _do_validate_attributes(self, position, ann_name, options):
671         '''
672         Validate the ``(attributes)`` annotation.
673
674         :param position: :class:`giscanner.message.Position` of the line in the source file
675                          containing the annotation to be validated
676         :param ann_name: name of the annotation holding the options to validate
677         :param options: annotation options to validate
678         '''
679
680         # The 'attributes' annotation allows free form annotations.
681         pass
682
683     def _do_validate_closure(self, position, ann_name, options):
684         '''
685         Validate the ``(closure)`` annotation.
686
687         :param position: :class:`giscanner.message.Position` of the line in the source file
688                          containing the annotation to be validated
689         :param ann_name: name of the annotation holding the options to validate
690         :param options: annotation options to validate
691         '''
692
693         self._validate_annotation(position, ann_name, options, max_n_options=1)
694
695     def _do_validate_constructor(self, position, ann_name, options):
696         '''
697         Validate the ``(constructor)`` annotation.
698
699         :param position: :class:`giscanner.message.Position` of the line in the source file
700                          containing the annotation to be validated
701         :param ann_name: name of the annotation holding the options to validate
702         :param options: annotation options to validate
703         '''
704
705         self._validate_annotation(position, ann_name, options, exact_n_options=0)
706
707     def _do_validate_destroy(self, position, ann_name, options):
708         '''
709         Validate the ``(destroy)`` annotation.
710
711         :param position: :class:`giscanner.message.Position` of the line in the source file
712                          containing the annotation to be validated
713         :param ann_name: name of the annotation holding the options to validate
714         :param options: annotation options to validate
715         '''
716
717         self._validate_annotation(position, ann_name, options, exact_n_options=1)
718
719     def _do_validate_element_type(self, position, ann_name, options):
720         '''
721         Validate the ``(element)`` annotation.
722
723         :param position: :class:`giscanner.message.Position` of the line in the source file
724                          containing the annotation to be validated
725         :param ann_name: name of the annotation holding the options to validate
726         :param options: annotation options to validate
727         '''
728
729         self._validate_annotation(position, ann_name, options, min_n_options=1, max_n_options=2)
730
731     def _do_validate_foreign(self, position, ann_name, options):
732         '''
733         Validate the ``(foreign)`` annotation.
734
735         :param position: :class:`giscanner.message.Position` of the line in the source file
736                          containing the annotation to be validated
737         :param ann_name: name of the annotation holding the options to validate
738         :param options: annotation options to validate
739         '''
740
741         self._validate_annotation(position, ann_name, options, exact_n_options=0)
742
743     def _do_validate_get_value_func(self, position, ann_name, options):
744         '''
745         Validate the ``(value-func)`` annotation.
746
747         :param position: :class:`giscanner.message.Position` of the line in the source file
748                          containing the annotation to be validated
749         :param ann_name: name of the annotation holding the options to validate
750         :param options: annotation options to validate
751         '''
752
753         self._validate_annotation(position, ann_name, options, exact_n_options=1)
754
755     def _do_validate_in(self, position, ann_name, options):
756         '''
757         Validate the ``(in)`` annotation.
758
759         :param position: :class:`giscanner.message.Position` of the line in the source file
760                          containing the annotation to be validated
761         :param ann_name: name of the annotation holding the options to validate
762         :param options: annotation options to validate
763         '''
764
765         self._validate_annotation(position, ann_name, options, exact_n_options=0)
766
767     def _do_validate_inout(self, position, ann_name, options):
768         '''
769         Validate the ``(in-out)`` annotation.
770
771         :param position: :class:`giscanner.message.Position` of the line in the source file
772                          containing the annotation to be validated
773         :param ann_name: name of the annotation holding the options to validate
774         :param options: annotation options to validate
775         '''
776
777         self._validate_annotation(position, ann_name, options, exact_n_options=0)
778
779     def _do_validate_method(self, position, ann_name, options):
780         '''
781         Validate the ``(method)`` annotation.
782
783         :param position: :class:`giscanner.message.Position` of the line in the source file
784                          containing the annotation to be validated
785         :param ann_name: name of the annotation holding the options to validate
786         :param options: annotation options to validate
787         '''
788
789         self._validate_annotation(position, ann_name, options, exact_n_options=0)
790
791     def _do_validate_out(self, position, ann_name, options):
792         '''
793         Validate the ``(out)`` annotation.
794
795         :param position: :class:`giscanner.message.Position` of the line in the source file
796                          containing the annotation to be validated
797         :param ann_name: name of the annotation holding the options to validate
798         :param options: annotation options to validate
799         '''
800
801         self._validate_annotation(position, ann_name, options, max_n_options=1,
802                                   choices=OUT_OPTIONS)
803
804     def _do_validate_ref_func(self, position, ann_name, options):
805         '''
806         Validate the ``(ref-func)`` annotation.
807
808         :param position: :class:`giscanner.message.Position` of the line in the source file
809                          containing the annotation to be validated
810         :param ann_name: name of the annotation holding the options to validate
811         :param options: annotation options to validate
812         '''
813
814         self._validate_annotation(position, ann_name, options, exact_n_options=1)
815
816     def _do_validate_rename_to(self, position, ann_name, options):
817         '''
818         Validate the ``(rename-to)`` annotation.
819
820         :param position: :class:`giscanner.message.Position` of the line in the source file
821                          containing the annotation to be validated
822         :param ann_name: name of the annotation holding the options to validate
823         :param options: annotation options to validate
824         '''
825
826         self._validate_annotation(position, ann_name, options, exact_n_options=1)
827
828     def _do_validate_scope(self, position, ann_name, options):
829         '''
830         Validate the ``(scope)`` annotation.
831
832         :param position: :class:`giscanner.message.Position` of the line in the source file
833                          containing the annotation to be validated
834         :param ann_name: name of the annotation holding the options to validate
835         :param options: annotation options to validate
836         '''
837
838         self._validate_annotation(position, ann_name, options, exact_n_options=1,
839                                   choices=SCOPE_OPTIONS)
840
841     def _do_validate_set_value_func(self, position, ann_name, options):
842         '''
843         Validate the ``(value-func)`` annotation.
844
845         :param position: :class:`giscanner.message.Position` of the line in the source file
846                          containing the annotation to be validated
847         :param ann_name: name of the annotation holding the options to validate
848         :param options: annotation options to validate
849         '''
850
851         self._validate_annotation(position, ann_name, options, exact_n_options=1)
852
853     def _do_validate_skip(self, position, ann_name, options):
854         '''
855         Validate the ``(skip)`` annotation.
856
857         :param position: :class:`giscanner.message.Position` of the line in the source file
858                          containing the annotation to be validated
859         :param ann_name: name of the annotation holding the options to validate
860         :param options: annotation options to validate
861         '''
862
863         self._validate_annotation(position, ann_name, options, exact_n_options=0)
864
865     def _do_validate_transfer(self, position, ann_name, options):
866         '''
867         Validate the ``(transfer)`` annotation.
868
869         :param position: :class:`giscanner.message.Position` of the line in the source file
870                          containing the annotation to be validated
871         :param ann_name: name of the annotation holding the options to validate
872         :param options: annotation options to validate
873         '''
874
875         self._validate_annotation(position, ann_name, options, exact_n_options=1,
876                                   choices=TRANSFER_OPTIONS)
877
878     def _do_validate_type(self, position, ann_name, options):
879         '''
880         Validate the ``(type)`` annotation.
881
882         :param position: :class:`giscanner.message.Position` of the line in the source file
883                          containing the annotation to be validated
884         :param ann_name: name of the annotation holding the options to validate
885         :param options: annotation options to validate
886         '''
887
888         self._validate_annotation(position, ann_name, options, exact_n_options=1)
889
890     def _do_validate_unref_func(self, position, ann_name, options):
891         '''
892         Validate the ``(unref-func)`` annotation.
893
894         :param position: :class:`giscanner.message.Position` of the line in the source file
895                          containing the annotation to be validated
896         :param ann_name: name of the annotation holding the options to validate
897         :param options: annotation options to validate
898         '''
899
900         self._validate_annotation(position, ann_name, options, exact_n_options=1)
901
902     def _do_validate_value(self, position, ann_name, options):
903         '''
904         Validate the ``(value)`` annotation.
905
906         :param position: :class:`giscanner.message.Position` of the line in the source file
907                          containing the annotation to be validated
908         :param ann_name: name of the annotation holding the options to validate
909         :param options: annotation options to validate
910         '''
911
912         self._validate_annotation(position, ann_name, options, exact_n_options=1)
913
914     def _do_validate_virtual(self, position, ann_name, options):
915         '''
916         Validate the ``(virtual)`` annotation.
917
918         :param position: :class:`giscanner.message.Position` of the line in the source file
919                          containing the annotation to be validated
920         :param ann_name: name of the annotation holding the options to validate
921         :param options: annotation options to validate
922         '''
923
924         self._validate_annotation(position, ann_name, options, exact_n_options=1)
925
926
927 class GtkDocParameter(GtkDocAnnotatable):
928     '''
929     Represents a GTK-Doc parameter part.
930     '''
931
932     __slots__ = ('name', 'description')
933
934     valid_annotations = (ANN_ALLOW_NONE, ANN_ARRAY, ANN_ATTRIBUTES, ANN_CLOSURE, ANN_DESTROY,
935                          ANN_ELEMENT_TYPE, ANN_IN, ANN_INOUT, ANN_OUT, ANN_SCOPE, ANN_SKIP,
936                          ANN_TRANSFER, ANN_TYPE)
937
938     def __init__(self, name, position=None):
939         GtkDocAnnotatable.__init__(self, position)
940
941         #: Parameter name.
942         self.name = name
943
944         #: Parameter description or :const:`None`.
945         self.description = None
946
947     def __repr__(self):
948         return '<GtkDocParameter %r %r>' % (self.name, self.annotations)
949
950
951 class GtkDocTag(GtkDocAnnotatable):
952     '''
953     Represents a GTK-Doc tag part.
954     '''
955
956     __slots__ = ('name', 'value', 'description')
957
958     valid_annotations = (ANN_ALLOW_NONE, ANN_ARRAY, ANN_ATTRIBUTES, ANN_ELEMENT_TYPE, ANN_SKIP,
959                          ANN_TRANSFER, ANN_TYPE)
960
961     def __init__(self, name, position=None):
962         GtkDocAnnotatable.__init__(self, position)
963
964         #: Tag name.
965         self.name = name
966
967         #: Tag value or :const:`None`.
968         self.value = None
969
970         #: Tag description or :const:`None`.
971         self.description = None
972
973     def __repr__(self):
974         return '<GtkDocTag %r %r>' % (self.name, self.annotations)
975
976
977 class GtkDocCommentBlock(GtkDocAnnotatable):
978     '''
979     Represents a GTK-Doc comment block.
980     '''
981
982     __slots__ = ('code_before', 'code_after', 'indentation',
983                  'name', 'params', 'description', 'tags')
984
985     #: Valid annotation names for the GTK-Doc comment block identifier part.
986     valid_annotations = (ANN_ATTRIBUTES, ANN_CONSTRUCTOR, ANN_FOREIGN, ANN_GET_VALUE_FUNC,
987                          ANN_METHOD, ANN_REF_FUNC, ANN_RENAME_TO, ANN_SET_VALUE_FUNC,
988                          ANN_SKIP, ANN_TRANSFER, ANN_TYPE, ANN_UNREF_FUNC, ANN_VALUE, ANN_VFUNC)
989
990     def __init__(self, name, position=None):
991         GtkDocAnnotatable.__init__(self, position)
992
993         #: Code preceding the GTK-Doc comment block start token ("``/**``"), if any.
994         self.code_before = None
995
996         #: Code following the GTK-Doc comment block end token ("``*/``"), if any.
997         self.code_after = None
998
999         #: List of indentation levels (preceding the "``*``") for all lines in the comment
1000         #: block's source text.
1001         self.indentation = []
1002
1003         #: Identifier name.
1004         self.name = name
1005
1006         #: Ordered dictionary mapping parameter names to :class:`GtkDocParameter` instances
1007         #: applied to this :class:`GtkDocCommentBlock`.
1008         self.params = OrderedDict()
1009
1010         #: The GTK-Doc comment block description part.
1011         self.description = None
1012
1013         #: Ordered dictionary mapping tag names to :class:`GtkDocTag` instances
1014         #: applied to this :class:`GtkDocCommentBlock`.
1015         self.tags = OrderedDict()
1016
1017     def __cmp__(self, other):
1018         # Note: This is used by g-ir-annotation-tool, which does a ``sorted(blocks.values())``,
1019         #       meaning that keeping this around makes update-glib-annotations.py patches
1020         #       easier to review.
1021         return cmp(self.name, other.name)
1022
1023     def __repr__(self):
1024         return '<GtkDocCommentBlock %r %r>' % (self.name, self.annotations)
1025
1026     def validate(self):
1027         '''
1028         Validate annotations applied to the :class:`GtkDocCommentBlock` identifier, parameters
1029         and tags.
1030         '''
1031         GtkDocAnnotatable.validate(self)
1032
1033         for param in self.params.values():
1034             param.validate()
1035
1036         for tag in self.tags.values():
1037             tag.validate()
1038
1039
1040 #: Result object returned by :class:`GtkDocCommentBlockParser`._parse_annotations()
1041 _ParseAnnotationsResult = namedtuple('Result', ['success', 'annotations', 'start_pos', 'end_pos'])
1042
1043 #: Result object returned by :class:`GtkDocCommentBlockParser`._parse_fields()
1044 _ParseFieldsResult = namedtuple('Result', ['success', 'annotations', 'description'])
1045
1046
1047 class GtkDocCommentBlockParser(object):
1048     '''
1049     Parse GTK-Doc comment blocks into a parse tree built out of :class:`GtkDocCommentBlock`,
1050     :class:`GtkDocParameter`, :class:`GtkDocTag` and :class:`GtkDocAnnotations`
1051     objects. This parser tries to accept malformed input whenever possible and does
1052     not cause the process to exit on syntax errors. It does however emit:
1053
1054         * warning messages at the slightest indication of recoverable malformed input and
1055         * error messages for unrecoverable malformed input
1056
1057     whenever possible. Recoverable, in this context, means that we can serialize the
1058     :class:`GtkDocCommentBlock` instance using a :class:`GtkDocCommentBlockWriter` without
1059     information being lost. It is usually a good idea to heed these warning and error messages
1060     as malformed input can result in both:
1061
1062         * invalid GTK-Doc output (HTML, pdf, ...) when the comment blocks are parsed
1063           with GTK-Doc's gtkdoc-mkdb
1064         * unexpected introspection behavior, for example missing parameters in the
1065           generated .gir and .typelib files
1066
1067     .. NOTE:: :class:`GtkDocCommentBlockParser` functionality is heavily based on gtkdoc-mkdb's
1068         `ScanSourceFile()`_ function and is currently in sync with GTK-Doc
1069         commit `47abcd5`_.
1070
1071     .. _ScanSourceFile():
1072            http://git.gnome.org/browse/gtk-doc/tree/gtkdoc-mkdb.in#n3722
1073     .. _47abcd5:
1074            https://git.gnome.org/browse/gtk-doc/commit/?id=47abcd53b8489ebceec9e394676512a181c1f1f6
1075     '''
1076
1077     def parse_comment_blocks(self, comments):
1078         '''
1079         Parse multiple GTK-Doc comment blocks.
1080
1081         :param comments: an iterable of ``(comment, filename, lineno)`` tuples
1082         :returns: a dictionary mapping identifier names to :class:`GtkDocCommentBlock` objects
1083         '''
1084
1085         comment_blocks = {}
1086
1087         for (comment, filename, lineno) in comments:
1088             try:
1089                 comment_block = self.parse_comment_block(comment, filename, lineno)
1090             except Exception:
1091                 error('unrecoverable parse error, please file a GObject-Introspection bug'
1092                       'report including the complete comment block at the indicated location.',
1093                       Position(filename, lineno))
1094                 continue
1095
1096             if comment_block is not None:
1097                 # Note: previous versions of this parser did not check if an identifier was
1098                 #       already stored in comment_blocks, so when different comment blocks where
1099                 #       encountered documenting the same identifier the last comment block seen
1100                 #       "wins". Keep this behavior for backwards compatibility, but emit a warning.
1101                 if comment_block.name in comment_blocks:
1102                     firstseen = comment_blocks[comment_block.name]
1103                     path = os.path.dirname(firstseen.position.filename)
1104                     warn('multiple comment blocks documenting \'%s:\' identifier '
1105                          '(already seen at %s).' %
1106                          (comment_block.name, firstseen.position.format(path)),
1107                          comment_block.position)
1108
1109                 comment_blocks[comment_block.name] = comment_block
1110
1111         return comment_blocks
1112
1113     def parse_comment_block(self, comment, filename, lineno):
1114         '''
1115         Parse a single GTK-Doc comment block.
1116
1117         :param comment: string representing the GTK-Doc comment block including it's
1118                         start ("``/**``") and end ("``*/``") tokens.
1119         :param filename: source file name where the comment block originated from
1120         :param lineno: line number in the source file where the comment block starts
1121         :returns: a :class:`GtkDocCommentBlock` object or ``None``
1122         '''
1123
1124         code_before = ''
1125         code_after = ''
1126         comment_block_pos = Position(filename, lineno)
1127         comment_lines = re.sub(LINE_BREAK_RE, '\n', comment).split('\n')
1128         comment_lines_len = len(comment_lines)
1129
1130         # Check for the start of the comment block.
1131         result = COMMENT_BLOCK_START_RE.match(comment_lines[0])
1132         if result:
1133             # Skip single line comment blocks
1134             if comment_lines_len == 1:
1135                 position = Position(filename, lineno)
1136                 marker = ' ' * result.end('code') + '^'
1137                 error('Skipping invalid GTK-Doc comment block:'
1138                       '\n%s\n%s' % (comment_lines[0], marker),
1139                      position)
1140                 return None
1141
1142             code_before = result.group('code')
1143             comment = result.group('comment')
1144
1145             if code_before:
1146                 position = Position(filename, lineno)
1147                 marker = ' ' * result.end('code') + '^'
1148                 warn('GTK-Doc comment block start token "/**" should '
1149                      'not be preceded by code:\n%s\n%s' % (comment_lines[0], marker),
1150                      position)
1151
1152             if comment:
1153                 position = Position(filename, lineno)
1154                 marker = ' ' * result.start('comment') + '^'
1155                 warn('GTK-Doc comment block start token "/**" should '
1156                      'not be followed by comment text:\n%s\n%s' % (comment_lines[0], marker),
1157                      position)
1158
1159                 comment_lines[0] = comment
1160             else:
1161                 del comment_lines[0]
1162         else:
1163             # Not a GTK-Doc comment block.
1164             return None
1165
1166         # Check for the end of the comment block.
1167         result = COMMENT_BLOCK_END_RE.match(comment_lines[-1])
1168         if result:
1169             code_after = result.group('code')
1170             comment = result.group('comment')
1171             if code_after:
1172                 position = Position(filename, lineno + comment_lines_len - 1)
1173                 marker = ' ' * result.end('code') + '^'
1174                 warn('GTK-Doc comment block end token "*/" should '
1175                      'not be followed by code:\n%s\n%s' % (comment_lines[-1], marker),
1176                      position)
1177
1178             if comment:
1179                 position = Position(filename, lineno + comment_lines_len - 1)
1180                 marker = ' ' * result.end('comment') + '^'
1181                 warn('GTK-Doc comment block end token "*/" should '
1182                      'not be preceded by comment text:\n%s\n%s' % (comment_lines[-1], marker),
1183                      position)
1184
1185                 comment_lines[-1] = comment
1186             else:
1187                 del comment_lines[-1]
1188         else:
1189             # Not a GTK-Doc comment block.
1190             return None
1191
1192         # If we get this far, we must be inside something
1193         # that looks like a GTK-Doc comment block.
1194         comment_block = None
1195         identifier_warned = False
1196         block_indent = []
1197         line_indent = None
1198         part_indent = None
1199         in_part = None
1200         current_part = None
1201         returns_seen = False
1202
1203         for line in comment_lines:
1204             lineno += 1
1205             position = Position(filename, lineno)
1206
1207             # Store the original line (without \n) and column offset
1208             # so we can generate meaningful warnings later on.
1209             original_line = line
1210             column_offset = 0
1211
1212             # Store indentation level of the comment (before the ' * ')
1213             result = INDENTATION_RE.match(line)
1214             block_indent.append(result.group('indentation'))
1215
1216             # Get rid of the ' * ' at the start of the line.
1217             result = COMMENT_ASTERISK_RE.match(line)
1218             if result:
1219                 comment = result.group('comment')
1220                 if comment:
1221                     marker = ' ' * result.start('comment') + '^'
1222                     error('invalid comment text:\n%s\n%s' %
1223                           (original_line, marker),
1224                           position)
1225
1226                 column_offset = result.end(0)
1227                 line = line[result.end(0):]
1228
1229             # Store indentation level of the line (after the ' * ').
1230             result = INDENTATION_RE.match(line)
1231             line_indent = len(result.group('indentation').replace('\t', '  '))
1232
1233             ####################################################################
1234             # Check for GTK-Doc comment block identifier.
1235             ####################################################################
1236             if comment_block is None:
1237                 result = SECTION_RE.match(line)
1238
1239                 if result:
1240                     identifier_name = 'SECTION:%s' % (result.group('section_name'), )
1241                     identifier_delimiter = None
1242                     identifier_fields = None
1243                     identifier_fields_start = None
1244                 else:
1245                     result = PROPERTY_RE.match(line)
1246
1247                     if result:
1248                         identifier_name = '%s:%s' % (result.group('class_name'),
1249                                                      result.group('property_name'))
1250                         identifier_delimiter = result.group('delimiter')
1251                         identifier_fields = result.group('fields')
1252                         identifier_fields_start = result.start('fields')
1253                     else:
1254                         result = SIGNAL_RE.match(line)
1255
1256                         if result:
1257                             identifier_name = '%s::%s' % (result.group('class_name'),
1258                                                           result.group('signal_name'))
1259                             identifier_delimiter = result.group('delimiter')
1260                             identifier_fields = result.group('fields')
1261                             identifier_fields_start = result.start('fields')
1262                         else:
1263                             result = SYMBOL_RE.match(line)
1264
1265                             if result:
1266                                 identifier_name = '%s' % (result.group('symbol_name'), )
1267                                 identifier_delimiter = result.group('delimiter')
1268                                 identifier_fields = result.group('fields')
1269                                 identifier_fields_start = result.start('fields')
1270
1271                 if result:
1272                     in_part = PART_IDENTIFIER
1273                     part_indent = line_indent
1274
1275                     comment_block = GtkDocCommentBlock(identifier_name, comment_block_pos)
1276                     comment_block.code_before = code_before
1277                     comment_block.code_after = code_after
1278
1279                     if identifier_fields:
1280                         res = self._parse_annotations(position,
1281                                                       column_offset + identifier_fields_start,
1282                                                       original_line,
1283                                                       identifier_fields)
1284
1285                         if res.success:
1286                             if identifier_fields[res.end_pos:].strip():
1287                                 # Not an identifier due to invalid trailing description field
1288                                 result = None
1289                                 in_part = None
1290                                 part_indent = None
1291                                 comment_block = None
1292                             else:
1293                                 comment_block.annotations = res.annotations
1294
1295                                 if not identifier_delimiter and res.annotations:
1296                                     marker_position = column_offset + result.start('delimiter')
1297                                     marker = ' ' * marker_position + '^'
1298                                     warn('missing ":" at column %s:\n%s\n%s' %
1299                                          (marker_position + 1, original_line, marker),
1300                                          position)
1301
1302                 if not result:
1303                     # Emit a single warning when the identifier is not found on the first line
1304                     if not identifier_warned:
1305                         identifier_warned = True
1306                         marker = ' ' * column_offset + '^'
1307                         error('identifier not found on the first line:\n%s\n%s' %
1308                               (original_line, marker),
1309                               position)
1310                 continue
1311
1312             ####################################################################
1313             # Check for comment block parameters.
1314             ####################################################################
1315             result = PARAMETER_RE.match(line)
1316             if result:
1317                 part_indent = line_indent
1318                 param_name = result.group('parameter_name')
1319                 param_name_lower = param_name.lower()
1320                 param_fields = result.group('fields')
1321                 param_fields_start = result.start('fields')
1322                 marker = ' ' * (result.start('parameter_name') + column_offset) + '^'
1323
1324                 if in_part not in [PART_IDENTIFIER, PART_PARAMETERS]:
1325                     warn('"@%s" parameter unexpected at this location:\n%s\n%s' %
1326                          (param_name, original_line, marker),
1327                          position)
1328
1329                 in_part = PART_PARAMETERS
1330
1331                 if param_name_lower == TAG_RETURNS:
1332                     # Deprecated return value as parameter instead of tag
1333                     param_name = TAG_RETURNS
1334
1335                     if not returns_seen:
1336                         returns_seen = True
1337                     else:
1338                         error('encountered multiple "Returns" parameters or tags for "%s".' %
1339                               (comment_block.name, ),
1340                               position)
1341
1342                     tag = GtkDocTag(TAG_RETURNS, position)
1343
1344                     if param_fields:
1345                         result = self._parse_fields(position,
1346                                                     column_offset + param_fields_start,
1347                                                     original_line,
1348                                                     param_fields)
1349                         if result.success:
1350                             tag.annotations = result.annotations
1351                             tag.description = result.description
1352                     comment_block.tags[TAG_RETURNS] = tag
1353                     current_part = tag
1354                     continue
1355                 elif (param_name == 'Varargs'
1356                 or (param_name.endswith('...') and param_name != '...')):
1357                     # Deprecated @Varargs notation or named __VA_ARGS__ instead of @...
1358                     warn('"@%s" parameter is deprecated, please use "@..." instead:\n%s\n%s' %
1359                          (param_name, original_line, marker),
1360                          position)
1361                     param_name = '...'
1362
1363                 if param_name in comment_block.params.keys():
1364                     error('multiple "@%s" parameters for identifier "%s":\n%s\n%s' %
1365                           (param_name, comment_block.name, original_line, marker),
1366                           position)
1367
1368                 parameter = GtkDocParameter(param_name, position)
1369
1370                 if param_fields:
1371                     result = self._parse_fields(position,
1372                                                 column_offset + param_fields_start,
1373                                                 original_line,
1374                                                 param_fields)
1375                     if result.success:
1376                         parameter.annotations = result.annotations
1377                         parameter.description = result.description
1378
1379                 comment_block.params[param_name] = parameter
1380                 current_part = parameter
1381                 continue
1382
1383             ####################################################################
1384             # Check for comment block description.
1385             #
1386             # When we are parsing parameter parts or the identifier part (when
1387             # there are no parameters) and encounter an empty line, we must be
1388             # parsing the comment block description.
1389             #
1390             # Note: it is unclear why GTK-Doc does not allow paragraph breaks
1391             #       at this location as those might be handy describing
1392             #       parameters from time to time...
1393             ####################################################################
1394             if (EMPTY_LINE_RE.match(line) and in_part in [PART_IDENTIFIER, PART_PARAMETERS]):
1395                 in_part = PART_DESCRIPTION
1396                 part_indent = line_indent
1397                 continue
1398
1399             ####################################################################
1400             # Check for GTK-Doc comment block tags.
1401             ####################################################################
1402             result = TAG_RE.match(line)
1403             if result and line_indent <= part_indent:
1404                 part_indent = line_indent
1405                 tag_name = result.group('tag_name')
1406                 tag_name_lower = tag_name.lower()
1407                 tag_fields = result.group('fields')
1408                 tag_fields_start = result.start('fields')
1409                 marker = ' ' * (result.start('tag_name') + column_offset) + '^'
1410
1411                 if tag_name_lower in DEPRECATED_GI_ANN_TAGS:
1412                     # Deprecated GObject-Introspection specific tags.
1413                     # Emit a warning and transform these into annotations on the identifier
1414                     # instead, as agreed upon in http://bugzilla.gnome.org/show_bug.cgi?id=676133
1415                     warn('GObject-Introspection specific GTK-Doc tag "%s" '
1416                          'has been deprecated, please use annotations on the identifier '
1417                          'instead:\n%s\n%s' % (tag_name, original_line, marker),
1418                          position)
1419
1420                     # Translate deprecated tag name into corresponding annotation name
1421                     ann_name = tag_name_lower.replace(' ', '-')
1422
1423                     if tag_name_lower == TAG_ATTRIBUTES:
1424                         transformed = ''
1425                         result = self._parse_fields(position,
1426                                                     result.start('tag_name') + column_offset,
1427                                                     line,
1428                                                     tag_fields.strip(),
1429                                                     False,
1430                                                     False)
1431
1432                         if result.success:
1433                             for annotation in result.annotations:
1434                                 ann_options = self._parse_annotation_options_list(position, marker,
1435                                                                                   line, annotation)
1436                                 n_options = len(ann_options)
1437                                 if n_options == 1:
1438                                     transformed = '%s %s' % (transformed, ann_options[0], )
1439                                 elif n_options == 2:
1440                                     transformed = '%s %s=%s' % (transformed, ann_options[0],
1441                                                                 ann_options[1])
1442                                 else:
1443                                     # Malformed Attributes: tag
1444                                     error('malformed "Attributes:" tag will be ignored:\n%s\n%s' %
1445                                           (original_line, marker),
1446                                           position)
1447                                     transformed = None
1448
1449                             if transformed:
1450                                 transformed = '%s %s' % (ann_name, transformed.strip())
1451                                 ann_name, docannotation = self._parse_annotation(
1452                                     position,
1453                                     column_offset + tag_fields_start,
1454                                     original_line,
1455                                     transformed)
1456                                 stored_annotation = comment_block.annotations.get('attributes')
1457                                 if stored_annotation:
1458                                     error('Duplicate "Attributes:" annotation will '
1459                                           'be ignored:\n%s\n%s' % (original_line, marker),
1460                                           position)
1461                                 else:
1462                                     comment_block.annotations[ann_name] = docannotation
1463                     else:
1464                         ann_name, options = self._parse_annotation(position,
1465                                                                column_offset + tag_fields_start,
1466                                                                line,
1467                                                                '%s %s' % (ann_name, tag_fields))
1468                         comment_block.annotations[ann_name] = options
1469
1470                     continue
1471                 elif tag_name_lower == TAG_DESCRIPTION:
1472                     # Deprecated GTK-Doc Description: tag
1473                     warn('GTK-Doc tag "Description:" has been deprecated:\n%s\n%s' %
1474                          (original_line, marker),
1475                          position)
1476
1477                     in_part = PART_DESCRIPTION
1478
1479                     if comment_block.description is None:
1480                         comment_block.description = tag_fields
1481                     else:
1482                         comment_block.description += '\n%s' % (tag_fields, )
1483                     continue
1484
1485                 # Now that the deprecated stuff is out of the way, continue parsing real tags
1486                 if (in_part == PART_DESCRIPTION
1487                 or (in_part == PART_PARAMETERS and not comment_block.description)
1488                 or (in_part == PART_IDENTIFIER and not comment_block.params and not
1489                 comment_block.description)):
1490                     in_part = PART_TAGS
1491
1492                 if in_part != PART_TAGS:
1493                     in_part = PART_TAGS
1494                     warn('"%s:" tag unexpected at this location:\n%s\n%s' %
1495                          (tag_name, original_line, marker),
1496                          position)
1497
1498                 if tag_name_lower in [TAG_RETURN, TAG_RETURNS,
1499                                       TAG_RETURN_VALUE, TAG_RETURNS_VALUE]:
1500                     if not returns_seen:
1501                         returns_seen = True
1502                     else:
1503                         error('encountered multiple return value parameters or tags for "%s".' %
1504                               (comment_block.name, ),
1505                               position)
1506
1507                     tag = GtkDocTag(TAG_RETURNS, position)
1508
1509                     if tag_fields:
1510                         result = self._parse_fields(position,
1511                                                     column_offset + tag_fields_start,
1512                                                     original_line,
1513                                                     tag_fields)
1514                         if result.success:
1515                             tag.annotations = result.annotations
1516                             tag.description = result.description
1517
1518                     comment_block.tags[TAG_RETURNS] = tag
1519                     current_part = tag
1520                     continue
1521                 else:
1522                     if tag_name_lower in comment_block.tags.keys():
1523                         error('multiple "%s:" tags for identifier "%s":\n%s\n%s' %
1524                               (tag_name, comment_block.name, original_line, marker),
1525                               position)
1526
1527                     tag = GtkDocTag(tag_name_lower, position)
1528
1529                     if tag_fields:
1530                         result = self._parse_fields(position,
1531                                                     column_offset + tag_fields_start,
1532                                                     original_line,
1533                                                     tag_fields)
1534                         if result.success:
1535                             if result.annotations:
1536                                 error('annotations not supported for tag "%s:".' % (tag_name, ),
1537                                       position)
1538
1539                             if tag_name_lower in [TAG_DEPRECATED, TAG_SINCE]:
1540                                 result = TAG_VALUE_VERSION_RE.match(result.description)
1541                                 tag.value = result.group('value')
1542                                 tag.description = result.group('description')
1543                             elif tag_name_lower == TAG_STABILITY:
1544                                 result = TAG_VALUE_STABILITY_RE.match(result.description)
1545                                 tag.value = result.group('value').capitalize()
1546                                 tag.description = result.group('description')
1547
1548                     comment_block.tags[tag_name_lower] = tag
1549                     current_part = tag
1550                     continue
1551
1552             ####################################################################
1553             # If we get here, we must be in the middle of a multiline
1554             # comment block, parameter or tag description.
1555             ####################################################################
1556             if EMPTY_LINE_RE.match(line) is None:
1557                 line = line.rstrip()
1558
1559             if in_part in [PART_IDENTIFIER, PART_DESCRIPTION]:
1560                 if not comment_block.description:
1561                     if in_part == PART_IDENTIFIER:
1562                         self._validate_multiline_annotation_continuation(line, original_line,
1563                                                                          column_offset, position)
1564                 if comment_block.description is None:
1565                     comment_block.description = line
1566                 else:
1567                     comment_block.description += '\n' + line
1568                 continue
1569             elif in_part in [PART_PARAMETERS, PART_TAGS]:
1570                 if not current_part.description:
1571                     self._validate_multiline_annotation_continuation(line, original_line,
1572                                                                      column_offset, position)
1573                 if current_part.description is None:
1574                     current_part.description = line
1575                 else:
1576                     current_part.description += '\n' + line
1577                 continue
1578
1579         ########################################################################
1580         # Finished parsing this comment block.
1581         ########################################################################
1582         if comment_block:
1583             # We have picked up a couple of \n characters that where not
1584             # intended. Strip those.
1585             if comment_block.description:
1586                 comment_block.description = comment_block.description.strip()
1587
1588             for tag in comment_block.tags.values():
1589                 self._clean_description_field(tag)
1590
1591             for param in comment_block.params.values():
1592                 self._clean_description_field(param)
1593
1594             comment_block.indentation = block_indent
1595             comment_block.validate()
1596             return comment_block
1597         else:
1598             return None
1599
1600     def _clean_description_field(self, part):
1601         '''
1602         Remove extraneous leading and trailing whitespace from description fields.
1603
1604         :param part: a GTK-Doc comment block part having a description field
1605         '''
1606
1607         if part.description:
1608             if part.description.strip() == '':
1609                 part.description = None
1610             else:
1611                 if EMPTY_LINE_RE.match(part.description.split('\n', 1)[0]):
1612                     part.description = part.description.rstrip()
1613                 else:
1614                     part.description = part.description.strip()
1615
1616     def _validate_multiline_annotation_continuation(self, line, original_line,
1617                                                     column_offset, position):
1618         '''
1619         Validate annotatable parts' source text ensuring annotations don't span multiple lines.
1620         For example, the following comment block would result in a warning being emitted for
1621         the forth line::
1622
1623             /**
1624              * shiny_function:
1625              * @array_: (out caller-allocates) (array)
1626              *          (element-type utf8) (transfer full): A beautiful array
1627              */
1628
1629         :param line: line to validate, stripped from  ("``*/``") at start of the line.
1630         :param original_line: original line (including  ("``*/``"))  being validated
1631         :param column_offset: number of characters stripped from `line` when   ("``*/``")
1632                               was removed
1633         :param position: :class:`giscanner.message.Position` of `line` in the source file
1634         '''
1635
1636         result = self._parse_annotations(position, column_offset, original_line, line)
1637
1638         if result.success and result.annotations:
1639             marker = ' ' * (result.start_pos + column_offset) + '^'
1640             error('ignoring invalid multiline annotation continuation:\n%s\n%s' %
1641                   (original_line, marker),
1642                   position)
1643
1644     def _parse_annotation_options_list(self, position, column, line, options):
1645         '''
1646         Parse annotation options into a list. For example::
1647
1648             ┌──────────────────────────────────────────────────────────────┐
1649             │ 'option1 option2 option3'                                    │ ─▷ source
1650             ├──────────────────────────────────────────────────────────────┤
1651             │ ['option1', 'option2', 'option3']                            │ ◁─ parsed options
1652             └──────────────────────────────────────────────────────────────┘
1653
1654         :param position: :class:`giscanner.message.Position` of `line` in the source file
1655         :param column: start column of the `options` in the source file
1656         :param line: complete source line
1657         :param options: annotation options to parse
1658         :returns: a list of annotation options
1659         '''
1660
1661         parsed = []
1662
1663         if options:
1664             result = options.find('=')
1665             if result >= 0:
1666                 marker = ' ' * (column + result) + '^'
1667                 warn('invalid annotation options: expected a "list" but '
1668                      'received "key=value pairs":\n%s\n%s' % (line, marker),
1669                      position)
1670                 parsed = self._parse_annotation_options_unknown(position, column, line, options)
1671             else:
1672                 parsed = options.split(' ')
1673
1674         return parsed
1675
1676     def _parse_annotation_options_dict(self, position, column, line, options):
1677         '''
1678         Parse annotation options into a dict. For example::
1679
1680             ┌──────────────────────────────────────────────────────────────┐
1681             │ 'option1=value1 option2 option3=value2'                      │ ─▷ source
1682             ├──────────────────────────────────────────────────────────────┤
1683             │ {'option1': 'value1', 'option2': None, 'option3': 'value2'}  │ ◁─ parsed options
1684             └──────────────────────────────────────────────────────────────┘
1685
1686         :param position: :class:`giscanner.message.Position` of `line` in the source file
1687         :param column: start column of the `options` in the source file
1688         :param line: complete source line
1689         :param options: annotation options to parse
1690         :returns: an ordered dictionary of annotation options
1691         '''
1692
1693         parsed = OrderedDict()
1694
1695         if options:
1696             for p in options.split(' '):
1697                 parts = p.split('=', 1)
1698                 key = parts[0]
1699                 value = parts[1] if len(parts) == 2 else None
1700                 parsed[key] = value
1701
1702         return parsed
1703
1704     def _parse_annotation_options_unknown(self, position, column, line, options):
1705         '''
1706         Parse annotation options into a list holding a single item. This is used when the
1707         annotation options to parse in not known to be a list nor dict. For example::
1708
1709             ┌──────────────────────────────────────────────────────────────┐
1710             │ '   option1 option2   option3=value1   '                     │ ─▷ source
1711             ├──────────────────────────────────────────────────────────────┤
1712             │ ['option1 option2   option3=value1']                         │ ◁─ parsed options
1713             └──────────────────────────────────────────────────────────────┘
1714
1715         :param position: :class:`giscanner.message.Position` of `line` in the source file
1716         :param column: start column of the `options` in the source file
1717         :param line: complete source line
1718         :param options: annotation options to parse
1719         :returns: a list of annotation options
1720         '''
1721
1722         if options:
1723             return [options.strip()]
1724
1725     def _parse_annotation(self, position, column, line, annotation):
1726         '''
1727         Parse an annotation into the annotation name and a list or dict (depending on the
1728         name of the annotation) holding the options. For example::
1729
1730             ┌──────────────────────────────────────────────────────────────┐
1731             │ 'name opt1=value1 opt2=value2 opt3'                          │ ─▷ source
1732             ├──────────────────────────────────────────────────────────────┤
1733             │ 'name', {'opt1': 'value1', 'opt2':'value2', 'opt3':None}     │ ◁─ parsed annotation
1734             └──────────────────────────────────────────────────────────────┘
1735
1736             ┌──────────────────────────────────────────────────────────────┐
1737             │ 'name   opt1 opt2'                                           │ ─▷ source
1738             ├──────────────────────────────────────────────────────────────┤
1739             │ 'name', ['opt1', 'opt2']                                     │ ◁─ parsed annotation
1740             └──────────────────────────────────────────────────────────────┘
1741
1742             ┌──────────────────────────────────────────────────────────────┐
1743             │ 'unkownname   unknown list of options'                       │ ─▷ source
1744             ├──────────────────────────────────────────────────────────────┤
1745             │ 'unkownname', ['unknown list of options']                    │ ◁─ parsed annotation
1746             └──────────────────────────────────────────────────────────────┘
1747
1748         :param position: :class:`giscanner.message.Position` of `line` in the source file
1749         :param column: start column of the `annotation` in the source file
1750         :param line: complete source line
1751         :param annotation: annotation to parse
1752         :returns: a tuple containing the annotation name and options
1753         '''
1754
1755         # Transform deprecated type syntax "tokens"
1756         annotation = annotation.replace('<', ANN_LPAR).replace('>', ANN_RPAR)
1757
1758         parts = annotation.split(' ', 1)
1759         ann_name = parts[0].lower()
1760         ann_options = parts[1] if len(parts) == 2 else None
1761
1762         if ann_name == ANN_INOUT_ALT:
1763             marker = ' ' * (column) + '^'
1764             warn('"%s" annotation has been deprecated, please use "%s" instead:\n%s\n%s' %
1765                  (ANN_INOUT_ALT, ANN_INOUT, line, marker),
1766                  position)
1767
1768             ann_name = ANN_INOUT
1769         elif ann_name == ANN_ATTRIBUTE:
1770             marker = ' ' * (column) + '^'
1771             warn('"%s" annotation has been deprecated, please use "%s" instead:\n%s\n%s' %
1772                  (ANN_ATTRIBUTE, ANN_ATTRIBUTES, line, marker),
1773                  position)
1774
1775             ann_name = ANN_ATTRIBUTES
1776             ann_options = self._parse_annotation_options_list(position, column, line, ann_options)
1777             n_options = len(ann_options)
1778             if n_options == 1:
1779                 ann_options = ann_options[0]
1780             elif n_options == 2:
1781                 ann_options = '%s=%s' % (ann_options[0], ann_options[1])
1782             else:
1783                 marker = ' ' * (column) + '^'
1784                 error('malformed "(attribute)" annotation will be ignored:\n%s\n%s' %
1785                       (line, marker),
1786                       position)
1787                 return None, None
1788
1789         column += len(ann_name) + 2
1790
1791         if ann_name in LIST_ANNOTATIONS:
1792             ann_options = self._parse_annotation_options_list(position, column, line, ann_options)
1793         elif ann_name in DICT_ANNOTATIONS:
1794             ann_options = self._parse_annotation_options_dict(position, column, line, ann_options)
1795         else:
1796             ann_options = self._parse_annotation_options_unknown(position, column, line,
1797                                                                  ann_options)
1798
1799         return ann_name, ann_options
1800
1801     def _parse_annotations(self, position, column, line, fields, parse_options=True):
1802         '''
1803         Parse annotations into a :class:`GtkDocAnnotations` object.
1804
1805         :param position: :class:`giscanner.message.Position` of `line` in the source file
1806         :param column: start column of the `annotations` in the source file
1807         :param line: complete source line
1808         :param fields: string containing the fields to parse
1809         :param parse_options: whether options will be parsed into a :class:`GtkDocAnnotations`
1810                               object or into a :class:`list`
1811         :returns: if `parse_options` evaluates to True a :class:`GtkDocAnnotations` object,
1812                   a :class:`list` otherwise. If `line` does not contain any annotations,
1813                   :const:`None`
1814         '''
1815
1816         if parse_options:
1817             parsed_annotations = GtkDocAnnotations(position)
1818         else:
1819             parsed_annotations = []
1820
1821         i = 0
1822         parens_level = 0
1823         prev_char = ''
1824         char_buffer = []
1825         start_pos = 0
1826         end_pos = 0
1827
1828         for i, cur_char in enumerate(fields):
1829             cur_char_is_space = cur_char.isspace()
1830
1831             if cur_char == ANN_LPAR:
1832                 parens_level += 1
1833
1834                 if parens_level == 1:
1835                     start_pos = i
1836
1837                 if prev_char == ANN_LPAR:
1838                     marker = ' ' * (column + i) + '^'
1839                     error('unexpected parentheses, annotations will be ignored:\n%s\n%s' %
1840                           (line, marker),
1841                           position)
1842                     return _ParseAnnotationsResult(False, None, None, None)
1843                 elif parens_level > 1:
1844                     char_buffer.append(cur_char)
1845             elif cur_char == ANN_RPAR:
1846                 parens_level -= 1
1847
1848                 if prev_char == ANN_LPAR:
1849                     marker = ' ' * (column + i) + '^'
1850                     error('unexpected parentheses, annotations will be ignored:\n%s\n%s' %
1851                           (line, marker),
1852                           position)
1853                     return _ParseAnnotationsResult(False, None, None, None)
1854                 elif parens_level < 0:
1855                     marker = ' ' * (column + i) + '^'
1856                     error('unbalanced parentheses, annotations will be ignored:\n%s\n%s' %
1857                           (line, marker),
1858                           position)
1859                     return _ParseAnnotationsResult(False, None, None, None)
1860                 elif parens_level == 0:
1861                     end_pos = i + 1
1862
1863                     if parse_options is True:
1864                         name, options = self._parse_annotation(position,
1865                                                                column + start_pos,
1866                                                                line,
1867                                                                ''.join(char_buffer).strip())
1868                         if name is not None:
1869                             if name in parsed_annotations:
1870                                 marker = ' ' * (column + i) + '^'
1871                                 error('multiple "%s" annotations:\n%s\n%s' %
1872                                       (name, line, marker), position)
1873                             parsed_annotations[name] = options
1874                     else:
1875                         parsed_annotations.append(''.join(char_buffer).strip())
1876
1877                     char_buffer = []
1878                 else:
1879                     char_buffer.append(cur_char)
1880             elif cur_char_is_space:
1881                 if parens_level > 0:
1882                     char_buffer.append(cur_char)
1883             else:
1884                 if parens_level == 0:
1885                     break
1886                 else:
1887                     char_buffer.append(cur_char)
1888
1889             prev_char = cur_char
1890
1891         if parens_level > 0:
1892             marker = ' ' * (column + i) + '^'
1893             error('unbalanced parentheses, annotations will be ignored:\n%s\n%s' %
1894                   (line, marker),
1895                   position)
1896             return _ParseAnnotationsResult(False, None, None, None)
1897         else:
1898             return _ParseAnnotationsResult(True, parsed_annotations, start_pos, end_pos)
1899
1900     def _parse_fields(self, position, column, line, fields, parse_options=True,
1901                       validate_description_field=True):
1902         '''
1903         Parse annotations out of field data. For example::
1904
1905             ┌──────────────────────────────────────────────────────────────┐
1906             │ '(skip): description of some parameter                       │ ─▷ source
1907             ├──────────────────────────────────────────────────────────────┤
1908             │ ({'skip': []}, 'description of some parameter')              │ ◁─ annotations and
1909             └──────────────────────────────────────────────────────────────┘    remaining fields
1910
1911         :param position: :class:`giscanner.message.Position` of `line` in the source file
1912         :param column: start column of `fields` in the source file
1913         :param line: complete source line
1914         :param fields: string containing the fields to parse
1915         :param parse_options: whether options will be parsed into a :class:`GtkDocAnnotations`
1916                               object or into a :class:`list`
1917         :param validate_description_field: :const:`True` to validate the description field
1918         :returns: if `parse_options` evaluates to True a :class:`GtkDocAnnotations` object,
1919                   a :class:`list` otherwise. If `line` does not contain any annotations,
1920                   :const:`None` and a string holding the remaining fields
1921         '''
1922         description_field = ''
1923         result = self._parse_annotations(position, column, line, fields, parse_options)
1924         if result.success:
1925             description_field = fields[result.end_pos:].strip()
1926
1927             if description_field and validate_description_field:
1928                 if description_field.startswith(':'):
1929                     description_field = description_field[1:]
1930                 else:
1931                     if result.end_pos > 0:
1932                         marker_position = column + result.end_pos
1933                         marker = ' ' * marker_position + '^'
1934                         warn('missing ":" at column %s:\n%s\n%s' %
1935                              (marker_position + 1, line, marker),
1936                              position)
1937
1938         return _ParseFieldsResult(result.success, result.annotations, description_field)
1939
1940
1941 class GtkDocCommentBlockWriter(object):
1942     '''
1943     Serialized :class:`GtkDocCommentBlock` objects into GTK-Doc comment blocks.
1944     '''
1945
1946     def __init__(self, indent=True):
1947         #: :const:`True` if the original indentation preceding the "``*``" needs to be retained,
1948         #: :const:`False` otherwise. Default value is :const:`True`.
1949         self.indent = indent
1950
1951     def _serialize_annotations(self, annotations):
1952         '''
1953         Serialize an annotation field. For example::
1954
1955             ┌──────────────────────────────────────────────────────────────┐
1956             │ {'name': {'opt1': 'value1', 'opt2':'value2', 'opt3':None}    │ ◁─ GtkDocAnnotations
1957             ├──────────────────────────────────────────────────────────────┤
1958             │ '(name opt1=value1 opt2=value2 opt3)'                        │ ─▷ serialized
1959             └──────────────────────────────────────────────────────────────┘
1960
1961             ┌──────────────────────────────────────────────────────────────┐
1962             │ {'name': ['opt1', 'opt2']}                                   │ ◁─ GtkDocAnnotations
1963             ├──────────────────────────────────────────────────────────────┤
1964             │ '(name opt1 opt2)'                                           │ ─▷ serialized
1965             └──────────────────────────────────────────────────────────────┘
1966
1967             ┌──────────────────────────────────────────────────────────────┐
1968             │ {'unkownname': ['unknown list of options']}                  │ ◁─ GtkDocAnnotations
1969             ├──────────────────────────────────────────────────────────────┤
1970             │ '(unkownname unknown list of options)'                       │ ─▷ serialized
1971             └──────────────────────────────────────────────────────────────┘
1972
1973         :param annotations: :class:`GtkDocAnnotations` to be serialized
1974         :returns: a string
1975         '''
1976
1977         serialized = []
1978
1979         for ann_name, options in annotations.items():
1980             if options:
1981                 if isinstance(options, list):
1982                     serialize_options = ' '.join(options)
1983                 else:
1984                     serialize_options = ''
1985
1986                     for key, value in options.items():
1987                         if value:
1988                             serialize_options += '%s=%s ' % (key, value)
1989                         else:
1990                             serialize_options += '%s ' % (key, )
1991
1992                     serialize_options = serialize_options.strip()
1993
1994                 serialized.append('(%s %s)' % (ann_name, serialize_options))
1995             else:
1996                 serialized.append('(%s)' % (ann_name, ))
1997
1998         return ' '.join(serialized)
1999
2000     def _serialize_parameter(self, parameter):
2001         '''
2002         Serialize a parameter.
2003
2004         :param parameter: :class:`GtkDocParameter` to be serialized
2005         :returns: a string
2006         '''
2007
2008         # parameter_name field
2009         serialized = '@%s' % (parameter.name, )
2010
2011         # annotations field
2012         if parameter.annotations:
2013             serialized += ': ' + self._serialize_annotations(parameter.annotations)
2014
2015         # description field
2016         if parameter.description:
2017             if parameter.description.startswith('\n'):
2018                 serialized += ':' + parameter.description
2019             else:
2020                 serialized += ': ' + parameter.description
2021         else:
2022             serialized += ':'
2023
2024         return serialized.split('\n')
2025
2026     def _serialize_tag(self, tag):
2027         '''
2028         Serialize a tag.
2029
2030         :param tag: :class:`GtkDocTag` to be serialized
2031         :returns: a string
2032         '''
2033
2034         # tag_name field
2035         serialized = tag.name.capitalize()
2036
2037         # annotations field
2038         if tag.annotations:
2039             serialized += ': ' + self._serialize_annotations(tag.annotations)
2040
2041         # value field
2042         if tag.value:
2043             serialized += ': ' + tag.value
2044
2045         # description field
2046         if tag.description:
2047             if tag.description.startswith('\n'):
2048                 serialized += ':' + tag.description
2049             else:
2050                 serialized += ': ' + tag.description
2051
2052         if not tag.value and not tag.description:
2053             serialized += ':'
2054
2055         return serialized.split('\n')
2056
2057     def write(self, block):
2058         '''
2059         Serialize a :class:`GtkDocCommentBlock` object.
2060
2061         :param block: :class:`GtkDocCommentBlock` to be serialized
2062         :returns: a string
2063         '''
2064
2065         if block is None:
2066             return ''
2067         else:
2068             lines = []
2069
2070             # Identifier part
2071             if block.name.startswith('SECTION'):
2072                 lines.append(block.name)
2073             else:
2074                 if block.annotations:
2075                     annotations = self._serialize_annotations(block.annotations)
2076                     lines.append('%s: %s' % (block.name, annotations))
2077                 else:
2078                     # Note: this delimiter serves no purpose other than most people being used
2079                     #       to reading/writing it. It is completely legal to ommit this.
2080                     lines.append('%s:' % (block.name, ))
2081
2082             # Parameter parts
2083             for param in block.params.values():
2084                 lines.extend(self._serialize_parameter(param))
2085
2086             # Comment block description part
2087             if block.description:
2088                 lines.append('')
2089                 for l in block.description.split('\n'):
2090                     lines.append(l)
2091
2092             # Tag parts
2093             if block.tags:
2094                 # Note: this empty line servers no purpose other than most people being used
2095                 #       to reading/writing it. It is completely legal to ommit this.
2096                 lines.append('')
2097                 for tag in block.tags.values():
2098                     lines.extend(self._serialize_tag(tag))
2099
2100             # Restore comment block indentation and *
2101             if self.indent:
2102                 indent = Counter(block.indentation).most_common(1)[0][0] or ' '
2103                 if indent.endswith('\t'):
2104                     start_indent = indent
2105                     line_indent = indent + ' '
2106                 else:
2107                     start_indent = indent[:-1]
2108                     line_indent = indent
2109             else:
2110                 start_indent = ''
2111                 line_indent = ' '
2112
2113             i = 0
2114             while i < len(lines):
2115                 line = lines[i]
2116                 if line:
2117                     lines[i] = '%s* %s\n' % (line_indent, line)
2118                 else:
2119                     lines[i] = '%s*\n' % (line_indent, )
2120                 i += 1
2121
2122             # Restore comment block start and end tokens
2123             lines.insert(0, '%s/**\n' % (start_indent, ))
2124             lines.append('%s*/\n' % (line_indent, ))
2125
2126             # Restore code before and after comment block start and end tokens
2127             if block.code_before:
2128                 lines.insert(0, '%s\n' % (block.code_before, ))
2129
2130             if block.code_after:
2131                 lines.append('%s\n' % (block.code_after, ))
2132
2133             return ''.join(lines)