annotationparser.py 99 KB

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