1 # -*- coding: utf-8 -*-
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>
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.
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.
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
26 GTK-Doc comment block format
27 ----------------------------
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.
32 Known parts and the fields they are constructed from look like the following
33 (optional fields are enclosed in square brackets)::
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 ├─────────────┴─────────────────┴───────────┴───────────────┤
48 └───────────────────────────────────────────────────────────┘
50 There are two conditions that must be met before a comment block is recognized
51 as a GTK-Doc comment block:
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
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
60 #. There must be exactly 1 `identifier` part on the first line of the
61 comment block which consists of:
63 * a required `identifier_name` field
64 * an optional `annotations` field
66 #. Zero or more `parameter` parts, each consisting of:
68 * a required `parameter_name` field
69 * an optional `annotations` field
70 * a required `description` field (can be the empty string)
72 #. One optional `comment block description` part which must begin with at
73 least 1 empty line signaling the start of this part.
75 #. Zero or more `tag` parts, each consisting of:
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)
82 Additionally, the following restrictions are in effect:
84 #. Separating parts with an empty line:
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
94 #. Parts and fields cannot span multiple lines, except for:
96 * the `comment_block_description` part
97 * `parameter description` and `tag description` fields
99 #. Taking the above restrictions into account, spanning multiple paragraphs is
100 limited to the `comment block description` part and `tag description` fields.
102 Refer to the `GTK-Doc manual`_ for more detailed usage information.
105 http://developer.gnome.org/gtk-doc-manual/1.18/documenting.html.en
109 from __future__ import absolute_import
114 from collections import namedtuple
115 from operator import ne, gt, lt
117 from .collections import Counter, OrderedDict
118 from .message import Position, warn, error
121 # GTK-Doc comment block parts
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'
133 TAG_STABILITY = 'stability'
135 GTKDOC_TAGS = [TAG_DEPRECATED,
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'
145 DEPRECATED_GTKDOC_TAGS = [TAG_DESCRIPTION,
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'
153 DEPRECATED_GI_TAGS = [TAG_RETURN,
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'
168 TAG_UNREF_FUNC = 'unref func'
170 TAG_VFUNC = 'virtual'
172 DEPRECATED_GI_ANN_TAGS = [TAG_ATTRIBUTES,
183 ALL_TAGS = GTKDOC_TAGS + DEPRECATED_GTKDOC_TAGS + DEPRECATED_GI_TAGS + DEPRECATED_GI_ANN_TAGS
185 # GObject-Introspection annotation start/end tokens
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'
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'
203 ANN_METHOD = 'method'
205 ANN_REF_FUNC = 'ref-func'
206 ANN_RENAME_TO = 'rename-to'
208 ANN_SET_VALUE_FUNC = 'set-value-func'
210 ANN_TRANSFER = 'transfer'
212 ANN_UNREF_FUNC = 'unref-func'
213 ANN_VFUNC = 'virtual'
216 GI_ANNS = [ANN_ALLOW_NONE,
240 # 2) Deprecated GObject-Introspection annotations
241 ANN_ATTRIBUTE = 'attribute'
242 ANN_INOUT_ALT = 'in-out'
244 DEPRECATED_GI_ANNS = [ANN_ATTRIBUTE,
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]
251 # (array) annotation options
252 OPT_ARRAY_FIXED_SIZE = 'fixed-size'
253 OPT_ARRAY_LENGTH = 'length'
254 OPT_ARRAY_ZERO_TERMINATED = 'zero-terminated'
256 ARRAY_OPTIONS = [OPT_ARRAY_FIXED_SIZE,
258 OPT_ARRAY_ZERO_TERMINATED]
260 # (out) annotation options
261 OPT_OUT_CALLEE_ALLOCATES = 'callee-allocates'
262 OPT_OUT_CALLER_ALLOCATES = 'caller-allocates'
264 OUT_OPTIONS = [OPT_OUT_CALLEE_ALLOCATES,
265 OPT_OUT_CALLER_ALLOCATES]
267 # (scope) annotation options
268 OPT_SCOPE_ASYNC = 'async'
269 OPT_SCOPE_CALL = 'call'
270 OPT_SCOPE_NOTIFIED = 'notified'
272 SCOPE_OPTIONS = [OPT_SCOPE_ASYNC,
276 # (transfer) annotation options
277 OPT_TRANSFER_CONTAINER = 'container'
278 OPT_TRANSFER_FLOATING = 'floating'
279 OPT_TRANSFER_FULL = 'full'
280 OPT_TRANSFER_NONE = 'none'
282 TRANSFER_OPTIONS = [OPT_TRANSFER_CONTAINER,
283 OPT_TRANSFER_FLOATING,
288 # Pattern used to normalize different types of line endings
289 LINE_BREAK_RE = re.compile(r'\r\n|\r|\n', re.UNICODE)
291 # Pattern matching the start token of a comment block.
292 COMMENT_BLOCK_START_RE = re.compile(
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
305 re.UNICODE | re.VERBOSE)
307 # Pattern matching the end token of a comment block.
308 COMMENT_BLOCK_END_RE = re.compile(
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
320 re.UNICODE | re.VERBOSE)
322 # Pattern matching the ' * ' at the beginning of every
323 # line inside a comment block.
324 COMMENT_ASTERISK_RE = re.compile(
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
336 re.UNICODE | re.VERBOSE)
338 # Pattern matching the indentation level of a line (used
339 # to get the indentation before and after the ' * ').
340 INDENTATION_RE = re.compile(
343 (?P<indentation>\s*) # 0 or more whitespace characters
347 re.UNICODE | re.VERBOSE)
349 # Pattern matching an empty line.
350 EMPTY_LINE_RE = re.compile(
353 \s* # 0 or more whitespace characters
356 re.UNICODE | re.VERBOSE)
358 # Pattern matching SECTION identifiers.
359 SECTION_RE = re.compile(
362 \s* # 0 or more whitespace characters
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
373 re.UNICODE | re.VERBOSE)
375 # Pattern matching symbol (function, constant, struct and enum) identifiers.
376 SYMBOL_RE = re.compile(
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
390 re.UNICODE | re.VERBOSE)
392 # Pattern matching property identifiers.
393 PROPERTY_RE = re.compile(
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
411 re.UNICODE | re.VERBOSE)
413 # Pattern matching signal identifiers.
414 SIGNAL_RE = re.compile(
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
432 re.UNICODE | re.VERBOSE)
434 # Pattern matching parameters.
435 PARAMETER_RE = re.compile(
438 \s* # 0 or more whitespace characters
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
448 re.UNICODE | re.VERBOSE)
450 # Pattern matching tags.
451 _all_tags = '|'.join(ALL_TAGS).replace(' ', r'\s')
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
464 re.UNICODE | re.VERBOSE | re.IGNORECASE)
466 # Pattern matching value and description fields for TAG_DEPRECATED & TAG_SINCE tags.
467 TAG_VALUE_VERSION_RE = re.compile(
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
479 re.UNICODE | re.VERBOSE)
481 # Pattern matching value and description fields for TAG_STABILITY tags.
482 TAG_VALUE_STABILITY_RE = re.compile(
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
494 re.UNICODE | re.VERBOSE | re.IGNORECASE)
497 class GtkDocAnnotations(OrderedDict):
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`.
504 __slots__ = ('position')
506 def __init__(self, position=None):
507 OrderedDict.__init__(self)
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
514 class GtkDocAnnotatable(object):
516 Base class for GTK-Doc comment block parts that can be annotated.
519 __slots__ = ('position', 'annotations')
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 = ()
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
531 #: A :class:`GtkDocAnnotations` instance representing the annotations
532 #: applied to this :class:`GtkDocAnnotatable` instance.
533 self.annotations = GtkDocAnnotations()
536 return '<GtkDocAnnotatable %r %r>' % (self.annotations, )
540 Validate annotations stored by the :class:`GtkDocAnnotatable` instance, if any.
544 position = self.annotations.position
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)
555 # Not error() as ann_name might be valid in some newer
556 # GObject-Instrospection version.
557 warn('unknown annotation: %s' % (ann_name, ), position)
559 def _validate_options(self, position, ann_name, n_options, expected_n_options, operator,
562 Validate the number of options held by an annotation according to the test
563 ``operator(n_options, expected_n_options)``.
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.
579 t = '%d' % (n_options, )
581 if expected_n_options == 0:
583 elif expected_n_options == 1:
586 s = '%d options' % (expected_n_options, )
588 if operator(n_options, expected_n_options):
589 warn('"%s" annotation %s %s, %s given' % (ann_name, message, s, t), position)
591 def _validate_annotation(self, position, ann_name, options, choices=None,
592 exact_n_options=None, min_n_options=None, max_n_options=None):
594 Validate an annotation.
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
606 n_options = len(options)
608 if exact_n_options is not None:
609 self._validate_options(position,
610 ann_name, n_options, exact_n_options, ne, 'needs')
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')
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')
620 if options and choices is not None:
622 if option not in choices:
623 warn('invalid "%s" annotation option: "%s"' % (ann_name, option), position)
625 def _do_validate_allow_none(self, position, ann_name, options):
627 Validate the ``(allow-none)`` annotation.
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
635 self._validate_annotation(position, ann_name, options, exact_n_options=0)
637 def _do_validate_array(self, position, ann_name, options):
639 Validate the ``(array)`` annotation.
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
647 if len(options) == 0:
650 for option, value in options.items():
651 if option in [OPT_ARRAY_ZERO_TERMINATED, OPT_ARRAY_FIXED_SIZE]:
654 except (TypeError, ValueError):
656 warn('"%s" annotation option "%s" needs a value' % (ann_name, option),
659 warn('invalid "%s" annotation option "%s" value "%s", must be an integer' %
660 (ann_name, option, value),
662 elif option == OPT_ARRAY_LENGTH:
664 warn('"%s" annotation option "length" needs a value' % (ann_name, ),
667 warn('invalid "%s" annotation option: "%s"' % (ann_name, option),
670 def _do_validate_attributes(self, position, ann_name, options):
672 Validate the ``(attributes)`` annotation.
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
680 # The 'attributes' annotation allows free form annotations.
683 def _do_validate_closure(self, position, ann_name, options):
685 Validate the ``(closure)`` annotation.
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
693 self._validate_annotation(position, ann_name, options, max_n_options=1)
695 def _do_validate_constructor(self, position, ann_name, options):
697 Validate the ``(constructor)`` annotation.
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
705 self._validate_annotation(position, ann_name, options, exact_n_options=0)
707 def _do_validate_destroy(self, position, ann_name, options):
709 Validate the ``(destroy)`` annotation.
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
717 self._validate_annotation(position, ann_name, options, exact_n_options=1)
719 def _do_validate_element_type(self, position, ann_name, options):
721 Validate the ``(element)`` annotation.
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
729 self._validate_annotation(position, ann_name, options, min_n_options=1, max_n_options=2)
731 def _do_validate_foreign(self, position, ann_name, options):
733 Validate the ``(foreign)`` annotation.
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
741 self._validate_annotation(position, ann_name, options, exact_n_options=0)
743 def _do_validate_get_value_func(self, position, ann_name, options):
745 Validate the ``(value-func)`` annotation.
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
753 self._validate_annotation(position, ann_name, options, exact_n_options=1)
755 def _do_validate_in(self, position, ann_name, options):
757 Validate the ``(in)`` annotation.
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
765 self._validate_annotation(position, ann_name, options, exact_n_options=0)
767 def _do_validate_inout(self, position, ann_name, options):
769 Validate the ``(in-out)`` annotation.
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
777 self._validate_annotation(position, ann_name, options, exact_n_options=0)
779 def _do_validate_method(self, position, ann_name, options):
781 Validate the ``(method)`` annotation.
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
789 self._validate_annotation(position, ann_name, options, exact_n_options=0)
791 def _do_validate_out(self, position, ann_name, options):
793 Validate the ``(out)`` annotation.
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
801 self._validate_annotation(position, ann_name, options, max_n_options=1,
804 def _do_validate_ref_func(self, position, ann_name, options):
806 Validate the ``(ref-func)`` annotation.
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
814 self._validate_annotation(position, ann_name, options, exact_n_options=1)
816 def _do_validate_rename_to(self, position, ann_name, options):
818 Validate the ``(rename-to)`` annotation.
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
826 self._validate_annotation(position, ann_name, options, exact_n_options=1)
828 def _do_validate_scope(self, position, ann_name, options):
830 Validate the ``(scope)`` annotation.
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
838 self._validate_annotation(position, ann_name, options, exact_n_options=1,
839 choices=SCOPE_OPTIONS)
841 def _do_validate_set_value_func(self, position, ann_name, options):
843 Validate the ``(value-func)`` annotation.
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
851 self._validate_annotation(position, ann_name, options, exact_n_options=1)
853 def _do_validate_skip(self, position, ann_name, options):
855 Validate the ``(skip)`` annotation.
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
863 self._validate_annotation(position, ann_name, options, exact_n_options=0)
865 def _do_validate_transfer(self, position, ann_name, options):
867 Validate the ``(transfer)`` annotation.
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
875 self._validate_annotation(position, ann_name, options, exact_n_options=1,
876 choices=TRANSFER_OPTIONS)
878 def _do_validate_type(self, position, ann_name, options):
880 Validate the ``(type)`` annotation.
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
888 self._validate_annotation(position, ann_name, options, exact_n_options=1)
890 def _do_validate_unref_func(self, position, ann_name, options):
892 Validate the ``(unref-func)`` annotation.
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
900 self._validate_annotation(position, ann_name, options, exact_n_options=1)
902 def _do_validate_value(self, position, ann_name, options):
904 Validate the ``(value)`` annotation.
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
912 self._validate_annotation(position, ann_name, options, exact_n_options=1)
914 def _do_validate_virtual(self, position, ann_name, options):
916 Validate the ``(virtual)`` annotation.
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
924 self._validate_annotation(position, ann_name, options, exact_n_options=1)
927 class GtkDocParameter(GtkDocAnnotatable):
929 Represents a GTK-Doc parameter part.
932 __slots__ = ('name', 'description')
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)
938 def __init__(self, name, position=None):
939 GtkDocAnnotatable.__init__(self, position)
944 #: Parameter description or :const:`None`.
945 self.description = None
948 return '<GtkDocParameter %r %r>' % (self.name, self.annotations)
951 class GtkDocTag(GtkDocAnnotatable):
953 Represents a GTK-Doc tag part.
956 __slots__ = ('name', 'value', 'description')
958 valid_annotations = (ANN_ALLOW_NONE, ANN_ARRAY, ANN_ATTRIBUTES, ANN_ELEMENT_TYPE, ANN_SKIP,
959 ANN_TRANSFER, ANN_TYPE)
961 def __init__(self, name, position=None):
962 GtkDocAnnotatable.__init__(self, position)
967 #: Tag value or :const:`None`.
970 #: Tag description or :const:`None`.
971 self.description = None
974 return '<GtkDocTag %r %r>' % (self.name, self.annotations)
977 class GtkDocCommentBlock(GtkDocAnnotatable):
979 Represents a GTK-Doc comment block.
982 __slots__ = ('code_before', 'code_after', 'indentation',
983 'name', 'params', 'description', 'tags')
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)
990 def __init__(self, name, position=None):
991 GtkDocAnnotatable.__init__(self, position)
993 #: Code preceding the GTK-Doc comment block start token ("``/**``"), if any.
994 self.code_before = None
996 #: Code following the GTK-Doc comment block end token ("``*/``"), if any.
997 self.code_after = None
999 #: List of indentation levels (preceding the "``*``") for all lines in the comment
1000 #: block's source text.
1001 self.indentation = []
1006 #: Ordered dictionary mapping parameter names to :class:`GtkDocParameter` instances
1007 #: applied to this :class:`GtkDocCommentBlock`.
1008 self.params = OrderedDict()
1010 #: The GTK-Doc comment block description part.
1011 self.description = None
1013 #: Ordered dictionary mapping tag names to :class:`GtkDocTag` instances
1014 #: applied to this :class:`GtkDocCommentBlock`.
1015 self.tags = OrderedDict()
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
1021 return cmp(self.name, other.name)
1024 return '<GtkDocCommentBlock %r %r>' % (self.name, self.annotations)
1028 Validate annotations applied to the :class:`GtkDocCommentBlock` identifier, parameters
1031 GtkDocAnnotatable.validate(self)
1033 for param in self.params.values():
1036 for tag in self.tags.values():
1040 #: Result object returned by :class:`GtkDocCommentBlockParser`._parse_annotations()
1041 _ParseAnnotationsResult = namedtuple('Result', ['success', 'annotations', 'start_pos', 'end_pos'])
1043 #: Result object returned by :class:`GtkDocCommentBlockParser`._parse_fields()
1044 _ParseFieldsResult = namedtuple('Result', ['success', 'annotations', 'description'])
1047 class GtkDocCommentBlockParser(object):
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:
1054 * warning messages at the slightest indication of recoverable malformed input and
1055 * error messages for unrecoverable malformed input
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:
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
1067 .. NOTE:: :class:`GtkDocCommentBlockParser` functionality is heavily based on gtkdoc-mkdb's
1068 `ScanSourceFile()`_ function and is currently in sync with GTK-Doc
1071 .. _ScanSourceFile():
1072 http://git.gnome.org/browse/gtk-doc/tree/gtkdoc-mkdb.in#n3722
1074 https://git.gnome.org/browse/gtk-doc/commit/?id=47abcd53b8489ebceec9e394676512a181c1f1f6
1077 def parse_comment_blocks(self, comments):
1079 Parse multiple GTK-Doc comment blocks.
1081 :param comments: an iterable of ``(comment, filename, lineno)`` tuples
1082 :returns: a dictionary mapping identifier names to :class:`GtkDocCommentBlock` objects
1087 for (comment, filename, lineno) in comments:
1089 comment_block = self.parse_comment_block(comment, filename, lineno)
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))
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)
1109 comment_blocks[comment_block.name] = comment_block
1111 return comment_blocks
1113 def parse_comment_block(self, comment, filename, lineno):
1115 Parse a single GTK-Doc comment block.
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``
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)
1130 # Check for the start of the comment block.
1131 result = COMMENT_BLOCK_START_RE.match(comment_lines[0])
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),
1142 code_before = result.group('code')
1143 comment = result.group('comment')
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),
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),
1159 comment_lines[0] = comment
1161 del comment_lines[0]
1163 # Not a GTK-Doc comment block.
1166 # Check for the end of the comment block.
1167 result = COMMENT_BLOCK_END_RE.match(comment_lines[-1])
1169 code_after = result.group('code')
1170 comment = result.group('comment')
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),
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),
1185 comment_lines[-1] = comment
1187 del comment_lines[-1]
1189 # Not a GTK-Doc comment block.
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
1201 returns_seen = False
1203 for line in comment_lines:
1205 position = Position(filename, lineno)
1207 # Store the original line (without \n) and column offset
1208 # so we can generate meaningful warnings later on.
1209 original_line = line
1212 # Store indentation level of the comment (before the ' * ')
1213 result = INDENTATION_RE.match(line)
1214 block_indent.append(result.group('indentation'))
1216 # Get rid of the ' * ' at the start of the line.
1217 result = COMMENT_ASTERISK_RE.match(line)
1219 comment = result.group('comment')
1221 marker = ' ' * result.start('comment') + '^'
1222 error('invalid comment text:\n%s\n%s' %
1223 (original_line, marker),
1226 column_offset = result.end(0)
1227 line = line[result.end(0):]
1229 # Store indentation level of the line (after the ' * ').
1230 result = INDENTATION_RE.match(line)
1231 line_indent = len(result.group('indentation').replace('\t', ' '))
1233 ####################################################################
1234 # Check for GTK-Doc comment block identifier.
1235 ####################################################################
1236 if comment_block is None:
1237 result = SECTION_RE.match(line)
1240 identifier_name = 'SECTION:%s' % (result.group('section_name'), )
1241 identifier_delimiter = None
1242 identifier_fields = None
1243 identifier_fields_start = None
1245 result = PROPERTY_RE.match(line)
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')
1254 result = SIGNAL_RE.match(line)
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')
1263 result = SYMBOL_RE.match(line)
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')
1272 in_part = PART_IDENTIFIER
1273 part_indent = line_indent
1275 comment_block = GtkDocCommentBlock(identifier_name, comment_block_pos)
1276 comment_block.code_before = code_before
1277 comment_block.code_after = code_after
1279 if identifier_fields:
1280 res = self._parse_annotations(position,
1281 column_offset + identifier_fields_start,
1286 if identifier_fields[res.end_pos:].strip():
1287 # Not an identifier due to invalid trailing description field
1291 comment_block = None
1293 comment_block.annotations = res.annotations
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),
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),
1312 ####################################################################
1313 # Check for comment block parameters.
1314 ####################################################################
1315 result = PARAMETER_RE.match(line)
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) + '^'
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),
1329 in_part = PART_PARAMETERS
1331 if param_name_lower == TAG_RETURNS:
1332 # Deprecated return value as parameter instead of tag
1333 param_name = TAG_RETURNS
1335 if not returns_seen:
1338 error('encountered multiple "Returns" parameters or tags for "%s".' %
1339 (comment_block.name, ),
1342 tag = GtkDocTag(TAG_RETURNS, position)
1345 result = self._parse_fields(position,
1346 column_offset + param_fields_start,
1350 tag.annotations = result.annotations
1351 tag.description = result.description
1352 comment_block.tags[TAG_RETURNS] = tag
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),
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),
1368 parameter = GtkDocParameter(param_name, position)
1371 result = self._parse_fields(position,
1372 column_offset + param_fields_start,
1376 parameter.annotations = result.annotations
1377 parameter.description = result.description
1379 comment_block.params[param_name] = parameter
1380 current_part = parameter
1383 ####################################################################
1384 # Check for comment block description.
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.
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
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) + '^'
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),
1420 # Translate deprecated tag name into corresponding annotation name
1421 ann_name = tag_name_lower.replace(' ', '-')
1423 if tag_name_lower == TAG_ATTRIBUTES:
1425 result = self._parse_fields(position,
1426 result.start('tag_name') + column_offset,
1433 for annotation in result.annotations:
1434 ann_options = self._parse_annotation_options_list(position, marker,
1436 n_options = len(ann_options)
1438 transformed = '%s %s' % (transformed, ann_options[0], )
1439 elif n_options == 2:
1440 transformed = '%s %s=%s' % (transformed, ann_options[0],
1443 # Malformed Attributes: tag
1444 error('malformed "Attributes:" tag will be ignored:\n%s\n%s' %
1445 (original_line, marker),
1450 transformed = '%s %s' % (ann_name, transformed.strip())
1451 ann_name, docannotation = self._parse_annotation(
1453 column_offset + tag_fields_start,
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),
1462 comment_block.annotations[ann_name] = docannotation
1464 ann_name, options = self._parse_annotation(position,
1465 column_offset + tag_fields_start,
1467 '%s %s' % (ann_name, tag_fields))
1468 comment_block.annotations[ann_name] = options
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),
1477 in_part = PART_DESCRIPTION
1479 if comment_block.description is None:
1480 comment_block.description = tag_fields
1482 comment_block.description += '\n%s' % (tag_fields, )
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)):
1492 if in_part != PART_TAGS:
1494 warn('"%s:" tag unexpected at this location:\n%s\n%s' %
1495 (tag_name, original_line, marker),
1498 if tag_name_lower in [TAG_RETURN, TAG_RETURNS,
1499 TAG_RETURN_VALUE, TAG_RETURNS_VALUE]:
1500 if not returns_seen:
1503 error('encountered multiple return value parameters or tags for "%s".' %
1504 (comment_block.name, ),
1507 tag = GtkDocTag(TAG_RETURNS, position)
1510 result = self._parse_fields(position,
1511 column_offset + tag_fields_start,
1515 tag.annotations = result.annotations
1516 tag.description = result.description
1518 comment_block.tags[TAG_RETURNS] = tag
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),
1527 tag = GtkDocTag(tag_name_lower, position)
1530 result = self._parse_fields(position,
1531 column_offset + tag_fields_start,
1535 if result.annotations:
1536 error('annotations not supported for tag "%s:".' % (tag_name, ),
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')
1548 comment_block.tags[tag_name_lower] = tag
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()
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
1567 comment_block.description += '\n' + line
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
1576 current_part.description += '\n' + line
1579 ########################################################################
1580 # Finished parsing this comment block.
1581 ########################################################################
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()
1588 for tag in comment_block.tags.values():
1589 self._clean_description_field(tag)
1591 for param in comment_block.params.values():
1592 self._clean_description_field(param)
1594 comment_block.indentation = block_indent
1595 comment_block.validate()
1596 return comment_block
1600 def _clean_description_field(self, part):
1602 Remove extraneous leading and trailing whitespace from description fields.
1604 :param part: a GTK-Doc comment block part having a description field
1607 if part.description:
1608 if part.description.strip() == '':
1609 part.description = None
1611 if EMPTY_LINE_RE.match(part.description.split('\n', 1)[0]):
1612 part.description = part.description.rstrip()
1614 part.description = part.description.strip()
1616 def _validate_multiline_annotation_continuation(self, line, original_line,
1617 column_offset, position):
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
1625 * @array_: (out caller-allocates) (array)
1626 * (element-type utf8) (transfer full): A beautiful array
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 ("``*/``")
1633 :param position: :class:`giscanner.message.Position` of `line` in the source file
1636 result = self._parse_annotations(position, column_offset, original_line, line)
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),
1644 def _parse_annotation_options_list(self, position, column, line, options):
1646 Parse annotation options into a list. For example::
1648 ┌──────────────────────────────────────────────────────────────┐
1649 │ 'option1 option2 option3' │ ─▷ source
1650 ├──────────────────────────────────────────────────────────────┤
1651 │ ['option1', 'option2', 'option3'] │ ◁─ parsed options
1652 └──────────────────────────────────────────────────────────────┘
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
1664 result = options.find('=')
1666 marker = ' ' * (column + result) + '^'
1667 warn('invalid annotation options: expected a "list" but '
1668 'received "key=value pairs":\n%s\n%s' % (line, marker),
1670 parsed = self._parse_annotation_options_unknown(position, column, line, options)
1672 parsed = options.split(' ')
1676 def _parse_annotation_options_dict(self, position, column, line, options):
1678 Parse annotation options into a dict. For example::
1680 ┌──────────────────────────────────────────────────────────────┐
1681 │ 'option1=value1 option2 option3=value2' │ ─▷ source
1682 ├──────────────────────────────────────────────────────────────┤
1683 │ {'option1': 'value1', 'option2': None, 'option3': 'value2'} │ ◁─ parsed options
1684 └──────────────────────────────────────────────────────────────┘
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
1693 parsed = OrderedDict()
1696 for p in options.split(' '):
1697 parts = p.split('=', 1)
1699 value = parts[1] if len(parts) == 2 else None
1704 def _parse_annotation_options_unknown(self, position, column, line, options):
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::
1709 ┌──────────────────────────────────────────────────────────────┐
1710 │ ' option1 option2 option3=value1 ' │ ─▷ source
1711 ├──────────────────────────────────────────────────────────────┤
1712 │ ['option1 option2 option3=value1'] │ ◁─ parsed options
1713 └──────────────────────────────────────────────────────────────┘
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
1723 return [options.strip()]
1725 def _parse_annotation(self, position, column, line, annotation):
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::
1730 ┌──────────────────────────────────────────────────────────────┐
1731 │ 'name opt1=value1 opt2=value2 opt3' │ ─▷ source
1732 ├──────────────────────────────────────────────────────────────┤
1733 │ 'name', {'opt1': 'value1', 'opt2':'value2', 'opt3':None} │ ◁─ parsed annotation
1734 └──────────────────────────────────────────────────────────────┘
1736 ┌──────────────────────────────────────────────────────────────┐
1737 │ 'name opt1 opt2' │ ─▷ source
1738 ├──────────────────────────────────────────────────────────────┤
1739 │ 'name', ['opt1', 'opt2'] │ ◁─ parsed annotation
1740 └──────────────────────────────────────────────────────────────┘
1742 ┌──────────────────────────────────────────────────────────────┐
1743 │ 'unkownname unknown list of options' │ ─▷ source
1744 ├──────────────────────────────────────────────────────────────┤
1745 │ 'unkownname', ['unknown list of options'] │ ◁─ parsed annotation
1746 └──────────────────────────────────────────────────────────────┘
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
1755 # Transform deprecated type syntax "tokens"
1756 annotation = annotation.replace('<', ANN_LPAR).replace('>', ANN_RPAR)
1758 parts = annotation.split(' ', 1)
1759 ann_name = parts[0].lower()
1760 ann_options = parts[1] if len(parts) == 2 else None
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),
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),
1775 ann_name = ANN_ATTRIBUTES
1776 ann_options = self._parse_annotation_options_list(position, column, line, ann_options)
1777 n_options = len(ann_options)
1779 ann_options = ann_options[0]
1780 elif n_options == 2:
1781 ann_options = '%s=%s' % (ann_options[0], ann_options[1])
1783 marker = ' ' * (column) + '^'
1784 error('malformed "(attribute)" annotation will be ignored:\n%s\n%s' %
1789 column += len(ann_name) + 2
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)
1796 ann_options = self._parse_annotation_options_unknown(position, column, line,
1799 return ann_name, ann_options
1801 def _parse_annotations(self, position, column, line, fields, parse_options=True):
1803 Parse annotations into a :class:`GtkDocAnnotations` object.
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,
1817 parsed_annotations = GtkDocAnnotations(position)
1819 parsed_annotations = []
1828 for i, cur_char in enumerate(fields):
1829 cur_char_is_space = cur_char.isspace()
1831 if cur_char == ANN_LPAR:
1834 if parens_level == 1:
1837 if prev_char == ANN_LPAR:
1838 marker = ' ' * (column + i) + '^'
1839 error('unexpected parentheses, annotations will be ignored:\n%s\n%s' %
1842 return _ParseAnnotationsResult(False, None, None, None)
1843 elif parens_level > 1:
1844 char_buffer.append(cur_char)
1845 elif cur_char == ANN_RPAR:
1848 if prev_char == ANN_LPAR:
1849 marker = ' ' * (column + i) + '^'
1850 error('unexpected parentheses, annotations will be ignored:\n%s\n%s' %
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' %
1859 return _ParseAnnotationsResult(False, None, None, None)
1860 elif parens_level == 0:
1863 if parse_options is True:
1864 name, options = self._parse_annotation(position,
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
1875 parsed_annotations.append(''.join(char_buffer).strip())
1879 char_buffer.append(cur_char)
1880 elif cur_char_is_space:
1881 if parens_level > 0:
1882 char_buffer.append(cur_char)
1884 if parens_level == 0:
1887 char_buffer.append(cur_char)
1889 prev_char = cur_char
1891 if parens_level > 0:
1892 marker = ' ' * (column + i) + '^'
1893 error('unbalanced parentheses, annotations will be ignored:\n%s\n%s' %
1896 return _ParseAnnotationsResult(False, None, None, None)
1898 return _ParseAnnotationsResult(True, parsed_annotations, start_pos, end_pos)
1900 def _parse_fields(self, position, column, line, fields, parse_options=True,
1901 validate_description_field=True):
1903 Parse annotations out of field data. For example::
1905 ┌──────────────────────────────────────────────────────────────┐
1906 │ '(skip): description of some parameter │ ─▷ source
1907 ├──────────────────────────────────────────────────────────────┤
1908 │ ({'skip': []}, 'description of some parameter') │ ◁─ annotations and
1909 └──────────────────────────────────────────────────────────────┘ remaining fields
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
1922 description_field = ''
1923 result = self._parse_annotations(position, column, line, fields, parse_options)
1925 description_field = fields[result.end_pos:].strip()
1927 if description_field and validate_description_field:
1928 if description_field.startswith(':'):
1929 description_field = description_field[1:]
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),
1938 return _ParseFieldsResult(result.success, result.annotations, description_field)
1941 class GtkDocCommentBlockWriter(object):
1943 Serialized :class:`GtkDocCommentBlock` objects into GTK-Doc comment blocks.
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
1951 def _serialize_annotations(self, annotations):
1953 Serialize an annotation field. For example::
1955 ┌──────────────────────────────────────────────────────────────┐
1956 │ {'name': {'opt1': 'value1', 'opt2':'value2', 'opt3':None} │ ◁─ GtkDocAnnotations
1957 ├──────────────────────────────────────────────────────────────┤
1958 │ '(name opt1=value1 opt2=value2 opt3)' │ ─▷ serialized
1959 └──────────────────────────────────────────────────────────────┘
1961 ┌──────────────────────────────────────────────────────────────┐
1962 │ {'name': ['opt1', 'opt2']} │ ◁─ GtkDocAnnotations
1963 ├──────────────────────────────────────────────────────────────┤
1964 │ '(name opt1 opt2)' │ ─▷ serialized
1965 └──────────────────────────────────────────────────────────────┘
1967 ┌──────────────────────────────────────────────────────────────┐
1968 │ {'unkownname': ['unknown list of options']} │ ◁─ GtkDocAnnotations
1969 ├──────────────────────────────────────────────────────────────┤
1970 │ '(unkownname unknown list of options)' │ ─▷ serialized
1971 └──────────────────────────────────────────────────────────────┘
1973 :param annotations: :class:`GtkDocAnnotations` to be serialized
1979 for ann_name, options in annotations.items():
1981 if isinstance(options, list):
1982 serialize_options = ' '.join(options)
1984 serialize_options = ''
1986 for key, value in options.items():
1988 serialize_options += '%s=%s ' % (key, value)
1990 serialize_options += '%s ' % (key, )
1992 serialize_options = serialize_options.strip()
1994 serialized.append('(%s %s)' % (ann_name, serialize_options))
1996 serialized.append('(%s)' % (ann_name, ))
1998 return ' '.join(serialized)
2000 def _serialize_parameter(self, parameter):
2002 Serialize a parameter.
2004 :param parameter: :class:`GtkDocParameter` to be serialized
2008 # parameter_name field
2009 serialized = '@%s' % (parameter.name, )
2012 if parameter.annotations:
2013 serialized += ': ' + self._serialize_annotations(parameter.annotations)
2016 if parameter.description:
2017 if parameter.description.startswith('\n'):
2018 serialized += ':' + parameter.description
2020 serialized += ': ' + parameter.description
2024 return serialized.split('\n')
2026 def _serialize_tag(self, tag):
2030 :param tag: :class:`GtkDocTag` to be serialized
2035 serialized = tag.name.capitalize()
2039 serialized += ': ' + self._serialize_annotations(tag.annotations)
2043 serialized += ': ' + tag.value
2047 if tag.description.startswith('\n'):
2048 serialized += ':' + tag.description
2050 serialized += ': ' + tag.description
2052 if not tag.value and not tag.description:
2055 return serialized.split('\n')
2057 def write(self, block):
2059 Serialize a :class:`GtkDocCommentBlock` object.
2061 :param block: :class:`GtkDocCommentBlock` to be serialized
2071 if block.name.startswith('SECTION'):
2072 lines.append(block.name)
2074 if block.annotations:
2075 annotations = self._serialize_annotations(block.annotations)
2076 lines.append('%s: %s' % (block.name, annotations))
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, ))
2083 for param in block.params.values():
2084 lines.extend(self._serialize_parameter(param))
2086 # Comment block description part
2087 if block.description:
2089 for l in block.description.split('\n'):
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.
2097 for tag in block.tags.values():
2098 lines.extend(self._serialize_tag(tag))
2100 # Restore comment block indentation and *
2102 indent = Counter(block.indentation).most_common(1)[0][0] or ' '
2103 if indent.endswith('\t'):
2104 start_indent = indent
2105 line_indent = indent + ' '
2107 start_indent = indent[:-1]
2108 line_indent = indent
2114 while i < len(lines):
2117 lines[i] = '%s* %s\n' % (line_indent, line)
2119 lines[i] = '%s*\n' % (line_indent, )
2122 # Restore comment block start and end tokens
2123 lines.insert(0, '%s/**\n' % (start_indent, ))
2124 lines.append('%s*/\n' % (line_indent, ))
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, ))
2130 if block.code_after:
2131 lines.append('%s\n' % (block.code_after, ))
2133 return ''.join(lines)