gobject-introspection 2.2.0-x86-mingw32 → 2.2.1-x86-mingw32

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