Revert "help/manual: install gtk-doc html version of manual"
[gtk-doc.git] / gtkdoc / mkdb.py
blob37a69315e38cd21840160348a24b5b2d658688e1
1 # -*- python; coding: utf-8 -*-
3 # gtk-doc - GTK DocBook documentation generator.
4 # Copyright (C) 1998 Damon Chaplin
5 # 2007-2016 Stefan Sauer
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
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.
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 02110-1301, USA.
22 """
23 Creates the DocBook files from the source comments.
24 """
26 from collections import OrderedDict
27 import logging
28 import os
29 import re
30 import string
32 from . import common, md_to_db
34 # Options
35 MODULE = None
36 DB_OUTPUT_DIR = None
37 INLINE_MARKUP_MODE = None
38 DEFAULT_STABILITY = None
39 NAME_SPACE = ''
40 ROOT_DIR = '.'
42 # These global arrays store information on signals. Each signal has an entry
43 # in each of these arrays at the same index, like a multi-dimensional array.
44 SignalObjects = [] # The GtkObject which emits the signal.
45 SignalNames = [] # The signal name.
46 SignalReturns = [] # The return type.
47 SignalFlags = [] # Flags for the signal
48 SignalPrototypes = [] # The rest of the prototype of the signal handler.
50 # These global arrays store information on Args. Each Arg has an entry
51 # in each of these arrays at the same index, like a multi-dimensional array.
52 ArgObjects = [] # The GtkObject which has the Arg.
53 ArgNames = [] # The Arg name.
54 ArgTypes = [] # The Arg type - gint, GtkArrowType etc.
55 ArgFlags = [] # How the Arg can be used - readable/writable etc.
56 ArgNicks = [] # The nickname of the Arg.
57 ArgBlurbs = [] # Docstring of the Arg.
58 ArgDefaults = [] # Default value of the Arg.
59 ArgRanges = [] # The range of the Arg type
61 # These global hashes store declaration info keyed on a symbol name.
62 Declarations = {}
63 DeclarationTypes = {}
64 DeclarationConditional = {}
65 DeclarationOutput = {}
66 Deprecated = {}
67 Since = {}
68 StabilityLevel = {}
69 StructHasTypedef = {}
71 # These global hashes store the existing documentation.
72 SymbolDocs = {}
73 SymbolParams = {}
74 SymbolAnnotations = {}
76 # These global hashes store documentation scanned from the source files.
77 SourceSymbolDocs = {}
78 SourceSymbolParams = {}
79 SourceSymbolSourceFile = {}
80 SourceSymbolSourceLine = {}
82 # all documentation goes in here, so we can do coverage analysis
83 AllSymbols = {}
84 AllIncompleteSymbols = {}
85 AllUnusedSymbols = {}
86 AllDocumentedSymbols = {}
88 # Undeclared yet documented symbols
89 UndeclaredSymbols = {}
91 # These global arrays store GObject, subclasses and the hierarchy (also of
92 # non-object derived types).
93 Objects = []
94 ObjectLevels = []
95 ObjectRoots = {}
97 Interfaces = {}
98 Prerequisites = {}
100 # holds the symbols which are mentioned in <MODULE>-sections.txt and in which
101 # section they are defined
102 KnownSymbols = {}
103 SymbolSection = {}
104 SymbolSectionId = {}
106 # collects index entries
107 IndexEntriesFull = {}
108 IndexEntriesSince = {}
109 IndexEntriesDeprecated = {}
111 # Standard C preprocessor directives, which we ignore for '#' abbreviations.
112 PreProcessorDirectives = {
113 'assert', 'define', 'elif', 'else', 'endif', 'error', 'if', 'ifdef', 'ifndef',
114 'include', 'line', 'pragma', 'unassert', 'undef', 'warning'
117 # remember used annotation (to write minimal glossary)
118 AnnotationsUsed = {}
120 # the regexp that parses the annotation is in ScanSourceFile()
121 AnnotationDefinition = {
122 # the GObjectIntrospection annotations are defined at:
123 # https://live.gnome.org/GObjectIntrospection/Annotations
124 'allow-none': "NULL is OK, both for passing and for returning.",
125 'nullable': "NULL may be passed as the value in, out, in-out; or as a return value.",
126 'not nullable': "NULL must not be passed as the value in, out, in-out; or as a return value.",
127 'optional': "NULL may be passed instead of a pointer to a location.",
128 'not optional': "NULL must not be passed as the pointer to a location.",
129 'array': "Parameter points to an array of items.",
130 'attribute': "Deprecated free-form custom annotation, replaced by (attributes) annotation.",
131 'attributes': "Free-form key-value pairs.",
132 'closure': "This parameter is a 'user_data', for callbacks; many bindings can pass NULL here.",
133 'constructor': "This symbol is a constructor, not a static method.",
134 'destroy': "This parameter is a 'destroy_data', for callbacks.",
135 'default': "Default parameter value (for in case the <acronym>shadows</acronym>-to function has less parameters).",
136 'element-type': "Generics and defining elements of containers and arrays.",
137 'error-domains': "Typed errors. Similar to throws in Java.",
138 'foreign': "This is a foreign struct.",
139 'get-value-func': "The specified function is used to convert a struct from a GValue, must be a GTypeInstance.",
140 'in': "Parameter for input. Default is <acronym>transfer none</acronym>.",
141 'inout': "Parameter for input and for returning results. Default is <acronym>transfer full</acronym>.",
142 'in-out': "Parameter for input and for returning results. Default is <acronym>transfer full</acronym>.",
143 'method': "This is a method",
144 'not-error': "A GError parameter is not to be handled like a normal GError.",
145 'out': "Parameter for returning results. Default is <acronym>transfer full</acronym>.",
146 'out caller-allocates': "Out parameter, where caller must allocate storage.",
147 'out callee-allocates': "Out parameter, where caller must allocate storage.",
148 'ref-func': "The specified function is used to ref a struct, must be a GTypeInstance.",
149 'rename-to': "Rename the original symbol's name to SYMBOL.",
150 'scope call': "The callback is valid only during the call to the method.",
151 'scope async': "The callback is valid until first called.",
152 'scope notified': "The callback is valid until the GDestroyNotify argument is called.",
153 'set-value-func': "The specified function is used to convert from a struct to a GValue, must be a GTypeInstance.",
154 'skip': "Exposed in C code, not necessarily available in other languages.",
155 'transfer container': "Free data container after the code is done.",
156 'transfer floating': "Alias for <acronym>transfer none</acronym>, used for objects with floating refs.",
157 'transfer full': "Free data after the code is done.",
158 'transfer none': "Don't free data after the code is done.",
159 'type': "Override the parsed C type with given type.",
160 'unref-func': "The specified function is used to unref a struct, must be a GTypeInstance.",
161 'virtual': "This is the invoker for a virtual method.",
162 'value': "The specified value overrides the evaluated value of the constant.",
163 # Stability Level definition
164 # https://bugzilla.gnome.org/show_bug.cgi?id=170860
165 'Stable': '''The intention of a Stable interface is to enable arbitrary third parties to
166 develop applications to these interfaces, release them, and have confidence that
167 they will run on all minor releases of the product (after the one in which the
168 interface was introduced, and within the same major release). Even at a major
169 release, incompatible changes are expected to be rare, and to have strong
170 justifications.
171 ''',
172 'Unstable': '''Unstable interfaces are experimental or transitional. They are typically used to
173 give outside developers early access to new or rapidly changing technology, or
174 to provide an interim solution to a problem where a more general solution is
175 anticipated. No claims are made about either source or binary compatibility from
176 one minor release to the next.
178 The Unstable interface level is a warning that these interfaces are subject to
179 change without warning and should not be used in unbundled products.
181 Given such caveats, customer impact need not be a factor when considering
182 incompatible changes to an Unstable interface in a major or minor release.
183 Nonetheless, when such changes are introduced, the changes should still be
184 mentioned in the release notes for the affected release.
185 ''',
186 'Private': '''An interface that can be used within the GNOME stack itself, but that is not
187 documented for end-users. Such functions should only be used in specified and
188 documented ways.
189 ''',
192 # Function and other declaration output settings.
193 RETURN_TYPE_FIELD_WIDTH = 20
194 MAX_SYMBOL_FIELD_WIDTH = 40
196 # XML header
197 doctype_header = None
199 # refentry template
200 REFENTRY = string.Template('''${header}
201 <refentry id="${section_id}">
202 <refmeta>
203 <refentrytitle role="top_of_page" id="${section_id}.top_of_page">${title}</refentrytitle>
204 <manvolnum>3</manvolnum>
205 <refmiscinfo>${MODULE} Library${image}</refmiscinfo>
206 </refmeta>
207 <refnamediv>
208 <refname>${title}</refname>
209 <refpurpose>${short_desc}</refpurpose>
210 </refnamediv>
211 ${stability}
212 ${functions_synop}${args_synop}${signals_synop}${object_anchors}${other_synop}${hierarchy}${prerequisites}${derived}${interfaces}${implementations}
213 ${include_output}
214 <refsect1 id="${section_id}.description" role="desc">
215 <title role="desc.title">Description</title>
216 ${extralinks}${long_desc}
217 </refsect1>
218 <refsect1 id="${section_id}.functions_details" role="details">
219 <title role="details.title">Functions</title>
220 ${functions_details}
221 </refsect1>
222 <refsect1 id="${section_id}.other_details" role="details">
223 <title role="details.title">Types and Values</title>
224 ${other_details}
225 </refsect1>
226 ${args_desc}${signals_desc}${see_also}
227 </refentry>
228 ''')
231 def Run(options):
232 global MODULE, INLINE_MARKUP_MODE, DEFAULT_STABILITY, NAME_SPACE, \
233 DB_OUTPUT_DIR, doctype_header
235 logging.info('options: %s', str(options.__dict__))
237 # We should pass the options variable around instead of this global variable horror
238 # but too much of the code expects these to be around. Fix this once the transition is done.
239 MODULE = options.module
240 INLINE_MARKUP_MODE = options.xml_mode or options.sgml_mode
241 DEFAULT_STABILITY = options.default_stability
242 NAME_SPACE = options.name_space
244 main_sgml_file = options.main_sgml_file
245 if not main_sgml_file:
246 # backwards compatibility
247 if os.path.exists(MODULE + "-docs.sgml"):
248 main_sgml_file = MODULE + "-docs.sgml"
249 else:
250 main_sgml_file = MODULE + "-docs.xml"
252 # extract docbook header or define default
253 doctype_header = GetDocbookHeader(main_sgml_file)
255 # This is where we put all the DocBook output.
256 DB_OUTPUT_DIR = DB_OUTPUT_DIR if DB_OUTPUT_DIR else os.path.join(ROOT_DIR, "xml")
257 if not os.path.isdir(DB_OUTPUT_DIR):
258 os.mkdir(DB_OUTPUT_DIR)
260 ReadKnownSymbols(os.path.join(ROOT_DIR, MODULE + "-sections.txt"))
261 ReadSignalsFile(os.path.join(ROOT_DIR, MODULE + ".signals"))
262 ReadArgsFile(os.path.join(ROOT_DIR, MODULE + ".args"))
263 ReadObjectHierarchy(os.path.join(ROOT_DIR, MODULE + ".hierarchy"))
264 ReadInterfaces(os.path.join(ROOT_DIR, MODULE + ".interfaces"))
265 ReadPrerequisites(os.path.join(ROOT_DIR, MODULE + ".prerequisites"))
267 ReadDeclarationsFile(os.path.join(ROOT_DIR, MODULE + "-decl.txt"), 0)
268 if os.path.isfile(os.path.join(ROOT_DIR, MODULE + "-overrides.txt")):
269 ReadDeclarationsFile(os.path.join(ROOT_DIR, MODULE + "-overrides.txt"), 1)
271 # Scan sources
272 if options.source_suffixes:
273 suffix_list = ['.' + ext for ext in options.source_suffixes.split(',')]
274 else:
275 suffix_list = ['.c', '.h']
277 source_dirs = options.source_dir
278 ignore_files = options.ignore_files
279 logging.info(" ignore files: " + ignore_files)
280 for sdir in source_dirs:
281 ReadSourceDocumentation(sdir, suffix_list, source_dirs, ignore_files)
283 changed, book_top, book_bottom = OutputDB(os.path.join(ROOT_DIR, MODULE + "-sections.txt"), options)
284 OutputBook(main_sgml_file, book_top, book_bottom)
286 logging.info("All files created: %d", changed)
288 # If any of the DocBook files have changed, update the timestamp file (so
289 # it can be used for Makefile dependencies).
290 if changed or not os.path.exists(os.path.join(ROOT_DIR, "sgml.stamp")):
292 # try to detect the common prefix
293 # GtkWidget, GTK_WIDGET, gtk_widget -> gtk
294 if NAME_SPACE == '':
295 NAME_SPACE = DetermineNamespace()
297 logging.info('namespace prefix ="%s"', NAME_SPACE)
299 OutputIndex("api-index-full", IndexEntriesFull)
300 OutputIndex("api-index-deprecated", IndexEntriesDeprecated)
301 OutputSinceIndexes()
302 OutputAnnotationGlossary()
304 with open(os.path.join(ROOT_DIR, 'sgml.stamp'), 'w') as h:
305 h.write('timestamp')
308 def OutputObjectList():
309 """This outputs the alphabetical list of objects, in a columned table."""
310 # FIXME: Currently this also outputs ancestor objects which may not actually
311 # be in this module.
312 cols = 3
314 # FIXME: use .xml
315 old_object_index = os.path.join(DB_OUTPUT_DIR, "object_index.sgml")
316 new_object_index = os.path.join(DB_OUTPUT_DIR, "object_index.new")
318 OUTPUT = open(new_object_index, 'w', encoding='utf-8')
320 OUTPUT.write('''%s
321 <informaltable pgwide="1" frame="none">
322 <tgroup cols="%s">
323 <colspec colwidth="1*"/>
324 <colspec colwidth="1*"/>
325 <colspec colwidth="1*"/>
326 <tbody>
327 ''' % (MakeDocHeader("informaltable"), cols))
329 count = 0
330 object = None
331 for object in sorted(Objects):
332 xref = MakeXRef(object)
333 if count % cols == 0:
334 OUTPUT.write("<row>\n")
335 OUTPUT.write("<entry>%s</entry>\n" % xref)
336 if count % cols == cols - 1:
337 OUTPUT.write("</row>\n")
338 count += 1
340 if count == 0:
341 # emit an empty row, since empty tables are invalid
342 OUTPUT.write("<row><entry> </entry></row>\n")
344 else:
345 if count % cols > 0:
346 OUTPUT.write("</row>\n")
348 OUTPUT.write('''</tbody></tgroup></informaltable>\n''')
349 OUTPUT.close()
351 common.UpdateFileIfChanged(old_object_index, new_object_index, 0)
354 def TrimTextBlock(desc):
355 """Trims extra whitespace.
357 Empty lines inside a block are preserved.
358 Args:
359 desc (str): the text block to trim. May contain newlines.
362 # strip trailing spaces on every line
363 return re.sub(r'\s+$', '\n', desc.lstrip(), flags=re.MULTILINE)
366 def OutputDB(file, options):
367 """Generate docbook files.
369 This collects the output for each section of the docs, and outputs each file
370 when the end of the section is found.
372 Args:
373 file (str): the $MODULE-sections.txt file which contains all of the
374 functions/macros/structs etc. being documented, organised
375 into sections and subsections.
376 options: commandline options
379 logging.info("Reading: %s", file)
380 INPUT = open(file, 'r', encoding='utf-8')
381 filename = ''
382 book_top = ''
383 book_bottom = ''
384 includes = options.default_includes or ''
385 section_includes = ''
386 in_section = 0
387 title = ''
388 section_id = ''
389 subsection = ''
390 num_symbols = 0
391 changed = 0
392 functions_synop = ''
393 other_synop = ''
394 functions_details = ''
395 other_details = ''
396 signals_synop = ''
397 signals_desc = ''
398 args_synop = ''
399 child_args_synop = ''
400 style_args_synop = ''
401 args_desc = ''
402 child_args_desc = ''
403 style_args_desc = ''
404 hierarchy_str = ''
405 hierarchy = []
406 interfaces = ''
407 implementations = ''
408 prerequisites = ''
409 derived = ''
410 file_objects = []
411 file_def_line = {}
412 symbol_def_line = {}
414 MergeSourceDocumentation()
416 line_number = 0
417 for line in INPUT:
418 line_number += 1
420 if line.startswith('#'):
421 continue
423 logging.info("section file data: %d: %s", line_number, line)
425 m1 = re.search(r'^<SUBSECTION\s*(.*)>', line, re.I)
426 m2 = re.search(r'^<TITLE>(.*)<\/TITLE', line)
427 m3 = re.search(r'^<FILE>(.*)<\/FILE>', line)
428 m4 = re.search(r'^<INCLUDE>(.*)<\/INCLUDE>', line)
429 m5 = re.search(r'^(\S+)', line)
431 if line.startswith('<SECTION>'):
432 num_symbols = 0
433 in_section = False
434 file_objects = []
435 symbol_def_line = {}
437 elif m1:
438 other_synop += "\n"
439 functions_synop += "\n"
440 subsection = m1.group(1)
442 elif line.startswith('<SUBSECTION>'):
443 continue
444 elif m2:
445 title = m2.group(1)
446 logging.info("Section: %s", title)
448 # We don't want warnings if object & class structs aren't used.
449 DeclarationOutput[title] = 1
450 DeclarationOutput["%sClass" % title] = 1
451 DeclarationOutput["%sIface" % title] = 1
452 DeclarationOutput["%sInterface" % title] = 1
454 elif m3:
455 filename = m3.group(1)
456 if filename not in file_def_line:
457 file_def_line[filename] = line_number
458 else:
459 common.LogWarning(file, line_number, "Double <FILE>%s</FILE> entry. Previous occurrence on line %s." %
460 (filename, file_def_line[filename]))
461 if title == '':
462 key = filename + ":Title"
463 if key in SourceSymbolDocs:
464 title = SourceSymbolDocs[key].rstrip()
466 elif m4:
467 if in_section:
468 section_includes = m4.group(1)
469 else:
470 if options.default_includes:
471 common.LogWarning(file, line_number, "Default <INCLUDE> being overridden by command line option.")
472 else:
473 includes = m4.group(1)
475 elif re.search(r'^<\/SECTION>', line):
476 logging.info("End of section: %s", title)
477 # TODO: also output if we have sections docs?
478 # long_desc = SymbolDocs.get(filename + ":Long_Description")
479 if num_symbols > 0:
480 # collect documents
481 book_bottom += " <xi:include href=\"xml/%s.xml\"/>\n" % filename
483 key = filename + ":Include"
484 if key in SourceSymbolDocs:
485 if section_includes:
486 common.LogWarning(file, line_number, "Section <INCLUDE> being overridden by inline comments.")
487 section_includes = SourceSymbolDocs[key]
489 if section_includes == '':
490 section_includes = includes
492 signals_synop = re.sub(r'^\n*', '', signals_synop)
493 signals_synop = re.sub(r'\n+$', '\n', signals_synop)
495 if signals_synop != '':
496 signals_synop = '''<refsect1 id="%s.signals" role="signal_proto">
497 <title role="signal_proto.title">Signals</title>
498 <informaltable frame="none">
499 <tgroup cols="3">
500 <colspec colname="signals_return" colwidth="150px"/>
501 <colspec colname="signals_name" colwidth="300px"/>
502 <colspec colname="signals_flags" colwidth="200px"/>
503 <tbody>
505 </tbody>
506 </tgroup>
507 </informaltable>
508 </refsect1>
509 ''' % (section_id, signals_synop)
510 signals_desc = TrimTextBlock(signals_desc)
511 signals_desc = '''<refsect1 id="%s.signal-details" role="signals">
512 <title role="signals.title">Signal Details</title>
514 </refsect1>
515 ''' % (section_id, signals_desc)
517 args_synop = re.sub(r'^\n*', '', args_synop)
518 args_synop = re.sub(r'\n+$', '\n', args_synop)
519 if args_synop != '':
520 args_synop = '''<refsect1 id="%s.properties" role="properties">
521 <title role="properties.title">Properties</title>
522 <informaltable frame="none">
523 <tgroup cols="3">
524 <colspec colname="properties_type" colwidth="150px"/>
525 <colspec colname="properties_name" colwidth="300px"/>
526 <colspec colname="properties_flags" colwidth="200px"/>
527 <tbody>
529 </tbody>
530 </tgroup>
531 </informaltable>
532 </refsect1>
533 ''' % (section_id, args_synop)
534 args_desc = TrimTextBlock(args_desc)
535 args_desc = '''<refsect1 id="%s.property-details" role="property_details">
536 <title role="property_details.title">Property Details</title>
538 </refsect1>
539 ''' % (section_id, args_desc)
541 child_args_synop = re.sub(r'^\n*', '', child_args_synop)
542 child_args_synop = re.sub(r'\n+$', '\n', child_args_synop)
543 if child_args_synop != '':
544 args_synop += '''<refsect1 id="%s.child-properties" role="child_properties">
545 <title role="child_properties.title">Child Properties</title>
546 <informaltable frame="none">
547 <tgroup cols="3">
548 <colspec colname="child_properties_type" colwidth="150px"/>
549 <colspec colname="child_properties_name" colwidth="300px"/>
550 <colspec colname="child_properties_flags" colwidth="200px"/>
551 <tbody>
553 </tbody>
554 </tgroup>
555 </informaltable>
556 </refsect1>
557 ''' % (section_id, child_args_synop)
558 child_args_desc = TrimTextBlock(child_args_desc)
559 args_desc += '''<refsect1 id="%s.child-property-details" role="child_property_details">
560 <title role="child_property_details.title">Child Property Details</title>
562 </refsect1>
563 ''' % (section_id, child_args_desc)
565 style_args_synop = re.sub(r'^\n*', '', style_args_synop)
566 style_args_synop = re.sub(r'\n+$', '\n', style_args_synop)
567 if style_args_synop != '':
568 args_synop += '''<refsect1 id="%s.style-properties" role="style_properties">
569 <title role="style_properties.title">Style Properties</title>
570 <informaltable frame="none">
571 <tgroup cols="3">
572 <colspec colname="style_properties_type" colwidth="150px"/>
573 <colspec colname="style_properties_name" colwidth="300px"/>
574 <colspec colname="style_properties_flags" colwidth="200px"/>
575 <tbody>
577 </tbody>
578 </tgroup>
579 </informaltable>
580 </refsect1>
581 ''' % (section_id, style_args_synop)
582 style_args_desc = TrimTextBlock(style_args_desc)
583 args_desc += '''<refsect1 id="%s.style-property-details" role="style_properties_details">
584 <title role="style_properties_details.title">Style Property Details</title>
586 </refsect1>
587 ''' % (section_id, style_args_desc)
589 hierarchy_str = AddTreeLineArt(hierarchy)
590 if hierarchy_str != '':
591 hierarchy_str = '''<refsect1 id="%s.object-hierarchy" role="object_hierarchy">
592 <title role="object_hierarchy.title">Object Hierarchy</title>
593 <screen>%s
594 </screen>
595 </refsect1>
596 ''' % (section_id, hierarchy_str)
598 interfaces = TrimTextBlock(interfaces)
599 if interfaces != '':
600 interfaces = '''<refsect1 id="%s.implemented-interfaces" role="impl_interfaces">
601 <title role="impl_interfaces.title">Implemented Interfaces</title>
603 </refsect1>
604 ''' % (section_id, interfaces)
606 implementations = TrimTextBlock(implementations)
607 if implementations != '':
608 implementations = '''<refsect1 id="%s.implementations" role="implementations">
609 <title role="implementations.title">Known Implementations</title>
611 </refsect1>
612 ''' % (section_id, implementations)
614 prerequisites = TrimTextBlock(prerequisites)
615 if prerequisites != '':
616 prerequisites = '''<refsect1 id="%s.prerequisites" role="prerequisites">
617 <title role="prerequisites.title">Prerequisites</title>
619 </refsect1>
620 ''' % (section_id, prerequisites)
622 derived = TrimTextBlock(derived)
623 if derived != '':
624 derived = '''<refsect1 id="%s.derived-interfaces" role="derived_interfaces">
625 <title role="derived_interfaces.title">Known Derived Interfaces</title>
627 </refsect1>
628 ''' % (section_id, derived)
630 functions_synop = re.sub(r'^\n*', '', functions_synop)
631 functions_synop = re.sub(r'\n+$', '\n', functions_synop)
632 if functions_synop != '':
633 functions_synop = '''<refsect1 id="%s.functions" role="functions_proto">
634 <title role="functions_proto.title">Functions</title>
635 <informaltable pgwide="1" frame="none">
636 <tgroup cols="2">
637 <colspec colname="functions_return" colwidth="150px"/>
638 <colspec colname="functions_name"/>
639 <tbody>
641 </tbody>
642 </tgroup>
643 </informaltable>
644 </refsect1>
645 ''' % (section_id, functions_synop)
647 other_synop = re.sub(r'^\n*', '', other_synop)
648 other_synop = re.sub(r'\n+$', '\n', other_synop)
649 if other_synop != '':
650 other_synop = '''<refsect1 id="%s.other" role="other_proto">
651 <title role="other_proto.title">Types and Values</title>
652 <informaltable role="enum_members_table" pgwide="1" frame="none">
653 <tgroup cols="2">
654 <colspec colname="name" colwidth="150px"/>
655 <colspec colname="description"/>
656 <tbody>
658 </tbody>
659 </tgroup>
660 </informaltable>
661 </refsect1>
662 ''' % (section_id, other_synop)
664 file_changed = OutputDBFile(filename, title, section_id,
665 section_includes,
666 functions_synop, other_synop,
667 functions_details, other_details,
668 signals_synop, signals_desc,
669 args_synop, args_desc,
670 hierarchy_str, interfaces,
671 implementations,
672 prerequisites, derived,
673 file_objects)
674 if file_changed:
675 changed = True
677 title = ''
678 section_id = ''
679 subsection = ''
680 in_section = 0
681 section_includes = ''
682 functions_synop = ''
683 other_synop = ''
684 functions_details = ''
685 other_details = ''
686 signals_synop = ''
687 signals_desc = ''
688 args_synop = ''
689 child_args_synop = ''
690 style_args_synop = ''
691 args_desc = ''
692 child_args_desc = ''
693 style_args_desc = ''
694 hierarchy_str = ''
695 hierarchy = []
696 interfaces = ''
697 implementations = ''
698 prerequisites = ''
699 derived = ''
701 elif m5:
702 symbol = m5.group(1)
703 logging.info(' Symbol: "%s" in subsection: "%s"', symbol, subsection)
705 # check for duplicate entries
706 if symbol not in symbol_def_line:
707 declaration = Declarations.get(symbol)
708 # FIXME: with this we'll output empty declaration
709 if declaration is not None:
710 if CheckIsObject(symbol):
711 file_objects.append(symbol)
713 # We don't want standard macros/functions of GObjects,
714 # or private declarations.
715 if subsection != "Standard" and subsection != "Private":
716 synop, desc = OutputDeclaration(symbol, declaration)
717 type = DeclarationTypes[symbol]
719 if type == 'FUNCTION' or type == 'USER_FUNCTION':
720 functions_synop += synop
721 functions_details += desc
722 elif type == 'MACRO' and re.search(symbol + r'\(', declaration):
723 functions_synop += synop
724 functions_details += desc
725 else:
726 other_synop += synop
727 other_details += desc
729 sig_synop, sig_desc = GetSignals(symbol)
730 arg_synop, child_arg_synop, style_arg_synop, arg_desc, child_arg_desc, style_arg_desc = GetArgs(
731 symbol)
732 ifaces = GetInterfaces(symbol)
733 impls = GetImplementations(symbol)
734 prereqs = GetPrerequisites(symbol)
735 der = GetDerived(symbol)
736 hierarchy = GetHierarchy(symbol, hierarchy)
738 signals_synop += sig_synop
739 signals_desc += sig_desc
740 args_synop += arg_synop
741 child_args_synop += child_arg_synop
742 style_args_synop += style_arg_synop
743 args_desc += arg_desc
744 child_args_desc += child_arg_desc
745 style_args_desc += style_arg_desc
746 interfaces += ifaces
747 implementations += impls
748 prerequisites += prereqs
749 derived += der
751 # Note that the declaration has been output.
752 DeclarationOutput[symbol] = True
753 elif subsection != "Standard" and subsection != "Private":
754 UndeclaredSymbols[symbol] = True
755 common.LogWarning(file, line_number, "No declaration found for %s." % symbol)
757 num_symbols += 1
758 symbol_def_line[symbol] = line_number
760 if section_id == '':
761 if title == '' and filename == '':
762 common.LogWarning(file, line_number, "Section has no title and no file.")
764 # FIXME: one of those would be enough
765 # filename should be an internal detail for gtk-doc
766 if title == '':
767 title = filename
768 elif filename == '':
769 filename = title
771 filename = filename.replace(' ', '_')
773 section_id = SourceSymbolDocs.get(filename + ":Section_Id")
774 if section_id and section_id.strip() != '':
775 # Remove trailing blanks and use as is
776 section_id = section_id.rstrip()
777 elif CheckIsObject(title):
778 # GObjects use their class name as the ID.
779 section_id = common.CreateValidSGMLID(title)
780 else:
781 section_id = common.CreateValidSGMLID(MODULE + '-' + title)
783 SymbolSection[symbol] = title
784 SymbolSectionId[symbol] = section_id
786 else:
787 common.LogWarning(file, line_number, "Double symbol entry for %s. "
788 "Previous occurrence on line %d." % (symbol, symbol_def_line[symbol]))
789 INPUT.close()
791 OutputMissingDocumentation()
792 OutputUndeclaredSymbols()
793 OutputUnusedSymbols()
795 if options.outputallsymbols:
796 OutputAllSymbols()
798 if options.outputsymbolswithoutsince:
799 OutputSymbolsWithoutSince()
801 for filename in options.expand_content_files.split():
802 file_changed = OutputExtraFile(filename)
803 if file_changed:
804 changed = True
806 return (changed, book_top, book_bottom)
809 def DetermineNamespace():
810 """Find common set of characters."""
811 name_space = ''
812 pos = 0
813 ratio = 0.0
814 while True:
815 prefix = {}
816 letter = ''
817 for symbol in IndexEntriesFull.keys():
818 if name_space == '' or name_space.lower() in symbol.lower():
819 if len(symbol) > pos:
820 letter = symbol[pos:pos + 1]
821 # stop prefix scanning
822 if letter == "_":
823 # stop on "_"
824 break
825 # Should we also stop on a uppercase char, if last was lowercase
826 # GtkWidget, if we have the 'W' and had the 't' before
827 # or should we count upper and lowercase, and stop one 2nd uppercase, if we already had a lowercase
828 # GtkWidget, the 'W' would be the 2nd uppercase and with 't','k' we had lowercase chars before
829 # need to recound each time as this is per symbol
830 ul = letter.upper()
831 if ul in prefix:
832 prefix[ul] += 1
833 else:
834 prefix[ul] = 1
836 if letter != '' and letter != "_":
837 maxletter = ''
838 maxsymbols = 0
839 for letter in prefix.keys():
840 logging.debug("ns prefix: %s: %s", letter, prefix[letter])
841 if prefix[letter] > maxsymbols:
842 maxletter = letter
843 maxsymbols = prefix[letter]
845 ratio = float(len(IndexEntriesFull)) / prefix[maxletter]
846 logging.debug('most symbols start with %s, that is %f', maxletter, (100 * ratio))
847 if ratio > 0.9:
848 # do another round
849 name_space += maxletter
851 pos += 1
853 else:
854 ratio = 0.0
856 if ratio < 0.9:
857 break
858 return name_space
861 def OutputIndex(basename, apiindex):
862 """Writes an index that can be included into the main-document into an <index> tag.
864 Args:
865 basename (str): name of the index file without extension
866 apiindex (dict): the index data
868 old_index = os.path.join(DB_OUTPUT_DIR, basename + '.xml')
869 new_index = os.path.join(DB_OUTPUT_DIR, basename + '.new')
870 lastletter = " "
871 divopen = 0
872 symbol = None
873 short_symbol = None
875 OUTPUT = open(new_index, 'w')
877 OUTPUT.write(MakeDocHeader("indexdiv") + "\n<indexdiv id=\"%s\">\n" % basename)
879 logging.info("generate %s index (%d entries) with namespace %s", basename, len(apiindex), NAME_SPACE)
881 # do a case insensitive sort while chopping off the prefix
882 mapped_keys = [
884 'original': x,
885 'short': re.sub(r'^' + NAME_SPACE + r'\_?(.*)', r'\1', x.upper(), flags=re.I),
886 } for x in apiindex.keys()]
887 sorted_keys = sorted(mapped_keys, key=lambda d: (d['short'], d['original']))
889 for key in sorted_keys:
890 symbol = key['original']
891 short = key['short']
892 if short != '':
893 short_symbol = short
894 else:
895 short_symbol = symbol
897 # generate a short symbol description
898 symbol_desc = ''
899 symbol_section = ''
900 symbol_section_id = ''
901 symbol_type = ''
902 if symbol in DeclarationTypes:
903 symbol_type = DeclarationTypes[symbol].lower()
905 if symbol_type == '':
906 logging.info("trying symbol %s", symbol)
907 m1 = re.search(r'(.*)::(.*)', symbol)
908 m2 = re.search(r'(.*):(.*)', symbol)
909 if m1:
910 oname = m1.group(1)
911 osym = m1.group(2)
912 logging.info(" trying object signal %s:%s in %d signals", oname, osym, len(SignalNames))
913 for name in SignalNames:
914 logging.info(" " + name)
915 if name == osym:
916 symbol_type = "object signal"
917 if oname in SymbolSection:
918 symbol_section = SymbolSection[oname]
919 symbol_section_id = SymbolSectionId[oname]
920 break
921 elif m2:
922 oname = m2.group(1)
923 osym = m2.group(2)
924 logging.info(" trying object property %s::%s in %d properties", oname, osym, len(ArgNames))
925 for name in ArgNames:
926 logging.info(" " + name)
927 if name == osym:
928 symbol_type = "object property"
929 if oname in SymbolSection:
930 symbol_section = SymbolSection[oname]
931 symbol_section_id = SymbolSectionId[oname]
932 break
933 else:
934 if symbol in SymbolSection:
935 symbol_section = SymbolSection[symbol]
936 symbol_section_id = SymbolSectionId[symbol]
938 if symbol_type != '':
939 symbol_desc = ", " + symbol_type
940 if symbol_section != '':
941 symbol_desc += " in <link linkend=\"%s\">%s</link>" % (symbol_section_id, symbol_section)
942 # symbol_desc +=" in " + ExpandAbbreviations(symbol, "#symbol_section")
944 curletter = short_symbol[0].upper()
945 ixid = apiindex[symbol]
947 logging.info(" add symbol %s with %s to index in section '%s' (derived from %s)",
948 symbol, ixid, curletter, short_symbol)
950 if curletter != lastletter:
951 lastletter = curletter
953 if divopen:
954 OUTPUT.write("</indexdiv>\n")
956 OUTPUT.write("<indexdiv><title>%s</title>\n" % curletter)
957 divopen = True
959 OUTPUT.write('<indexentry><primaryie linkends="%s"><link linkend="%s">%s</link>%s</primaryie></indexentry>\n' %
960 (ixid, ixid, symbol, symbol_desc))
962 if divopen:
963 OUTPUT.write("</indexdiv>\n")
965 OUTPUT.write("</indexdiv>\n")
966 OUTPUT.close()
968 common.UpdateFileIfChanged(old_index, new_index, 0)
971 def OutputSinceIndexes():
972 """Generate the 'since' api index files."""
973 for version in set(Since.values()):
974 logging.info("Since : [%s]", version)
975 index = {x: IndexEntriesSince[x] for x in IndexEntriesSince.keys() if Since[x] == version}
976 OutputIndex("api-index-" + version, index)
979 def OutputAnnotationGlossary():
980 """Writes a glossary of the used annotation terms.
982 The glossary file can be included into the main document.
984 # if there are no annotations used return
985 if not AnnotationsUsed:
986 return
988 old_glossary = os.path.join(DB_OUTPUT_DIR, "annotation-glossary.xml")
989 new_glossary = os.path.join(DB_OUTPUT_DIR, "annotation-glossary.new")
990 lastletter = " "
991 divopen = False
993 # add acronyms that are referenced from acronym text
994 rerun = True
995 while rerun:
996 rerun = False
997 for annotation in AnnotationsUsed:
998 if annotation not in AnnotationDefinition:
999 continue
1000 m = re.search(r'<acronym>([\w ]+)<\/acronym>', AnnotationDefinition[annotation])
1001 if m and m.group(1) not in AnnotationsUsed:
1002 AnnotationsUsed[m.group(1)] = 1
1003 rerun = True
1004 break
1006 OUTPUT = open(new_glossary, 'w', encoding='utf-8')
1008 OUTPUT.write('''%s
1009 <glossary id="annotation-glossary">
1010 <title>Annotation Glossary</title>
1011 ''' % MakeDocHeader("glossary"))
1013 for annotation in sorted(AnnotationsUsed.keys(), key=str.lower):
1014 if annotation in AnnotationDefinition:
1015 definition = AnnotationDefinition[annotation]
1016 curletter = annotation[0].upper()
1018 if curletter != lastletter:
1019 lastletter = curletter
1021 if divopen:
1022 OUTPUT.write("</glossdiv>\n")
1024 OUTPUT.write("<glossdiv><title>%s</title>\n" % curletter)
1025 divopen = True
1027 OUTPUT.write(''' <glossentry>
1028 <glossterm><anchor id="annotation-glossterm-%s"/>%s</glossterm>
1029 <glossdef>
1030 <para>%s</para>
1031 </glossdef>
1032 </glossentry>
1033 ''' % (annotation, annotation, definition))
1035 if divopen:
1036 OUTPUT.write("</glossdiv>\n")
1038 OUTPUT.write("</glossary>\n")
1039 OUTPUT.close()
1041 common.UpdateFileIfChanged(old_glossary, new_glossary, 0)
1044 def ReadKnownSymbols(file):
1045 """Collect the names of non-private symbols from the $MODULE-sections.txt file.
1047 Args:
1048 file: the $MODULE-sections.txt file
1051 subsection = ''
1053 logging.info("Reading: %s", file)
1054 INPUT = open(file, 'r', encoding='utf-8')
1055 for line in INPUT:
1056 if line.startswith('#'):
1057 continue
1059 if line.startswith('<SECTION>'):
1060 subsection = ''
1061 continue
1063 m = re.search(r'^<SUBSECTION\s*(.*)>', line, flags=re.I)
1064 if m:
1065 subsection = m.group(1)
1066 continue
1068 if line.startswith('<SUBSECTION>'):
1069 continue
1071 if re.search(r'^<TITLE>(.*)<\/TITLE>', line):
1072 continue
1074 m = re.search(r'^<FILE>(.*)<\/FILE>', line)
1075 if m:
1076 KnownSymbols[m.group(1) + ":Long_Description"] = 1
1077 KnownSymbols[m.group(1) + ":Short_Description"] = 1
1078 continue
1080 m = re.search(r'^<INCLUDE>(.*)<\/INCLUDE>', line)
1081 if m:
1082 continue
1084 m = re.search(r'^<\/SECTION>', line)
1085 if m:
1086 continue
1088 m = re.search(r'^(\S+)', line)
1089 if m:
1090 symbol = m.group(1)
1091 if subsection != "Standard" and subsection != "Private":
1092 KnownSymbols[symbol] = 1
1093 else:
1094 KnownSymbols[symbol] = 0
1095 INPUT.close()
1098 def OutputDeclaration(symbol, declaration):
1099 """Returns the formatted documentation block for a symbol.
1101 Args:
1102 symbol (str): the name of the function/macro/...
1103 declaration (str): the declaration of the function/macro.
1105 Returns:
1106 str: the formatted documentation
1109 dtype = DeclarationTypes[symbol]
1110 if dtype == 'MACRO':
1111 return OutputMacro(symbol, declaration)
1112 elif dtype == 'TYPEDEF':
1113 return OutputTypedef(symbol, declaration)
1114 elif dtype == 'STRUCT':
1115 return OutputStruct(symbol, declaration)
1116 elif dtype == 'ENUM':
1117 return OutputEnum(symbol, declaration)
1118 elif dtype == 'UNION':
1119 return OutputUnion(symbol, declaration)
1120 elif dtype == 'VARIABLE':
1121 return OutputVariable(symbol, declaration)
1122 elif dtype == 'FUNCTION':
1123 return OutputFunction(symbol, declaration, dtype)
1124 elif dtype == 'USER_FUNCTION':
1125 return OutputFunction(symbol, declaration, dtype)
1126 else:
1127 logging.warning("Unknown symbol type %s for symbol %s", dtype, symbol)
1128 return ('', '')
1131 def OutputSymbolTraits(symbol):
1132 """Returns the Since and StabilityLevel paragraphs for a symbol.
1134 Args:
1135 symbol (str): the name to describe
1137 Returns:
1138 str: paragraph or empty string
1141 desc = ''
1143 if symbol in Since:
1144 link_id = "api-index-" + Since[symbol]
1145 desc += "<para role=\"since\">Since: <link linkend=\"%s\">%s</link></para>" % (link_id, Since[symbol])
1147 if symbol in StabilityLevel:
1148 stability = StabilityLevel[symbol]
1149 if stability in AnnotationDefinition:
1150 AnnotationsUsed[stability] = True
1151 stability = "<acronym>%s</acronym>" % stability
1152 desc += "<para role=\"stability\">Stability Level: %s</para>" % stability
1153 return desc
1156 def uri_escape(text):
1157 if text is None:
1158 return None
1160 # Build a char to hex map
1161 escapes = {chr(i): ("%%%02X" % i) for i in range(256)}
1163 # Default unsafe characters. RFC 2732 ^(uric - reserved)
1164 def do_escape(char):
1165 return escapes[char]
1166 return re.sub(r"([^A-Za-z0-9\-_.!~*'()]", do_escape, text)
1169 def OutputSymbolExtraLinks(symbol):
1170 """Returns extralinks for the symbol (if enabled).
1172 Args:
1173 symbol (str): the name to describe
1175 Returns:
1176 str: paragraph or empty string
1178 desc = ''
1180 if False: # NEW FEATURE: needs configurability
1181 sstr = uri_escape(symbol)
1182 mstr = uri_escape(MODULE)
1183 desc += '''<ulink role="extralinks" url="http://www.google.com/codesearch?q=%s">code search</ulink>
1184 <ulink role="extralinks" url="http://library.gnome.org/edit?module=%s&amp;symbol=%s">edit documentation</ulink>
1185 ''' % (sstr, mstr, sstr)
1187 return desc
1190 def OutputSectionExtraLinks(symbol, docsymbol):
1191 desc = ''
1193 if False: # NEW FEATURE: needs configurability
1194 sstr = uri_escape(symbol)
1195 mstr = uri_escape(MODULE)
1196 dsstr = uri_escape(docsymbol)
1197 desc += '''<ulink role="extralinks" url="http://www.google.com/codesearch?q=%s">code search</ulink>
1198 <ulink role="extralinks" url="http://library.gnome.org/edit?module=%s&amp;symbol=%s">edit documentation</ulink>
1199 ''' % (sstr, mstr, dsstr)
1200 return desc
1203 def OutputMacro(symbol, declaration):
1204 """Returns the synopsis and detailed description of a macro.
1206 Args:
1207 symbol (str): the macro name.
1208 declaration (str): the declaration of the macro.
1210 Returns:
1211 str: the formated docs
1213 sid = common.CreateValidSGMLID(symbol)
1214 condition = MakeConditionDescription(symbol)
1215 synop = "<row><entry role=\"define_keyword\">#define</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link>" % (
1216 sid, symbol)
1218 fields = common.ParseMacroDeclaration(declaration, CreateValidSGML)
1219 title = symbol
1220 if len(fields) > 0:
1221 title += '()'
1223 desc = '<refsect2 id="%s" role="macro"%s>\n<title>%s</title>\n' % (sid, condition, title)
1224 desc += MakeIndexterms(symbol, sid)
1225 desc += "\n"
1226 desc += OutputSymbolExtraLinks(symbol)
1228 if len(fields) > 0:
1229 synop += "<phrase role=\"c_punctuation\">()</phrase>"
1231 synop += "</entry></row>\n"
1233 # Don't output the macro definition if is is a conditional macro or it
1234 # looks like a function, i.e. starts with "g_" or "_?gnome_", or it is
1235 # longer than 2 lines, otherwise we get lots of complicated macros like
1236 # g_assert.
1237 if symbol not in DeclarationConditional and not symbol.startswith('g_') \
1238 and not re.search(r'^_?gnome_', symbol) and declaration.count('\n') < 2:
1239 decl_out = CreateValidSGML(declaration)
1240 desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out
1241 else:
1242 desc += "<programlisting language=\"C\">" + "#define".ljust(RETURN_TYPE_FIELD_WIDTH) + symbol
1243 m = re.search(r'^\s*#\s*define\s+\w+(\([^\)]*\))', declaration)
1244 if m:
1245 args = m.group(1)
1246 pad = ' ' * (RETURN_TYPE_FIELD_WIDTH - len("#define "))
1247 # Align each line so that if should all line up OK.
1248 args = args.replace('\n', '\n' + pad)
1249 desc += CreateValidSGML(args)
1251 desc += "</programlisting>\n"
1253 desc += MakeDeprecationNote(symbol)
1255 parameters = OutputParamDescriptions("MACRO", symbol, fields)
1257 if symbol in SymbolDocs:
1258 symbol_docs = ConvertMarkDown(symbol, SymbolDocs[symbol])
1259 desc += symbol_docs
1261 desc += parameters
1262 desc += OutputSymbolTraits(symbol)
1263 desc += "</refsect2>\n"
1264 return (synop, desc)
1267 def OutputTypedef(symbol, declaration):
1268 """Returns the synopsis and detailed description of a typedef.
1270 Args:
1271 symbol (str): the typedef.
1272 declaration (str): the declaration of the typedef,
1273 e.g. 'typedef unsigned int guint;'
1275 Returns:
1276 str: the formated docs
1278 sid = common.CreateValidSGMLID(symbol)
1279 condition = MakeConditionDescription(symbol)
1280 desc = "<refsect2 id=\"%s\" role=\"typedef\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
1281 synop = "<row><entry role=\"typedef_keyword\">typedef</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1282 sid, symbol)
1284 desc += MakeIndexterms(symbol, sid)
1285 desc += "\n"
1286 desc += OutputSymbolExtraLinks(symbol)
1288 if symbol not in DeclarationConditional:
1289 decl_out = CreateValidSGML(declaration)
1290 desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out
1292 desc += MakeDeprecationNote(symbol)
1294 if symbol in SymbolDocs:
1295 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1297 desc += OutputSymbolTraits(symbol)
1298 desc += "</refsect2>\n"
1299 return (synop, desc)
1302 def OutputStruct(symbol, declaration):
1303 """Returns the synopsis and detailed description of a struct.
1305 We check if it is a object struct, and if so we only output parts of it that
1306 are noted as public fields. We also use a different IDs for object structs,
1307 since the original ID is used for the entire RefEntry.
1309 Args:
1310 symbol (str): the struct.
1311 declaration (str): the declaration of the struct.
1313 Returns:
1314 str: the formated docs
1316 is_gtype = False
1317 default_to_public = True
1318 if CheckIsObject(symbol):
1319 logging.info("Found struct gtype: %s", symbol)
1320 is_gtype = True
1321 default_to_public = ObjectRoots[symbol] == 'GBoxed'
1323 sid = None
1324 condition = None
1325 if is_gtype:
1326 sid = common.CreateValidSGMLID(symbol + "_struct")
1327 condition = MakeConditionDescription(symbol + "_struct")
1328 else:
1329 sid = common.CreateValidSGMLID(symbol)
1330 condition = MakeConditionDescription(symbol)
1332 # Determine if it is a simple struct or it also has a typedef.
1333 has_typedef = False
1334 if symbol in StructHasTypedef or re.search(r'^\s*typedef\s+', declaration):
1335 has_typedef = True
1337 type_output = None
1338 desc = None
1339 if has_typedef:
1340 # For structs with typedefs we just output the struct name.
1341 type_output = ''
1342 desc = "<refsect2 id=\"%s\" role=\"struct\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
1343 else:
1344 type_output = "struct"
1345 desc = "<refsect2 id=\"%s\" role=\"struct\"%s>\n<title>struct %s</title>\n" % (sid, condition, symbol)
1347 synop = "<row><entry role=\"datatype_keyword\">%s</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1348 type_output, sid, symbol)
1350 desc += MakeIndexterms(symbol, sid)
1351 desc += "\n"
1352 desc += OutputSymbolExtraLinks(symbol)
1354 # Form a pretty-printed, private-data-removed form of the declaration
1356 decl_out = ''
1357 if re.search(r'^\s*$', declaration):
1358 logging.info("Found opaque struct: %s", symbol)
1359 decl_out = "typedef struct _%s %s;" % (symbol, symbol)
1360 elif re.search(r'^\s*struct\s+\w+\s*;\s*$', declaration):
1361 logging.info("Found opaque struct: %s", symbol)
1362 decl_out = "struct %s;" % symbol
1363 else:
1364 m = re.search(
1365 r'^\s*(typedef\s+)?struct\s*\w*\s*(?:\/\*.*\*\/)?\s*{(.*)}\s*\w*\s*;\s*$', declaration, flags=re.S)
1366 if m:
1367 struct_contents = m.group(2)
1369 public = default_to_public
1370 new_declaration = ''
1372 for decl_line in struct_contents.splitlines():
1373 logging.info("Struct line: %s", decl_line)
1374 m2 = re.search(r'/\*\s*<\s*public\s*>\s*\*/', decl_line)
1375 m3 = re.search(r'/\*\s*<\s*(private|protected)\s*>\s*\*/', decl_line)
1376 if m2:
1377 public = True
1378 elif m3:
1379 public = False
1380 elif public:
1381 new_declaration += decl_line + "\n"
1383 if new_declaration:
1384 # Strip any blank lines off the ends.
1385 new_declaration = re.sub(r'^\s*\n', '', new_declaration)
1386 new_declaration = re.sub(r'\n\s*$', r'\n', new_declaration)
1388 if has_typedef:
1389 decl_out = "typedef struct {\n%s} %s;\n" % (new_declaration, symbol)
1390 else:
1391 decl_out = "struct %s {\n%s};\n" % (symbol, new_declaration)
1393 else:
1394 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1395 "Couldn't parse struct:\n%s" % declaration)
1397 # If we couldn't parse the struct or it was all private, output an
1398 # empty struct declaration.
1399 if decl_out == '':
1400 if has_typedef:
1401 decl_out = "typedef struct _%s %s;" % (symbol, symbol)
1402 else:
1403 decl_out = "struct %s;" % symbol
1405 decl_out = CreateValidSGML(decl_out)
1406 desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out
1408 desc += MakeDeprecationNote(symbol)
1410 if symbol in SymbolDocs:
1411 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1413 # Create a table of fields and descriptions
1415 # FIXME: Inserting &#160's into the produced type declarations here would
1416 # improve the output in most situations ... except for function
1417 # members of structs!
1418 def pfunc(*args):
1419 return '<structfield id="%s">%s</structfield>' % (common.CreateValidSGMLID(sid + '.' + args[0]), args[0])
1420 fields = common.ParseStructDeclaration(declaration, not default_to_public, 0, MakeXRef, pfunc)
1421 params = SymbolParams.get(symbol)
1423 # If no parameters are filled in, we don't generate the description
1424 # table, for backwards compatibility.
1425 found = False
1426 if params:
1427 found = next((True for p in params.values() if p.strip() != ''), False)
1429 if found:
1430 field_descrs = params
1431 missing_parameters = ''
1432 unused_parameters = ''
1433 sid = common.CreateValidSGMLID(symbol + ".members")
1435 desc += '''<refsect3 id="%s" role="struct_members">\n<title>Members</title>
1436 <informaltable role="struct_members_table" pgwide="1" frame="none">
1437 <tgroup cols="3">
1438 <colspec colname="struct_members_name" colwidth="300px"/>
1439 <colspec colname="struct_members_description"/>
1440 <colspec colname="struct_members_annotations" colwidth="200px"/>
1441 <tbody>
1442 ''' % sid
1444 for field_name, text in fields.items():
1445 param_annotations = ''
1447 desc += "<row role=\"member\"><entry role=\"struct_member_name\"><para>%s</para></entry>\n" % text
1448 if field_name in field_descrs:
1449 (field_descr, param_annotations) = ExpandAnnotation(symbol, field_descrs[field_name])
1450 field_descr = ConvertMarkDown(symbol, field_descr)
1451 # trim
1452 field_descr = re.sub(r'^(\s|\n)+', '', field_descr, flags=re.M | re.S)
1453 field_descr = re.sub(r'(\s|\n)+$', '', field_descr, flags=re.M | re.S)
1454 desc += "<entry role=\"struct_member_description\">%s</entry>\n<entry role=\"struct_member_annotations\">%s</entry>\n" % (
1455 field_descr, param_annotations)
1456 del field_descrs[field_name]
1457 else:
1458 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1459 "Field description for %s::%s is missing in source code comment block." % (symbol, field_name))
1460 if missing_parameters != '':
1461 missing_parameters += ", " + field_name
1462 else:
1463 missing_parameters = field_name
1465 desc += "<entry /><entry />\n"
1467 desc += "</row>\n"
1469 desc += "</tbody></tgroup></informaltable>\n</refsect3>\n"
1470 for field_name in field_descrs:
1471 # Documenting those standard fields is not required anymore, but
1472 # we don't want to warn if they are documented anyway.
1473 m = re.search(r'(g_iface|parent_instance|parent_class)', field_name)
1474 if m:
1475 continue
1477 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1478 "Field description for %s::%s is not used from source code comment block." % (symbol, field_name))
1479 if unused_parameters != '':
1480 unused_parameters += ", " + field_name
1481 else:
1482 unused_parameters = field_name
1484 # remember missing/unused parameters (needed in tmpl-free build)
1485 if missing_parameters != '' and (symbol not in AllIncompleteSymbols):
1486 AllIncompleteSymbols[symbol] = missing_parameters
1488 if unused_parameters != '' and (symbol not in AllUnusedSymbols):
1489 AllUnusedSymbols[symbol] = unused_parameters
1490 else:
1491 if fields:
1492 if symbol not in AllIncompleteSymbols:
1493 AllIncompleteSymbols[symbol] = "<items>"
1494 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1495 "Field descriptions for struct %s are missing in source code comment block." % symbol)
1496 logging.info("Remaining structs fields: " + ':'.join(fields) + "\n")
1498 desc += OutputSymbolTraits(symbol)
1499 desc += "</refsect2>\n"
1500 return (synop, desc)
1503 def OutputUnion(symbol, declaration):
1504 """Returns the synopsis and detailed description of a union.
1506 Args:
1507 symbol (str): the union.
1508 declaration (str): the declaration of the union.
1510 Returns:
1511 str: the formated docs
1513 is_gtype = False
1514 if CheckIsObject(symbol):
1515 logging.info("Found union gtype: %s", symbol)
1516 is_gtype = True
1518 sid = None
1519 condition = None
1520 if is_gtype:
1521 sid = common.CreateValidSGMLID(symbol + "_union")
1522 condition = MakeConditionDescription(symbol + "_union")
1523 else:
1524 sid = common.CreateValidSGMLID(symbol)
1525 condition = MakeConditionDescription(symbol)
1527 # Determine if it is a simple struct or it also has a typedef.
1528 has_typedef = False
1529 if symbol in StructHasTypedef or re.search(r'^\s*typedef\s+', declaration):
1530 has_typedef = True
1532 type_output = None
1533 desc = None
1534 if has_typedef:
1535 # For unions with typedefs we just output the union name.
1536 type_output = ''
1537 desc = "<refsect2 id=\"%s\" role=\"union\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
1538 else:
1539 type_output = "union"
1540 desc = "<refsect2 id=\"%s\" role=\"union\"%s>\n<title>union %s</title>\n" % (sid, condition, symbol)
1542 synop = "<row><entry role=\"datatype_keyword\">%s</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1543 type_output, sid, symbol)
1545 desc += MakeIndexterms(symbol, sid)
1546 desc += "\n"
1547 desc += OutputSymbolExtraLinks(symbol)
1548 desc += MakeDeprecationNote(symbol)
1550 if symbol in SymbolDocs:
1551 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1553 # Create a table of fields and descriptions
1555 # FIXME: Inserting &#160's into the produced type declarations here would
1556 # improve the output in most situations ... except for function
1557 # members of structs!
1558 def pfunc(*args):
1559 return '<structfield id="%s">%s</structfield>' % (common.CreateValidSGMLID(sid + '.' + args[0]), args[0])
1560 fields = common.ParseStructDeclaration(declaration, 0, 0, MakeXRef, pfunc)
1561 params = SymbolParams.get(symbol)
1563 # If no parameters are filled in, we don't generate the description
1564 # table, for backwards compatibility
1565 found = False
1566 if params:
1567 found = next((True for p in params.values() if p.strip() != ''), False)
1569 logging.debug('Union %s has %d entries, found=%d, has_typedef=%d', symbol, len(fields), found, has_typedef)
1571 if found:
1572 field_descrs = params
1573 missing_parameters = ''
1574 unused_parameters = ''
1575 sid = common.CreateValidSGMLID('%s.members' % symbol)
1577 desc += '''<refsect3 id="%s" role="union_members">\n<title>Members</title>
1578 <informaltable role="union_members_table" pgwide="1" frame="none">
1579 <tgroup cols="3">
1580 <colspec colname="union_members_name" colwidth="300px"/>
1581 <colspec colname="union_members_description"/>
1582 <colspec colname="union_members_annotations" colwidth="200px"/>
1583 <tbody>
1584 ''' % sid
1586 for field_name, text in fields.items():
1587 param_annotations = ''
1589 desc += "<row><entry role=\"union_member_name\"><para>%s</para></entry>\n" % text
1590 if field_name in field_descrs:
1591 (field_descr, param_annotations) = ExpandAnnotation(symbol, field_descrs[field_name])
1592 field_descr = ConvertMarkDown(symbol, field_descr)
1594 # trim
1595 field_descr = re.sub(r'^(\s|\n)+', '', field_descr, flags=re.M | re.S)
1596 field_descr = re.sub(r'(\s|\n)+$', '', field_descr, flags=re.M | re.S)
1597 desc += "<entry role=\"union_member_description\">%s</entry>\n<entry role=\"union_member_annotations\">%s</entry>\n" % (
1598 field_descr, param_annotations)
1599 del field_descrs[field_name]
1600 else:
1601 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1602 "Field description for %s::%s is missing in source code comment block." % (symbol, field_name))
1603 if missing_parameters != '':
1604 missing_parameters += ", " + field_name
1605 else:
1606 missing_parameters = field_name
1608 desc += "<entry /><entry />\n"
1610 desc += "</row>\n"
1612 desc += "</tbody></tgroup></informaltable>\n</refsect3>"
1613 for field_name in field_descrs:
1614 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1615 "Field description for %s::%s is not used from source code comment block." % (symbol, field_name))
1616 if unused_parameters != '':
1617 unused_parameters += ", " + field_name
1618 else:
1619 unused_parameters = field_name
1621 # remember missing/unused parameters (needed in tmpl-free build)
1622 if missing_parameters != '' and (symbol not in AllIncompleteSymbols):
1623 AllIncompleteSymbols[symbol] = missing_parameters
1625 if unused_parameters != '' and (symbol not in AllUnusedSymbols):
1626 AllUnusedSymbols[symbol] = unused_parameters
1627 else:
1628 if len(fields) > 0:
1629 if symbol not in AllIncompleteSymbols:
1630 AllIncompleteSymbols[symbol] = "<items>"
1631 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1632 "Field descriptions for union %s are missing in source code comment block." % symbol)
1633 logging.info("Remaining union fields: " + ':'.join(fields) + "\n")
1635 desc += OutputSymbolTraits(symbol)
1636 desc += "</refsect2>\n"
1637 return (synop, desc)
1640 def OutputEnum(symbol, declaration):
1641 """Returns the synopsis and detailed description of a enum.
1643 Args:
1644 symbol (str): the enum.
1645 declaration (str): the declaration of the enum.
1647 Returns:
1648 str: the formated docs
1650 is_gtype = False
1651 if CheckIsObject(symbol):
1652 logging.info("Found enum gtype: %s", symbol)
1653 is_gtype = True
1655 sid = None
1656 condition = None
1657 if is_gtype:
1658 sid = common.CreateValidSGMLID(symbol + "_enum")
1659 condition = MakeConditionDescription(symbol + "_enum")
1660 else:
1661 sid = common.CreateValidSGMLID(symbol)
1662 condition = MakeConditionDescription(symbol)
1664 synop = "<row><entry role=\"datatype_keyword\">enum</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1665 sid, symbol)
1666 desc = "<refsect2 id=\"%s\" role=\"enum\"%s>\n<title>enum %s</title>\n" % (sid, condition, symbol)
1668 desc += MakeIndexterms(symbol, sid)
1669 desc += "\n"
1670 desc += OutputSymbolExtraLinks(symbol)
1671 desc += MakeDeprecationNote(symbol)
1673 if symbol in SymbolDocs:
1674 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1676 # Create a table of fields and descriptions
1678 fields = common.ParseEnumDeclaration(declaration)
1679 params = SymbolParams.get(symbol)
1681 # If nothing at all is documented log a single summary warning at the end.
1682 # Otherwise, warn about each undocumented item.
1684 found = False
1685 if params:
1686 found = next((True for p in params.values() if p.strip() != ''), False)
1687 field_descrs = params
1688 else:
1689 field_descrs = {}
1691 missing_parameters = ''
1692 unused_parameters = ''
1694 sid = common.CreateValidSGMLID("%s.members" % symbol)
1695 desc += '''<refsect3 id="%s" role="enum_members">\n<title>Members</title>
1696 <informaltable role="enum_members_table" pgwide="1" frame="none">
1697 <tgroup cols="3">
1698 <colspec colname="enum_members_name" colwidth="300px"/>
1699 <colspec colname="enum_members_description"/>
1700 <colspec colname="enum_members_annotations" colwidth="200px"/>
1701 <tbody>
1702 ''' % sid
1704 for field_name in fields:
1705 field_descr = field_descrs.get(field_name)
1706 param_annotations = ''
1708 sid = common.CreateValidSGMLID(field_name)
1709 condition = MakeConditionDescription(field_name)
1710 desc += "<row role=\"constant\"><entry role=\"enum_member_name\"><para id=\"%s\">%s</para></entry>\n" % (
1711 sid, field_name)
1712 if field_descr:
1713 field_descr, param_annotations = ExpandAnnotation(symbol, field_descr)
1714 field_descr = ConvertMarkDown(symbol, field_descr)
1715 desc += "<entry role=\"enum_member_description\">%s</entry>\n<entry role=\"enum_member_annotations\">%s</entry>\n" % (
1716 field_descr, param_annotations)
1717 del field_descrs[field_name]
1718 else:
1719 if found:
1720 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1721 "Value description for %s::%s is missing in source code comment block." % (symbol, field_name))
1722 if missing_parameters != '':
1723 missing_parameters += ", " + field_name
1724 else:
1725 missing_parameters = field_name
1726 desc += "<entry /><entry />\n"
1727 desc += "</row>\n"
1729 desc += "</tbody></tgroup></informaltable>\n</refsect3>"
1730 for field_name in field_descrs:
1731 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1732 "Value description for %s::%s is not used from source code comment block." % (symbol, field_name))
1733 if unused_parameters != '':
1734 unused_parameters += ", " + field_name
1735 else:
1736 unused_parameters = field_name
1738 # remember missing/unused parameters (needed in tmpl-free build)
1739 if missing_parameters != '' and (symbol not in AllIncompleteSymbols):
1740 AllIncompleteSymbols[symbol] = missing_parameters
1742 if unused_parameters != '' and (symbol not in AllUnusedSymbols):
1743 AllUnusedSymbols[symbol] = unused_parameters
1745 if not found:
1746 if len(fields) > 0:
1747 if symbol not in AllIncompleteSymbols:
1748 AllIncompleteSymbols[symbol] = "<items>"
1749 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1750 "Value descriptions for %s are missing in source code comment block." % symbol)
1752 desc += OutputSymbolTraits(symbol)
1753 desc += "</refsect2>\n"
1754 return (synop, desc)
1757 def OutputVariable(symbol, declaration):
1758 """Returns the synopsis and detailed description of a variable.
1760 Args:
1761 symbol (str): the extern'ed variable.
1762 declaration (str): the declaration of the variable.
1764 Returns:
1765 str: the formated docs
1767 sid = common.CreateValidSGMLID(symbol)
1768 condition = MakeConditionDescription(symbol)
1770 logging.info("ouputing variable: '%s' '%s'", symbol, declaration)
1772 type_output = None
1773 m1 = re.search(
1774 r'^\s*extern\s+((const\s+|signed\s+|unsigned\s+|long\s+|short\s+)*\w+)(\s+\*+|\*+|\s)(\s*)(const\s+)*([A-Za-z]\w*)\s*;', declaration)
1775 m2 = re.search(
1776 r'\s*((const\s+|signed\s+|unsigned\s+|long\s+|short\s+)*\w+)(\s+\*+|\*+|\s)(\s*)(const\s+)*([A-Za-z]\w*)\s*=', declaration)
1777 if m1:
1778 mod1 = m1.group(1) or ''
1779 ptr = m1.group(3) or ''
1780 space = m1.group(4) or ''
1781 mod2 = m1.group(5) or ''
1782 type_output = "extern %s%s%s%s" % (mod1, ptr, space, mod2)
1783 elif m2:
1784 mod1 = m2.group(1) or ''
1785 ptr = m2.group(3) or ''
1786 space = m2.group(4) or ''
1787 mod2 = m2.group(5) or ''
1788 type_output = '%s%s%s%s' % (mod1, ptr, space, mod2)
1789 else:
1790 type_output = "extern"
1792 synop = "<row><entry role=\"variable_type\">%s</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1793 type_output, sid, symbol)
1795 desc = "<refsect2 id=\"%s\" role=\"variable\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
1797 desc += MakeIndexterms(symbol, sid)
1798 desc += "\n"
1799 desc += OutputSymbolExtraLinks(symbol)
1801 decl_out = CreateValidSGML(declaration)
1802 desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out
1804 desc += MakeDeprecationNote(symbol)
1806 if symbol in SymbolDocs:
1807 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1809 if symbol in SymbolAnnotations:
1810 param_desc = SymbolAnnotations[symbol]
1811 param_annotations = ''
1812 (param_desc, param_annotations) = ExpandAnnotation(symbol, param_desc)
1813 if param_annotations != '':
1814 desc += "\n<para>%s</para>" % param_annotations
1816 desc += OutputSymbolTraits(symbol)
1817 desc += "</refsect2>\n"
1818 return (synop, desc)
1821 def OutputFunction(symbol, declaration, symbol_type):
1822 """Returns the synopsis and detailed description of a function.
1824 Args:
1825 symbol (str): the function.
1826 declaration (str): the declaration of the function.
1828 Returns:
1829 str: the formated docs
1831 sid = common.CreateValidSGMLID(symbol)
1832 condition = MakeConditionDescription(symbol)
1834 # Take out the return type
1835 # $1 $2 $3
1836 regex = r'<RETURNS>\s*((?:const\s+|G_CONST_RETURN\s+|signed\s+|unsigned\s+|long\s+|short\s+|struct\s+|enum\s+)*)(\w+)(\s*\**\s*(?:const|G_CONST_RETURN)?\s*\**\s*(?:restrict)?\s*)<\/RETURNS>\n'
1837 m = re.search(regex, declaration)
1838 declaration = re.sub(regex, '', declaration)
1839 type_modifier = m.group(1) or ''
1840 type = m.group(2)
1841 pointer = m.group(3)
1842 pointer = pointer.rstrip()
1843 xref = MakeXRef(type, tagify(type, "returnvalue"))
1844 start = ''
1845 # if (symbol_type == 'USER_FUNCTION')
1846 # start = "typedef "
1849 # We output const rather than G_CONST_RETURN.
1850 type_modifier = re.sub(r'G_CONST_RETURN', 'const', type_modifier)
1851 pointer = re.sub(r'G_CONST_RETURN', 'const', pointer)
1852 pointer = re.sub(r'^\s+', '&#160;', pointer)
1854 ret_type_output = "%s%s%s%s\n" % (start, type_modifier, xref, pointer)
1856 indent_len = len(symbol) + 2
1857 char1 = char2 = char3 = ''
1858 if symbol_type == 'USER_FUNCTION':
1859 indent_len += 3
1860 char1 = "<phrase role=\"c_punctuation\">(</phrase>"
1861 char2 = "*"
1862 char3 = "<phrase role=\"c_punctuation\">)</phrase>"
1864 symbol_output = "%s<link linkend=\"%s\">%s%s</link>%s" % (char1, sid, char2, symbol, char3)
1865 if indent_len < MAX_SYMBOL_FIELD_WIDTH:
1866 symbol_desc_output = "%s%s%s%s " % (char1, char2, symbol, char3)
1867 else:
1868 indent_len = MAX_SYMBOL_FIELD_WIDTH - 8
1869 symbol_desc_output = ('%s%s%s%s\n' % (char1, char2, symbol, char3)) + (' ' * (indent_len - 1))
1871 synop = "<row><entry role=\"function_type\">%s</entry><entry role=\"function_name\">%s&#160;<phrase role=\"c_punctuation\">()</phrase></entry></row>\n" % (
1872 ret_type_output, symbol_output)
1874 desc = "<refsect2 id=\"%s\" role=\"function\"%s>\n<title>%s&#160;()</title>\n" % (sid, condition, symbol)
1876 desc += MakeIndexterms(symbol, sid)
1877 desc += "\n"
1878 desc += OutputSymbolExtraLinks(symbol)
1880 desc += "<programlisting language=\"C\">%s%s(" % (ret_type_output, symbol_desc_output)
1882 def tagfun(*args):
1883 return tagify(args[0], "parameter")
1885 fields = common.ParseFunctionDeclaration(declaration, MakeXRef, tagfun)
1887 first = True
1888 for field_name in fields.values():
1889 if first:
1890 desc += field_name
1891 first = False
1892 else:
1893 desc += ",\n" + (' ' * indent_len) + field_name
1895 desc += ");</programlisting>\n"
1897 desc += MakeDeprecationNote(symbol)
1899 if symbol in SymbolDocs:
1900 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1902 if symbol in SymbolAnnotations:
1903 param_desc = SymbolAnnotations[symbol]
1904 (param_desc, param_annotations) = ExpandAnnotation(symbol, param_desc)
1905 if param_annotations != '':
1906 desc += "\n<para>%s</para>" % param_annotations
1908 desc += OutputParamDescriptions("FUNCTION", symbol, fields.keys())
1909 desc += OutputSymbolTraits(symbol)
1910 desc += "</refsect2>\n"
1911 return (synop, desc)
1914 def OutputParamDescriptions(symbol_type, symbol, fields):
1915 """Returns the DocBook output describing the parameters of a symbol.
1917 This can be used for functions, macros or signal handlers.
1919 Args:
1920 symbol_type (str): 'FUNCTION', 'MACRO' or 'SIGNAL'. Signal
1921 handlers have an implicit user_data parameter last.
1922 symbol (str): the name of the symbol being described.
1923 fields (list): parsed fields from the declaration, used to determine
1924 undocumented/unused entries
1926 Returns:
1927 str: the formated parameter docs
1929 output = ''
1930 num_params = 0
1931 field_descrs = None
1933 if fields:
1934 field_descrs = [f for f in fields if f not in ['void', 'Returns']]
1935 else:
1936 field_descrs = []
1938 params = SymbolParams.get(symbol)
1939 logging.info("param_desc(%s, %s) = %s", symbol_type, symbol, str(params))
1940 # This might be an empty dict, but for SIGNALS we append the user_data docs.
1941 # TODO(ensonic): maybe create that docstring in GetSignals()
1942 if params is not None:
1943 returns = ''
1944 params_desc = ''
1945 missing_parameters = ''
1946 unused_parameters = ''
1948 for param_name, param_desc in params.items():
1949 (param_desc, param_annotations) = ExpandAnnotation(symbol, param_desc)
1950 param_desc = ConvertMarkDown(symbol, param_desc)
1951 # trim
1952 param_desc = re.sub(r'^(\s|\n)+', '', param_desc, flags=re.M | re.S)
1953 param_desc = re.sub(r'(\s|\n)+$', '', param_desc, flags=re.M | re.S)
1954 if param_name == "Returns":
1955 returns = param_desc
1956 if param_annotations != '':
1957 returns += "\n<para>%s</para>" % param_annotations
1959 elif param_name == "void":
1960 # FIXME: &common.LogWarning()?
1961 logging.info("!!!! void in params for %s?\n", symbol)
1962 else:
1963 if fields:
1964 if param_name not in field_descrs:
1965 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1966 "Parameter description for %s::%s is not used from source code comment block." % (symbol, param_name))
1967 if unused_parameters != '':
1968 unused_parameters += ", " + param_name
1969 else:
1970 unused_parameters = param_name
1971 else:
1972 field_descrs.remove(param_name)
1974 if param_desc != '':
1975 params_desc += "<row><entry role=\"parameter_name\"><para>%s</para></entry>\n<entry role=\"parameter_description\">%s</entry>\n<entry role=\"parameter_annotations\">%s</entry></row>\n" % (
1976 param_name, param_desc, param_annotations)
1977 num_params += 1
1979 for param_name in field_descrs:
1980 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1981 "Parameter description for %s::%s is missing in source code comment block." % (symbol, param_name))
1982 if missing_parameters != '':
1983 missing_parameters += ", " + param_name
1984 else:
1985 missing_parameters = param_name
1987 # Signals have an implicit user_data parameter which we describe.
1988 if symbol_type == "SIGNAL":
1989 params_desc += "<row><entry role=\"parameter_name\"><simpara>user_data</simpara></entry>\n<entry role=\"parameter_description\"><simpara>user data set when the signal handler was connected.</simpara></entry>\n<entry role=\"parameter_annotations\"></entry></row>\n"
1991 # Start a table if we need one.
1992 if params_desc != '':
1993 sid = common.CreateValidSGMLID("%s.parameters" % symbol)
1995 output += '''<refsect3 id="%s" role="parameters">\n<title>Parameters</title>
1996 <informaltable role="parameters_table" pgwide="1" frame="none">
1997 <tgroup cols="3">
1998 <colspec colname="parameters_name" colwidth="150px"/>
1999 <colspec colname="parameters_description"/>
2000 <colspec colname="parameters_annotations" colwidth="200px"/>
2001 <tbody>
2002 ''' % sid
2003 output += params_desc
2004 output += "</tbody></tgroup></informaltable>\n</refsect3>"
2006 # Output the returns info last
2007 if returns != '':
2008 sid = common.CreateValidSGMLID("%s.returns" % symbol)
2010 output += '''<refsect3 id="%s" role=\"returns\">\n<title>Returns</title>
2011 ''' % sid
2012 output += returns
2013 output += "\n</refsect3>"
2015 # remember missing/unused parameters (needed in tmpl-free build)
2016 if missing_parameters != '' and (symbol not in AllIncompleteSymbols):
2017 AllIncompleteSymbols[symbol] = missing_parameters
2019 if unused_parameters != '' and (symbol not in AllUnusedSymbols):
2020 AllUnusedSymbols[symbol] = unused_parameters
2022 if num_params == 0 and fields and field_descrs:
2023 if symbol not in AllIncompleteSymbols:
2024 AllIncompleteSymbols[symbol] = "<parameters>"
2025 return output
2028 def ParseStabilityLevel(stability, file, line, message):
2029 """Parses a stability level and outputs a warning if it isn't valid.
2030 Args:
2031 stability (str): the stability text.
2032 file, line: context for error message
2033 message: description of where the level is from, to use in any error message.
2034 Returns:
2035 str: the parsed stability level string.
2037 stability = stability.strip()
2038 sl = stability.strip().lower()
2039 if sl == 'stable':
2040 stability = "Stable"
2041 elif sl == 'unstable':
2042 stability = "Unstable"
2043 elif sl == 'private':
2044 stability = "Private"
2045 else:
2046 common.LogWarning(file, line,
2047 "%s is %s. It should be one of these: Stable, "
2048 "Unstable, or Private." % (
2049 message, stability))
2050 return str(stability)
2053 def OutputDBFile(file, title, section_id, includes, functions_synop, other_synop, functions_details, other_details, signals_synop, signals_desc, args_synop, args_desc, hierarchy, interfaces, implementations, prerequisites, derived, file_objects):
2054 """Outputs the final DocBook file for one section.
2056 Args:
2057 file (str): the name of the file.
2058 title (str): the title from the $MODULE-sections.txt file
2059 section_id (str): the id to use for the toplevel tag.
2060 includes (str): comma-separates list of include files added at top of
2061 synopsis, with '<' '>' around them (if not already enclosed in '').
2062 functions_synop (str): the DocBook for the Functions Synopsis part.
2063 other_synop (str): the DocBook for the Types and Values Synopsis part.
2064 functions_details (str): the DocBook for the Functions Details part.
2065 other_details (str): the DocBook for the Types and Values Details part.
2066 signal_synop (str): the DocBook for the Signal Synopsis part
2067 signal_desc (str): the DocBook for the Signal Description part
2068 args_synop (str): the DocBook for the Arg Synopsis part
2069 args_desc (str): the DocBook for the Arg Description part
2070 hierarchy (str): the DocBook for the Object Hierarchy part
2071 interfaces (str): the DocBook for the Interfaces part
2072 implementations (str): the DocBook for the Known Implementations part
2073 prerequisites (str): the DocBook for the Prerequisites part
2074 derived (str): the DocBook for the Derived Interfaces part
2075 file_objects (list): objects in this file
2077 Returns:
2078 bool: True if the docs where updated
2081 logging.info("Output docbook for file %s with title '%s'", file, title)
2083 # The edited title overrides the one from the sections file.
2084 new_title = SymbolDocs.get(file + ":Title")
2085 if new_title and not new_title.strip() == '':
2086 title = new_title
2087 logging.info("Found title: %s", title)
2089 short_desc = SymbolDocs.get(file + ":Short_Description")
2090 if not short_desc or short_desc.strip() == '':
2091 short_desc = ''
2092 else:
2093 # Don't use ConvertMarkDown here for now since we don't want blocks
2094 short_desc = ExpandAbbreviations(title + ":Short_description", short_desc)
2095 logging.info("Found short_desc: %s", short_desc)
2097 long_desc = SymbolDocs.get(file + ":Long_Description")
2098 if not long_desc or long_desc.strip() == '':
2099 long_desc = ''
2100 else:
2101 long_desc = ConvertMarkDown(title + ":Long_description", long_desc)
2102 logging.info("Found long_desc: %s", long_desc)
2104 see_also = SymbolDocs.get(file + ":See_Also")
2105 if not see_also or re.search(r'^\s*(<para>)?\s*(</para>)?\s*$', see_also):
2106 see_also = ''
2107 else:
2108 see_also = ConvertMarkDown(title + ":See_Also", see_also)
2109 logging.info("Found see_also: %s", see_also)
2111 if see_also:
2112 see_also = "<refsect1 id=\"%s.see-also\">\n<title>See Also</title>\n%s\n</refsect1>\n" % (section_id, see_also)
2114 stability = SymbolDocs.get(file + ":Stability_Level")
2115 if not stability or re.search(r'^\s*$', stability):
2116 stability = ''
2117 else:
2118 line_number = GetSymbolSourceLine(file + ":Stability_Level")
2119 stability = ParseStabilityLevel(stability, file, line_number, "Section stability level")
2120 logging.info("Found stability: %s", stability)
2122 if not stability:
2123 stability = DEFAULT_STABILITY or ''
2125 if stability:
2126 if stability in AnnotationDefinition:
2127 AnnotationsUsed[stability] = True
2128 stability = "<acronym>%s</acronym>" % stability
2129 stability = "<refsect1 id=\"%s.stability-level\">\n<title>Stability Level</title>\n%s, unless otherwise indicated\n</refsect1>\n" % (
2130 section_id, stability)
2132 image = SymbolDocs.get(file + ":Image")
2133 if not image or re.search(r'^\s*$', image):
2134 image = ''
2135 else:
2136 image = image.strip()
2138 format = None
2140 il = image.lower()
2141 if re.search(r'jpe?g$', il):
2142 format = "format='JPEG'"
2143 elif il.endswith('png'):
2144 format = "format='PNG'"
2145 elif il.endswith('svg'):
2146 format = "format='SVG'"
2147 else:
2148 format = ''
2150 image = " <inlinegraphic fileref='%s' %s/>\n" % (image, format)
2152 include_output = ''
2153 if includes:
2154 include_output += "<refsect1 id=\"%s.includes\"><title>Includes</title><synopsis>" % section_id
2155 for include in includes.split(','):
2156 if re.search(r'^\".+\"$', include):
2157 include_output += "#include %s\n" % include
2158 else:
2159 include = re.sub(r'^\s+|\s+$', '', include, flags=re.S)
2160 include_output += "#include &lt;%s&gt;\n" % include
2162 include_output += "</synopsis></refsect1>\n"
2164 extralinks = OutputSectionExtraLinks(title, "Section:%s" % file)
2166 old_db_file = os.path.join(DB_OUTPUT_DIR, file + '.xml')
2167 new_db_file = os.path.join(DB_OUTPUT_DIR, file + '.xml.new')
2169 OUTPUT = open(new_db_file, 'w', encoding='utf-8')
2171 object_anchors = ''
2172 for fobject in file_objects:
2173 if fobject == section_id:
2174 continue
2175 sid = common.CreateValidSGMLID(fobject)
2176 logging.info("Adding anchor for %s\n", fobject)
2177 object_anchors += "<anchor id=\"%s\"/>" % sid
2179 # Make sure we produce valid docbook
2180 if not functions_details:
2181 functions_details = "<para />"
2183 # We used to output this, but is messes up our common.UpdateFileIfChanged code
2184 # since it changes every day (and it is only used in the man pages):
2185 # "<refentry id="$section_id" revision="$mday $month $year">"
2187 OUTPUT.write(REFENTRY.substitute({
2188 'args_desc': args_desc,
2189 'args_synop': args_synop,
2190 'derived': derived,
2191 'extralinks': extralinks,
2192 'functions_details': functions_details,
2193 'functions_synop': functions_synop,
2194 'header': MakeDocHeader('refentry'),
2195 'hierarchy': hierarchy,
2196 'image': image,
2197 'include_output': include_output,
2198 'interfaces': interfaces,
2199 'implementations': implementations,
2200 'long_desc': long_desc,
2201 'object_anchors': object_anchors,
2202 'other_details': other_details,
2203 'other_synop': other_synop,
2204 'prerequisites': prerequisites,
2205 'section_id': section_id,
2206 'see_also': see_also,
2207 'signals_desc': signals_desc,
2208 'signals_synop': signals_synop,
2209 'short_desc': short_desc,
2210 'stability': stability,
2211 'title': title,
2212 'MODULE': MODULE.upper(),
2214 OUTPUT.close()
2216 return common.UpdateFileIfChanged(old_db_file, new_db_file, 0)
2219 def OutputProgramDBFile(program, section_id):
2220 """Outputs the final DocBook file for one program.
2222 Args:
2223 file (str): the name of the file.
2224 section_id (str): the id to use for the toplevel tag.
2226 Returns:
2227 bool: True if the docs where updated
2229 logging.info("Output program docbook for %s", program)
2231 short_desc = SourceSymbolDocs.get(program + ":Short_Description")
2232 if not short_desc or short_desc.strip() == '':
2233 short_desc = ''
2234 else:
2235 # Don't use ConvertMarkDown here for now since we don't want blocks
2236 short_desc = ExpandAbbreviations(program, short_desc)
2237 logging.info("Found short_desc: %s", short_desc)
2239 synopsis = SourceSymbolDocs.get(program + ":Synopsis")
2240 if synopsis and synopsis.strip() != '':
2241 items = synopsis.split(' ')
2242 for i in range(0, len(items)):
2243 parameter = items[i]
2244 choice = "plain"
2245 rep = ''
2247 # first parameter is the command name
2248 if i == 0:
2249 synopsis = "<command>%s</command>\n" % parameter
2250 continue
2252 # square brackets indicate optional parameters, curly brackets
2253 # indicate required parameters ("plain" parameters are also
2254 # mandatory, but do not get extra decoration)
2255 m1 = re.search(r'^\[(.+?)\]$', parameter)
2256 m2 = re.search(r'^\{(.+?)\}$', parameter)
2257 if m1:
2258 choice = "opt"
2259 parameter = m1.group(1)
2260 elif m2:
2261 choice = "req"
2262 parameter = m2.group(1)
2264 # parameters ending in "..." are repeatable
2265 if parameter.endswith('...'):
2266 rep = ' rep=\"repeat\"'
2267 parameter = parameter[:-3]
2269 # italic parameters are replaceable parameters
2270 parameter = re.sub(r'\*(.+?)\*', r'<replaceable>\1</replaceable>', parameter)
2272 synopsis += "<arg choice=\"%s\"%s>" % (choice, rep)
2273 synopsis += parameter
2274 synopsis += "</arg>\n"
2276 logging.info("Found synopsis: %s", synopsis)
2277 else:
2278 synopsis = "<command>%s</command>" % program
2280 long_desc = SourceSymbolDocs.get(program + ":Long_Description")
2281 if not long_desc or long_desc.strip() == '':
2282 long_desc = ''
2283 else:
2284 long_desc = ConvertMarkDown("%s:Long_description" % program, long_desc)
2285 logging.info("Found long_desc: %s", long_desc)
2287 options = ''
2288 o = program + ":Options"
2289 if o in SourceSymbolDocs:
2290 opts = SourceSymbolDocs[o].split('\t')
2292 logging.info('options: %d, %s', len(opts), str(opts))
2294 options = "<refsect1>\n<title>Options</title>\n<variablelist>\n"
2295 for k in range(0, len(opts), 2):
2296 opt_desc = opts[k + 1]
2298 opt_desc = re.sub(r'\*(.+?)\*', r'<replaceable>\1</replaceable>', opt_desc)
2300 options += "<varlistentry>\n<term>"
2301 opt_names = opts[k].split(',')
2302 for i in range(len(opt_names)):
2303 prefix = ', ' if i > 0 else ''
2304 # italic parameters are replaceable parameters
2305 opt_name = re.sub(r'\*(.+?)\*', r'<replaceable>\1</replaceable>', opt_names[i])
2307 options += "%s<option>%s</option>\n" % (prefix, opt_name)
2309 options += "</term>\n"
2310 options += "<listitem><para>%s</para></listitem>\n" % opt_desc
2311 options += "</varlistentry>\n"
2313 options += "</variablelist></refsect1>\n"
2315 exit_status = SourceSymbolDocs.get(program + ":Returns")
2316 if exit_status and exit_status != '':
2317 exit_status = ConvertMarkDown("%s:Returns" % program, exit_status)
2318 exit_status = "<refsect1 id=\"%s.exit-status\">\n<title>Exit Status</title>\n%s\n</refsect1>\n" % (
2319 section_id, exit_status)
2320 else:
2321 exit_status = ''
2323 see_also = SourceSymbolDocs.get(program + ":See_Also")
2324 if not see_also or re.search(r'^\s*(<para>)?\s*(</para>)?\s*$', see_also):
2325 see_also = ''
2326 else:
2327 see_also = ConvertMarkDown("%s:See_Also" % program, see_also)
2328 logging.info("Found see_also: %s", see_also)
2330 if see_also:
2331 see_also = "<refsect1 id=\"%s.see-also\">\n<title>See Also</title>\n%s\n</refsect1>\n" % (section_id, see_also)
2333 old_db_file = os.path.join(DB_OUTPUT_DIR, program + ".xml")
2334 new_db_file = os.path.join(DB_OUTPUT_DIR, program + ".xml.new")
2336 OUTPUT = open(new_db_file, 'w', encoding='utf-8')
2338 OUTPUT.write('''%s
2339 <refentry id="%s">
2340 <refmeta>
2341 <refentrytitle role="top_of_page" id="%s.top_of_page">%s</refentrytitle>
2342 <manvolnum>1</manvolnum>
2343 <refmiscinfo>User Commands</refmiscinfo>
2344 </refmeta>
2345 <refnamediv>
2346 <refname>%s</refname>
2347 <refpurpose>%s</refpurpose>
2348 </refnamediv>
2349 <refsynopsisdiv>
2350 <cmdsynopsis>%s</cmdsynopsis>
2351 </refsynopsisdiv>
2352 <refsect1 id="%s.description" role="desc">
2353 <title role="desc.title">Description</title>
2355 </refsect1>
2356 %s%s%s
2357 </refentry>
2358 ''' % (MakeDocHeader("refentry"), section_id, section_id, program, program, short_desc, synopsis, section_id, long_desc, options, exit_status, see_also))
2359 OUTPUT.close()
2361 return common.UpdateFileIfChanged(old_db_file, new_db_file, 0)
2364 def OutputExtraFile(file):
2365 """Copies an "extra" DocBook file into the output directory, expanding abbreviations.
2367 Args:
2368 file (str): the source file.
2370 Returns:
2371 bool: True if the docs where updated
2374 basename = os.path.basename(file)
2376 old_db_file = os.path.join(DB_OUTPUT_DIR, basename)
2377 new_db_file = os.path.join(DB_OUTPUT_DIR, basename + ".new")
2379 contents = open(file, 'r', encoding='utf-8').read()
2381 with open(new_db_file, 'w', encoding='utf-8') as out:
2382 out.write(ExpandAbbreviations(basename + " file", contents))
2384 return common.UpdateFileIfChanged(old_db_file, new_db_file, 0)
2387 def GetDocbookHeader(main_file):
2388 if os.path.exists(main_file):
2389 INPUT = open(main_file, 'r', encoding='utf-8')
2390 header = ''
2391 for line in INPUT:
2392 if re.search(r'^\s*<(book|chapter|article)', line):
2393 # check that the top-level tagSYSTEM or the doctype decl contain the xinclude namespace decl
2394 if not re.search(r'http:\/\/www.w3.org\/200[13]\/XInclude', line) and \
2395 not re.search(r'http:\/\/www.w3.org\/200[13]\/XInclude', header, flags=re.MULTILINE):
2396 header = ''
2397 break
2399 # if there are SYSTEM ENTITIES here, we should prepend "../" to the path
2400 # FIXME: not sure if we can do this now, as people already work-around the problem
2401 # r'#<!ENTITY % ([a-zA-Z-]+) SYSTEM \"([^/][a-zA-Z./]+)\">', r'<!ENTITY % \1 SYSTEM \"../\2\">';
2402 line = re.sub(
2403 r'<!ENTITY % gtkdocentities SYSTEM "([^"]*)">', r'<!ENTITY % gtkdocentities SYSTEM "../\1">', line)
2404 header += line
2405 INPUT.close()
2406 header = header.strip()
2407 else:
2408 header = '''<?xml version="1.0"?>
2409 <!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
2410 "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
2412 <!ENTITY % local.common.attrib "xmlns:xi CDATA #FIXED 'http://www.w3.org/2003/XInclude'">
2413 <!ENTITY % gtkdocentities SYSTEM "../xml/gtkdocentities.ent">
2414 %gtkdocentities;
2415 ]>'''
2416 return header
2419 def OutputBook(main_file, book_top, book_bottom):
2420 """Outputs the entities that need to be included into the main docbook file for the module.
2422 Args:
2423 book_top (str): the declarations of the entities, which are added
2424 at the top of the main docbook file.
2425 book_bottom (str): the entities, which are added in the main docbook
2426 file at the desired position.
2429 old_file = os.path.join(DB_OUTPUT_DIR, MODULE + "-doc.top")
2430 new_file = os.path.join(DB_OUTPUT_DIR, MODULE + "-doc.top.new")
2432 with open(new_file, 'w', encoding='utf-8') as out:
2433 out.write(book_top)
2435 common.UpdateFileIfChanged(old_file, new_file, 0)
2437 old_file = os.path.join(DB_OUTPUT_DIR, MODULE + "-doc.bottom")
2438 new_file = os.path.join(DB_OUTPUT_DIR, MODULE + "-doc.bottom.new")
2440 with open(new_file, 'w', encoding='utf-8') as out:
2441 out.write(book_bottom)
2443 common.UpdateFileIfChanged(old_file, new_file, 0)
2445 # If the main docbook file hasn't been created yet, we create it here.
2446 # The user can tweak it later.
2447 if main_file and not os.path.exists(main_file):
2448 OUTPUT = open(main_file, 'w', encoding='utf-8')
2450 logging.info("no master doc, create default one at: " + main_file)
2452 OUTPUT.write('''%s
2453 <book id="index" xmlns:xi="http://www.w3.org/2003/XInclude">
2454 <bookinfo>
2455 <title>&package_name; Reference Manual</title>
2456 <releaseinfo>
2457 for &package_string;.
2458 The latest version of this documentation can be found on-line at
2459 <ulink role="online-location" url="http://[SERVER]/&package_name;/index.html">http://[SERVER]/&package_name;/</ulink>.
2460 </releaseinfo>
2461 </bookinfo>
2463 <chapter>
2464 <title>[Insert title here]</title>
2465 %s </chapter>
2466 ''' % (MakeDocHeader("book"), book_bottom))
2467 if os.path.exists('xml/tree_index.sgml'):
2468 OUTPUT.write(''' <chapter id="object-tree">
2469 <title>Object Hierarchy</title>
2470 <xi:include href="xml/tree_index.sgml"/>
2471 </chapter>
2472 ''')
2473 else:
2474 OUTPUT.write(''' <!-- enable this when you use gobject types
2475 <chapter id="object-tree">
2476 <title>Object Hierarchy</title>
2477 <xi:include href="xml/tree_index.sgml"/>
2478 </chapter>
2480 ''')
2482 OUTPUT.write(''' <index id="api-index-full">
2483 <title>API Index</title>
2484 <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
2485 </index>
2486 <index id="deprecated-api-index" role="deprecated">
2487 <title>Index of deprecated API</title>
2488 <xi:include href="xml/api-index-deprecated.xml"><xi:fallback /></xi:include>
2489 </index>
2490 ''')
2491 for version in set(Since.values()):
2492 dash_version = version.replace('.', '-')
2493 OUTPUT.write(''' <index id="api-index-%s" role="%s">
2494 <title>Index of new API in %s</title>
2495 <xi:include href="xml/api-index-%s.xml"><xi:fallback /></xi:include>
2496 </index>
2497 ''' % (dash_version, version, version, version))
2499 if AnnotationsUsed:
2500 OUTPUT.write(''' <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
2501 ''')
2502 else:
2503 OUTPUT.write(''' <!-- enable this when you use gobject introspection annotations
2504 <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
2506 ''')
2508 OUTPUT.write('''</book>
2509 ''')
2511 OUTPUT.close()
2514 def CreateValidSGML(text):
2515 """Turn any chars which are used in XML into entities.
2517 e.g. '<' into '&lt;'
2519 Args:
2520 text (str): the text to turn into proper XML.
2522 Returns:
2523 str: escaped input
2526 text = text.replace('&', '&amp;') # Do this first, or the others get messed up.
2527 text = text.replace('<', '&lt;')
2528 text = text.replace('>', '&gt;')
2529 # browsers render single tabs inconsistently
2530 text = re.sub(r'([^\s])\t([^\s])', r'\1&#160;\2', text)
2531 return text
2534 def ConvertSGMLChars(symbol, text):
2535 """Escape XML chars.
2537 This is used for text in source code comment blocks, to turn
2538 chars which are used in XML into entities, e.g. '<' into
2539 &lt;'. Depending on INLINE_MARKUP_MODE, this is done
2540 unconditionally or only if the character doesn't seem to be
2541 part of an XML construct (tag or entity reference).
2542 Args:
2543 text (str): the text to turn into proper XML.
2545 Returns:
2546 str: escaped input
2549 if INLINE_MARKUP_MODE:
2550 # For the XML/SGML mode only convert to entities outside CDATA sections.
2551 return ModifyXMLElements(text, symbol,
2552 "<!\\[CDATA\\[|<programlisting[^>]*>",
2553 ConvertSGMLCharsEndTag,
2554 ConvertSGMLCharsCallback)
2555 # For the simple non-sgml mode, convert to entities everywhere.
2557 text = re.sub(r'&(?![a-zA-Z#]+;)', r'&amp;', text) # Do this first, or the others get messed up.
2558 # Allow '<' in verbatim markdown
2559 # TODO: we don't want to convert any of them between ``
2560 text = re.sub(r'(?<!`)<', r'&lt;', text)
2561 # Allow '>' at beginning of string for blockquote markdown
2562 text = re.sub(r'''(?<=[^\w\n"'\/-])>''', r'&gt;', text)
2564 return text
2567 def ConvertSGMLCharsEndTag(start_tag):
2568 if start_tag == '<![CDATA[':
2569 return "]]>"
2570 return "</programlisting>"
2573 def ConvertSGMLCharsCallback(text, symbol, tag):
2574 if re.search(r'^<programlisting', tag):
2575 logging.debug('call modifyXML')
2576 # We can handle <programlisting> specially here.
2577 return ModifyXMLElements(text, symbol,
2578 "<!\\[CDATA\\[",
2579 ConvertSGMLCharsEndTag,
2580 ConvertSGMLCharsCallback2)
2581 elif tag == '':
2582 logging.debug('replace entities')
2583 # If we're not in CDATA convert to entities.
2584 text = re.sub(r'&(?![a-zA-Z#]+;)', r'&amp;', text) # Do this first, or the others get messed up.
2585 text = re.sub(r'<(?![a-zA-Z\/!])', r'&lt;', text)
2586 # Allow '>' at beginning of string for blockquote markdown
2587 text = re.sub(r'''(?<=[^\w\n"'\/-])>''', r'&gt;', text)
2589 # Handle "#include <xxxxx>"
2590 text = re.sub(r'#include(\s+)<([^>]+)>', r'#include\1&lt;\2&gt;', text)
2592 return text
2595 def ConvertSGMLCharsCallback2(text, symbol, tag):
2596 # If we're not in CDATA convert to entities.
2597 # We could handle <programlisting> differently, though I'm not sure it helps.
2598 if tag == '':
2599 # replace only if its not a tag
2600 text = re.sub(r'&(?![a-zA-Z#]+;)', r'&amp;', text) # Do this first, or the others get messed up.
2601 text = re.sub(r'<(?![a-zA-Z\/!])', r'&lt;', text)
2602 text = re.sub(r'''(?<![a-zA-Z0-9"'\/-])>''', r'&gt;', text)
2603 # Handle "#include <xxxxx>"
2604 text = re.sub(r'/#include(\s+)<([^>]+)>', r'#include\1&lt;\2&gt;', text)
2606 return text
2609 def ExpandAnnotation(symbol, param_desc):
2610 """This turns annotations into acronym tags.
2611 Args:
2612 symbol (str): the symbol being documented, for error messages.
2613 param_desc (str): the text to expand.
2615 Returns:
2616 str: the remaining param_desc
2617 str: the formatted annotations
2619 param_annotations = ''
2621 # look for annotations at the start of the comment part
2622 # function level annotations don't end with a colon ':'
2623 m = re.search(r'^\s*\((.*?)\)(:|$)', param_desc)
2624 if m:
2625 param_desc = param_desc[m.end():]
2627 annotations = re.split(r'\)\s*\(', m.group(1))
2628 logging.info("annotations for %s: '%s'\n", symbol, m.group(1))
2629 for annotation in annotations:
2630 # need to search for the longest key-match in %AnnotationDefinition
2631 match_length = 0
2632 match_annotation = ''
2634 for annotationdef in AnnotationDefinition:
2635 if annotation.startswith(annotationdef):
2636 if len(annotationdef) > match_length:
2637 match_length = len(annotationdef)
2638 match_annotation = annotationdef
2640 annotation_extra = ''
2641 if match_annotation != '':
2642 m = re.search(match_annotation + r'\s+(.*)', annotation)
2643 if m:
2644 annotation_extra = " " + m.group(1)
2646 AnnotationsUsed[match_annotation] = 1
2647 param_annotations += "[<acronym>%s</acronym>%s]" % (match_annotation, annotation_extra)
2648 else:
2649 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
2650 "unknown annotation \"%s\" in documentation for %s." % (annotation, symbol))
2651 param_annotations += "[%s]" % annotation
2653 param_desc = param_desc.strip()
2654 m = re.search(r'^(.*?)\.*\s*$', param_desc, flags=re.S)
2655 param_desc = m.group(1) + '. '
2657 if param_annotations != '':
2658 param_annotations = "<emphasis role=\"annotation\">%s</emphasis>" % param_annotations
2660 return (param_desc, param_annotations)
2663 def ExpandAbbreviations(symbol, text):
2664 """Expand the shortcut notation for symbol references.
2666 This turns the abbreviations function(), macro(), @param, %constant, and #symbol
2667 into appropriate DocBook markup. CDATA sections and <programlisting> parts
2668 are skipped.
2670 Args:
2671 symbol (str): the symbol being documented, for error messages.
2672 text (str): the text to expand.
2674 Returns:
2675 str: the expanded text
2677 # Note: This is a fallback and normally done in the markdown parser
2679 logging.debug('expand abbreviations for "%s", text: [%s]', symbol, text)
2680 m = re.search(r'\|\[[^\n]*\n(.*)\]\|', text, flags=re.M | re.S)
2681 if m:
2682 logging.debug('replaced entities in code block')
2683 text = text[:m.start(1)] + md_to_db.ReplaceEntities(m.group(1)) + text[m.end(1):]
2685 # Convert "|[" and "]|" into the start and end of program listing examples.
2686 # Support \[<!-- language="C" --> modifiers
2687 text = re.sub(r'\|\[<!-- language="([^"]+)" -->',
2688 r'<informalexample><programlisting role="example" language="\1"><![CDATA[', text)
2689 text = re.sub(r'\|\[', r'<informalexample><programlisting role="example"><![CDATA[', text)
2690 text = re.sub(r'\]\|', r']]></programlisting></informalexample>', text)
2692 # keep CDATA unmodified, preserve ulink tags (ideally we preseve all tags
2693 # as such)
2694 return ModifyXMLElements(text, symbol,
2695 "<!\\[CDATA\\[|<ulink[^>]*>|<programlisting[^>]*>|<!DOCTYPE",
2696 ExpandAbbreviationsEndTag,
2697 ExpandAbbreviationsCallback)
2700 def ExpandAbbreviationsEndTag(start_tag):
2701 # Returns the end tag (as a regexp) corresponding to the given start tag.
2702 if start_tag == r'<!\[CDATA\[':
2703 return "]]>"
2704 if start_tag == "<!DOCTYPE":
2705 return '>'
2706 m = re.search(r'<(\w+)', start_tag)
2707 if m:
2708 return "</%s>" % m.group(1)
2710 logging.warning('no end tag for "%s"', start_tag)
2711 return ''
2714 def ExpandAbbreviationsCallback(text, symbol, tag):
2715 # Called inside or outside each CDATA or <programlisting> section.
2716 if tag.startswith(r'^<programlisting'):
2717 # Handle any embedded CDATA sections.
2718 return ModifyXMLElements(text, symbol,
2719 "<!\\[CDATA\\[",
2720 ExpandAbbreviationsEndTag,
2721 ExpandAbbreviationsCallback2)
2722 elif tag == '':
2723 # NOTE: this is a fallback. It is normally done by the Markdown parser.
2724 # but is also used for OutputExtraFile
2726 # We are outside any CDATA or <programlisting> sections, so we expand
2727 # any gtk-doc abbreviations.
2729 # Convert '@param()'
2730 # FIXME: we could make those also links ($symbol.$2), but that would be less
2731 # useful as the link target is a few lines up or down
2732 text = re.sub(r'(\A|[^\\])\@(\w+((\.|->)\w+)*)\s*\(\)', r'\1<parameter>\2()</parameter>', text)
2734 # Convert 'function()' or 'macro()'.
2735 # if there is abc_*_def() we don't want to make a link to _def()
2736 # FIXME: also handle abc(def(....)) : but that would need to be done recursively :/
2737 def f1(m):
2738 return m.group(1) + MakeXRef(m.group(2), tagify(m.group(2) + "()", "function"))
2739 text = re.sub(r'([^\*.\w])(\w+)\s*\(\)', f1, text)
2740 # handle #Object.func()
2741 text = re.sub(r'(\A|[^\\])#([\w\-:\.]+[\w]+)\s*\(\)', f1, text)
2743 # Convert '@param', but not '\@param'.
2744 text = re.sub(r'(\A|[^\\])\@(\w+((\.|->)\w+)*)', r'\1<parameter>\2</parameter>', text)
2745 text = re.sub(r'/\\\@', r'\@', text)
2747 # Convert '%constant', but not '\%constant'.
2748 # Also allow negative numbers, e.g. %-1.
2749 def f2(m):
2750 return m.group(1) + MakeXRef(m.group(2), tagify(m.group(2), "literal"))
2751 text = re.sub(r'(\A|[^\\])\%(-?\w+)', f2, text)
2752 text = re.sub(r'\\\%', r'\%', text)
2754 # Convert '#symbol', but not '\#symbol'.
2755 def f3(m):
2756 return m.group(1) + MakeHashXRef(m.group(2), "type")
2757 text = re.sub(r'(\A|[^\\])#([\w\-:\.]+[\w]+)', f3, text)
2758 text = re.sub(r'\\#', '#', text)
2760 return text
2763 def ExpandAbbreviationsCallback2(text, symbol, tag):
2764 # This is called inside a <programlisting>
2765 if tag == '':
2766 # We are inside a <programlisting> but outside any CDATA sections,
2767 # so we expand any gtk-doc abbreviations.
2768 # FIXME: why is this different from &ExpandAbbreviationsCallback(),
2769 # why not just call it
2770 text = re.sub(r'#(\w+)', lambda m: '%s;' % MakeHashXRef(m.group(1), ''), text)
2771 elif tag == "<![CDATA[":
2772 # NOTE: this is a fallback. It is normally done by the Markdown parser.
2773 text = ReplaceEntities(text, symbol)
2775 return text
2778 def MakeHashXRef(symbol, tag):
2779 text = symbol
2781 # Check for things like '#include', '#define', and skip them.
2782 if symbol in PreProcessorDirectives:
2783 return "#%s" % symbol
2785 # Get rid of special suffixes ('-struct','-enum').
2786 text = re.sub(r'-struct$', '', text)
2787 text = re.sub(r'-enum$', '', text)
2789 # If the symbol is in the form "Object::signal", then change the symbol to
2790 # "Object-signal" and use "signal" as the text.
2791 if '::' in symbol:
2792 o, s = symbol.split('::', 1)
2793 symbol = '%s-%s' % (o, s)
2794 text = u'“' + s + u'”'
2796 # If the symbol is in the form "Object:property", then change the symbol to
2797 # "Object--property" and use "property" as the text.
2798 if ':' in symbol:
2799 o, p = symbol.split(':', 1)
2800 symbol = '%s--%s' % (o, p)
2801 text = u'“' + p + u'”'
2803 if tag != '':
2804 text = tagify(text, tag)
2806 return MakeXRef(symbol, text)
2809 def ModifyXMLElements(text, symbol, start_tag_regexp, end_tag_func, callback):
2810 """Rewrite XML blocks.
2812 Looks for given XML element tags within the text, and calls
2813 the callback on pieces of text inside & outside those elements.
2814 Used for special handling of text inside things like CDATA
2815 and <programlisting>.
2817 Args:
2818 text (str): the text.
2819 symbol (str): the symbol currently being documented (only used for
2820 error messages).
2821 start_tag_regexp (str): the regular expression to match start tags.
2822 e.g. "<!\\[CDATA\\[|<programlisting[^>]*>" to
2823 match CDATA sections or programlisting elements.
2824 end_tag_func (func): function which is passed the matched start tag
2825 and should return the appropriate end tag string
2826 regexp.
2827 callback - callback called with each part of the text. It is
2828 called with a piece of text, the symbol being
2829 documented, and the matched start tag or '' if the text
2830 is outside the XML elements being matched.
2832 Returns:
2833 str: modified text
2835 before_tag = start_tag = end_tag_regexp = end_tag = None
2836 result = ''
2838 logging.debug('modify xml for symbol: %s, regex: %s, text: [%s]', symbol, start_tag_regexp, text)
2840 m = re.search(start_tag_regexp, text, flags=re.S)
2841 while m:
2842 before_tag = text[:m.start()] # Prematch for last successful match string
2843 start_tag = m.group(0) # Last successful match
2844 text = text[m.end():] # Postmatch for last successful match string
2845 # get the matching end-tag for current tag
2846 end_tag_regexp = end_tag_func(start_tag)
2848 logging.debug('symbol: %s matched start: %s, end_tag: %s, text: [%s]', symbol, start_tag, end_tag_regexp, text)
2850 logging.debug('converting before tag: [%s]', before_tag)
2851 result += callback(before_tag, symbol, '')
2852 result += start_tag
2854 m2 = re.search(end_tag_regexp, text, flags=re.S)
2855 if m2:
2856 before_tag = text[:m2.start()]
2857 end_tag = m2.group(0)
2858 text = text[m2.end():]
2860 logging.debug('symbol: %s matched end %s: text: [%s]', symbol, end_tag, text)
2862 result += callback(before_tag, symbol, start_tag)
2863 result += end_tag
2864 else:
2865 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
2866 "Can't find tag end: %s in docs for: %s." % (end_tag_regexp, symbol))
2867 # Just assume it is all inside the tag.
2868 result += callback(text, symbol, start_tag)
2869 text = ''
2870 m = re.search(start_tag_regexp, text, flags=re.S)
2872 # Handle any remaining text outside the tags.
2873 logging.debug('converting after tag: [%s]', text)
2874 result += callback(text, symbol, '')
2875 logging.debug('results for symbol: %s, text: [%s]', symbol, result)
2877 return result
2880 def tagify(text, elem):
2881 # Adds a tag around some text.
2882 # e.g tagify("Text", "literal") => "<literal>Text</literal>".
2883 return '<' + elem + '>' + text + '</' + elem + '>'
2886 def MakeDocHeader(tag):
2887 """Builds a docbook header for the given tag.
2889 Args:
2890 tag (str): doctype tag
2892 Returns:
2893 str: the docbook header
2895 header = re.sub(r'<!DOCTYPE \w+', r'<!DOCTYPE ' + tag, doctype_header)
2896 # fix the path for book since this is one level up
2897 if tag == 'book':
2898 header = re.sub(
2899 r'<!ENTITY % gtkdocentities SYSTEM "../([a-zA-Z./]+)">', r'<!ENTITY % gtkdocentities SYSTEM "\1">', header)
2900 return header
2903 def MakeXRef(symbol, text=None):
2904 """This returns a cross-reference link to the given symbol.
2906 Though it doesn't try to do this for a few standard C types that it knows
2907 won't be in the documentation.
2909 Args:
2910 symbol (str): the symbol to try to create a XRef to.
2911 text (str): text to put inside the XRef, defaults to symbol
2913 Returns:
2914 str: a docbook link
2916 symbol = symbol.strip()
2917 if not text:
2918 text = symbol
2920 # Get rid of special suffixes ('-struct','-enum').
2921 text = re.sub(r'-struct$', '', text)
2922 text = re.sub(r'-enum$', '', text)
2924 if ' ' in symbol:
2925 return text
2927 logging.info("Getting type link for %s -> %s", symbol, text)
2929 symbol_id = common.CreateValidSGMLID(symbol)
2930 return "<link linkend=\"%s\">%s</link>" % (symbol_id, text)
2933 def MakeIndexterms(symbol, sid):
2934 """This returns a indexterm elements for the given symbol
2936 Args:
2937 symbol (str): the symbol to create indexterms for
2939 Returns:
2940 str: doxbook index terms
2942 terms = ''
2943 sortas = ''
2945 # make the index useful, by ommiting the namespace when sorting
2946 if NAME_SPACE != '':
2947 m = re.search(r'^%s\_?(.*)' % NAME_SPACE, symbol, flags=re.I)
2948 if m:
2949 sortas = ' sortas="%s"' % m.group(1)
2951 if symbol in Deprecated:
2952 terms += "<indexterm zone=\"%s\" role=\"deprecated\"><primary%s>%s</primary></indexterm>" % (
2953 sid, sortas, symbol)
2954 IndexEntriesDeprecated[symbol] = sid
2955 IndexEntriesFull[symbol] = sid
2956 if symbol in Since:
2957 since = Since[symbol].strip()
2958 if since != '':
2959 terms += "<indexterm zone=\"%s\" role=\"%s\"><primary%s>%s</primary></indexterm>" % (
2960 sid, since, sortas, symbol)
2961 IndexEntriesSince[symbol] = sid
2962 IndexEntriesFull[symbol] = sid
2963 if terms == '':
2964 terms += "<indexterm zone=\"%s\"><primary%s>%s</primary></indexterm>" % (sid, sortas, symbol)
2965 IndexEntriesFull[symbol] = sid
2966 return terms
2969 def MakeDeprecationNote(symbol):
2970 """This returns a deprecation warning for the given symbol.
2972 Args:
2973 symbol (str): the symbol to try to create a warning for.
2975 Returns:
2976 str: formatted warning or empty string if symbol is not deprecated
2978 desc = ''
2979 if symbol in Deprecated:
2980 desc += "<warning><para><literal>%s</literal> " % symbol
2981 note = Deprecated[symbol]
2983 m = re.search(r'^\s*([0-9\.]+)\s*:?', note)
2984 if m:
2985 desc += "has been deprecated since version %s and should not be used in newly-written code.</para>" % m.group(
2987 else:
2988 desc += "is deprecated and should not be used in newly-written code.</para>"
2990 note = re.sub(r'^\s*([0-9\.]+)\s*:?\s*', '', note)
2991 note = note.strip()
2993 if note != '':
2994 note = ConvertMarkDown(symbol, note)
2995 desc += " " + note
2997 desc += "</warning>\n"
2999 return desc
3002 def MakeConditionDescription(symbol):
3003 """This returns a sumary of conditions for the given symbol.
3005 Args:
3006 symbol (str): the symbol to create the sumary for.
3008 Returns:
3009 str: formatted text or empty string if no special conditions apply.
3011 desc = ''
3012 if symbol in Deprecated:
3013 if desc != '':
3014 desc += "|"
3015 m = re.search(r'^\s*(.*?)\s*$', Deprecated[symbol])
3016 if m:
3017 desc += "deprecated:%s" % m.group(1)
3018 else:
3019 desc += "deprecated"
3021 if symbol in Since:
3022 if desc != '':
3023 desc += "|"
3024 m = re.search(r'^\s*(.*?)\s*$', Since[symbol])
3025 if m:
3026 desc += "since:%s" % m.group(1)
3027 else:
3028 desc += "since"
3030 if symbol in StabilityLevel:
3031 if desc != '':
3032 desc += "|"
3034 desc += "stability:" + StabilityLevel[symbol]
3036 if desc != '':
3037 cond = re.sub(r'"', r'&quot;', desc)
3038 desc = ' condition=\"%s\"' % cond
3039 logging.info("condition for '%s' = '%s'", symbol, desc)
3041 return desc
3044 def GetHierarchy(gobject, hierarchy):
3045 """Generate the object inheritance graph.
3047 Returns the DocBook output describing the ancestors and
3048 immediate children of a GObject subclass. It uses the
3049 global Objects and ObjectLevels arrays to walk the tree.
3051 Args:
3052 object (str): the GtkObject subclass.
3053 hierarchy (list) - previous hierarchy
3055 Returns:
3056 list: lines of docbook describing the hierarchy
3058 # Find object in the objects array.
3059 found = False
3060 children = []
3061 level = 0
3062 j = 0
3063 for i in range(len(Objects)):
3064 if found:
3065 if ObjectLevels[i] <= level:
3066 break
3068 elif ObjectLevels[i] == level + 1:
3069 children.append(Objects[i])
3071 elif Objects[i] == gobject:
3072 found = True
3073 j = i
3074 level = ObjectLevels[i]
3076 if not found:
3077 return hierarchy
3079 logging.info("=== Hierachy for: %s (%d existing entries) ===", gobject, len(hierarchy))
3081 # Walk up the hierarchy, pushing ancestors onto the ancestors array.
3082 ancestors = [gobject]
3083 logging.info("Level: %s", level)
3084 while level > 1:
3085 j -= 1
3086 if ObjectLevels[j] < level:
3087 ancestors.append(Objects[j])
3088 level = ObjectLevels[j]
3089 logging.info("Level: %s", level)
3091 # Output the ancestors, indented and with links.
3092 logging.info('%d ancestors', len(ancestors))
3093 last_index = 0
3094 level = 1
3095 for i in range(len(ancestors) - 1, -1, -1):
3096 ancestor = ancestors[i]
3097 ancestor_id = common.CreateValidSGMLID(ancestor)
3098 indent = ' ' * (level * 4)
3099 # Don't add a link to the current object, i.e. when i == 0.
3100 if i > 0:
3101 entry_text = indent + "<link linkend=\"%s\">%s</link>" % (ancestor_id, ancestor)
3102 alt_text = indent + ancestor
3103 else:
3104 entry_text = indent + ancestor
3105 alt_text = indent + "<link linkend=\"%s\">%s</link>" % (ancestor_id, ancestor)
3107 logging.info("Checking for '%s' or '%s'", entry_text, alt_text)
3108 # Check if we already have this object
3109 index = -1
3110 for j in range(len(hierarchy)):
3111 if hierarchy[j] == entry_text or (hierarchy[j] == alt_text):
3112 index = j
3113 break
3114 if index == -1:
3115 # We have a new entry, find insert position in alphabetical order
3116 found = False
3117 for j in range(last_index, len(hierarchy)):
3118 if not re.search(r'^' + indent, hierarchy[j]):
3119 last_index = j
3120 found = True
3121 break
3122 elif re.search(r'^%s[^ ]' % indent, hierarchy[j]):
3123 stripped_text = hierarchy[j]
3124 if r'<link linkend' not in entry_text:
3125 stripped_text = re.sub(r'<link linkend="[A-Za-z]*">', '', stripped_text)
3126 stripped_text = re.sub(r'</link>', '', stripped_text)
3128 if entry_text < stripped_text:
3129 last_index = j
3130 found = True
3131 break
3133 # Append to bottom
3134 if not found:
3135 last_index = len(hierarchy)
3137 logging.debug('insert at %d: %s', last_index, entry_text)
3138 hierarchy.insert(last_index, entry_text)
3139 last_index += 1
3140 else:
3141 # Already have this one, make sure we use the not linked version
3142 if r'<link linkend' not in entry_text:
3143 hierarchy[j] = entry_text
3145 # Remember index as base insert point
3146 last_index = index + 1
3148 level += 1
3150 # Output the children, indented and with links.
3151 logging.info('%d children', len(children))
3152 for i in range(len(children)):
3153 sid = common.CreateValidSGMLID(children[i])
3154 indented_text = ' ' * (level * 4) + "<link linkend=\"%s\">%s</link>" % (sid, children[i])
3155 logging.debug('insert at %d: %s', last_index, indented_text)
3156 hierarchy.insert(last_index, indented_text)
3157 last_index += 1
3158 return hierarchy
3161 def GetInterfaces(gobject):
3162 """Generate interface implementation graph.
3164 Returns the DocBook output describing the interfaces
3165 implemented by a class. It uses the global Interfaces hash.
3167 Args:
3168 object (str): the GObject subclass.
3170 Returns:
3171 str: implemented interfaces
3173 text = ''
3174 # Find object in the objects array.
3175 if gobject in Interfaces:
3176 ifaces = Interfaces[gobject].split()
3177 text = '''<para>
3178 %s implements
3179 ''' % gobject
3180 count = len(ifaces)
3181 for i in range(count):
3182 sid = common.CreateValidSGMLID(ifaces[i])
3183 text += " <link linkend=\"%s\">%s</link>" % (sid, ifaces[i])
3184 if i < count - 2:
3185 text += ', '
3186 elif i < count - 1:
3187 text += ' and '
3188 else:
3189 text += '.'
3190 text += '</para>\n'
3191 return text
3194 def GetImplementations(gobject):
3195 """Generate interface usage graph.
3197 Returns the DocBook output describing the implementations
3198 of an interface. It uses the global Interfaces hash.
3200 Args:
3201 object (str): the GObject subclass.
3203 Returns:
3204 str: interface implementations
3206 text = ''
3207 impls = []
3208 for key in Interfaces:
3209 if re.search(r'\b%s\b' % gobject, Interfaces[key]):
3210 impls.append(key)
3212 count = len(impls)
3213 if count > 0:
3214 impls.sort()
3215 text = '''<para>
3216 %s is implemented by
3217 ''' % gobject
3218 for i in range(count):
3219 sid = common.CreateValidSGMLID(impls[i])
3220 text += " <link linkend=\"%s\">%s</link>" % (sid, impls[i])
3221 if i < count - 2:
3222 text += ', '
3223 elif i < count - 1:
3224 text += ' and '
3225 else:
3226 text += '.'
3227 text += '</para>\n'
3228 return text
3231 def GetPrerequisites(iface):
3232 """Generates interface requirements.
3234 Returns the DocBook output describing the prerequisites
3235 of an interface. It uses the global Prerequisites hash.
3236 Args:
3237 iface (str): the interface.
3239 Returns:
3240 str: required interfaces
3243 text = ''
3244 if iface in Prerequisites:
3245 text = '''<para>
3246 %s requires
3247 ''' % iface
3248 prereqs = Prerequisites[iface].split()
3249 count = len(prereqs)
3250 for i in range(count):
3251 sid = common.CreateValidSGMLID(prereqs[i])
3252 text += " <link linkend=\"%s\">%s</link>" % (sid, prereqs[i])
3253 if i < count - 2:
3254 text += ', '
3255 elif i < count - 1:
3256 text += ' and '
3257 else:
3258 text += '.'
3259 text += '</para>\n'
3260 return text
3263 def GetDerived(iface):
3265 Returns the DocBook output describing the derived interfaces
3266 of an interface. It uses the global %Prerequisites hash.
3268 Args:
3269 iface (str): the interface.
3271 Returns:
3272 str: derived interfaces
3274 text = ''
3275 derived = []
3276 for key in Prerequisites:
3277 if re.search(r'\b%s\b' % iface, Prerequisites[key]):
3278 derived.append(key)
3280 count = len(derived)
3281 if count > 0:
3282 derived.sort()
3283 text = '''<para>
3284 %s is required by
3285 ''' % iface
3286 for i in range(count):
3287 sid = common.CreateValidSGMLID(derived[i])
3288 text += " <link linkend=\"%s\">%s</link>" % (sid, derived[i])
3289 if i < count - 2:
3290 text += ', '
3291 elif i < count - 1:
3292 text += ' and '
3293 else:
3294 text += '.'
3295 text += '</para>\n'
3296 return text
3299 def GetSignals(gobject):
3300 """Generate signal docs.
3302 Returns the synopsis and detailed description DocBook output
3303 for the signal handlers of a given GObject subclass.
3305 Args:
3306 object (str): the GObject subclass, e.g. 'GtkButton'.
3308 Returns:
3309 str: signal docs
3311 synop = ''
3312 desc = ''
3314 for i in range(len(SignalObjects)):
3315 if SignalObjects[i] == gobject:
3316 logging.info("Found signal: %s", SignalNames[i])
3317 name = SignalNames[i]
3318 symbol = '%s::%s' % (gobject, name)
3319 sid = common.CreateValidSGMLID('%s-%s' % (gobject, name))
3321 desc += u"<refsect2 id=\"%s\" role=\"signal\"><title>The <literal>“%s”</literal> signal</title>\n" % (
3322 sid, name)
3323 desc += MakeIndexterms(symbol, sid)
3324 desc += "\n"
3325 desc += OutputSymbolExtraLinks(symbol)
3327 desc += "<programlisting language=\"C\">"
3329 m = re.search(r'\s*(const\s+)?(\w+)\s*(\**)', SignalReturns[i])
3330 type_modifier = m.group(1) or ''
3331 gtype = m.group(2)
3332 pointer = m.group(3)
3333 xref = MakeXRef(gtype, tagify(gtype, "returnvalue"))
3335 ret_type_output = '%s%s%s' % (type_modifier, xref, pointer)
3336 callback_name = "user_function"
3337 desc += '%s\n%s (' % (ret_type_output, callback_name)
3339 indentation = ' ' * (len(callback_name) + 2)
3341 sourceparams = SourceSymbolParams.get(symbol)
3342 sourceparam_names = None
3343 if sourceparams:
3344 sourceparam_names = list(sourceparams) # keys as list
3345 params = SignalPrototypes[i].splitlines()
3346 type_len = len("gpointer")
3347 name_len = len("user_data")
3348 # do two passes, the first one is to calculate padding
3349 for l in range(2):
3350 for j in range(len(params)):
3351 param_name = None
3352 # allow alphanumerics, '_', '[' & ']' in param names
3353 m = re.search(r'^\s*(\w+)\s*(\**)\s*([\w\[\]]+)\s*$', params[j])
3354 if m:
3355 gtype = m.group(1)
3356 pointer = m.group(2)
3357 if sourceparam_names:
3358 if j < len(sourceparam_names):
3359 param_name = sourceparam_names[j]
3360 logging.info('from sourceparams: "%s" (%d: %s)', param_name, j, params[j])
3361 # we're mssing the docs for this param, don't warn here though
3362 else:
3363 param_name = m.group(3)
3364 logging.info('from params: "%s" (%d: %s)', param_name, j, params[j])
3366 if not param_name:
3367 param_name = "arg%d" % j
3369 if l == 0:
3370 if len(gtype) + len(pointer) > type_len:
3371 type_len = len(gtype) + len(pointer)
3372 if len(param_name) > name_len:
3373 name_len = len(param_name)
3374 else:
3375 logging.info("signal arg[%d]: '%s'", j, param_name)
3376 xref = MakeXRef(gtype, tagify(gtype, "type"))
3377 pad = ' ' * (type_len - len(gtype) - len(pointer))
3378 desc += '%s%s %s%s,\n' % (xref, pad, pointer, param_name)
3379 desc += indentation
3381 else:
3382 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
3383 "Can't parse arg: %s\nArgs:%s" % (params[j], SignalPrototypes[i]))
3385 xref = MakeXRef("gpointer", tagify("gpointer", "type"))
3386 pad = ' ' * (type_len - len("gpointer"))
3387 desc += '%s%s user_data)' % (xref, pad)
3388 desc += "</programlisting>\n"
3390 flags = SignalFlags[i]
3391 flags_string = ''
3392 if flags:
3393 if 'f' in flags:
3394 flags_string = "<link linkend=\"G-SIGNAL-RUN-FIRST:CAPS\">Run First</link>"
3396 elif 'l' in flags:
3397 flags_string = "<link linkend=\"G-SIGNAL-RUN-LAST:CAPS\">Run Last</link>"
3399 elif 'c' in flags:
3400 flags_string = "<link linkend=\"G-SIGNAL-RUN-CLEANUP:CAPS\">Cleanup</link>"
3401 flags_string = "Cleanup"
3403 if 'r' in flags:
3404 if flags_string:
3405 flags_string += " / "
3406 flags_string = "<link linkend=\"G-SIGNAL-NO-RECURSE:CAPS\">No Recursion</link>"
3408 if 'd' in flags:
3409 if flags_string:
3410 flags_string += " / "
3411 flags_string = "<link linkend=\"G-SIGNAL-DETAILED:CAPS\">Has Details</link>"
3413 if 'a' in flags:
3414 if flags_string:
3415 flags_string += " / "
3416 flags_string = "<link linkend=\"G-SIGNAL-ACTION:CAPS\">Action</link>"
3418 if 'h' in flags:
3419 if flags_string:
3420 flags_string += " / "
3421 flags_string = "<link linkend=\"G-SIGNAL-NO-HOOKS:CAPS\">No Hooks</link>"
3423 synop += "<row><entry role=\"signal_type\">%s</entry><entry role=\"signal_name\"><link linkend=\"%s\">%s</link></entry><entry role=\"signal_flags\">%s</entry></row>\n" % (
3424 ret_type_output, sid, name, flags_string)
3426 parameters = OutputParamDescriptions("SIGNAL", symbol, None)
3427 logging.info("formatted signal params: '%s' -> '%s'", symbol, parameters)
3429 AllSymbols[symbol] = 1
3430 if symbol in SymbolDocs:
3431 symbol_docs = ConvertMarkDown(symbol, SymbolDocs[symbol])
3433 desc += symbol_docs
3435 if not IsEmptyDoc(SymbolDocs[symbol]):
3436 AllDocumentedSymbols[symbol] = 1
3438 if symbol in SymbolAnnotations:
3439 param_desc = SymbolAnnotations[symbol]
3440 (param_desc, param_annotations) = ExpandAnnotation(symbol, param_desc)
3441 if param_annotations != '':
3442 desc += "\n<para>%s</para>" % param_annotations
3444 desc += MakeDeprecationNote(symbol)
3446 desc += parameters
3447 if flags_string:
3448 desc += "<para>Flags: %s</para>\n" % flags_string
3450 desc += OutputSymbolTraits(symbol)
3451 desc += "</refsect2>"
3453 return (synop, desc)
3456 def GetArgs(gobject):
3457 """Generate property docs.
3459 Returns the synopsis and detailed description DocBook output
3460 for the Args of a given GtkObject subclass.
3462 Args:
3463 object (str): the GObject subclass, e.g. 'GtkButton'.
3465 Returns:
3466 str: property docs
3468 synop = ''
3469 desc = ''
3470 child_synop = ''
3471 child_desc = ''
3472 style_synop = ''
3473 style_desc = ''
3475 for i in range(len(ArgObjects)):
3476 if ArgObjects[i] == gobject:
3477 logging.info("Found arg: %s", ArgNames[i])
3478 name = ArgNames[i]
3479 flags = ArgFlags[i]
3480 flags_string = ''
3481 kind = ''
3482 id_sep = ''
3484 if 'c' in flags:
3485 kind = "child property"
3486 id_sep = "c-"
3487 elif 's' in flags:
3488 kind = "style property"
3489 id_sep = "s-"
3490 else:
3491 kind = "property"
3493 # Remember only one colon so we don't clash with signals.
3494 symbol = '%s:%s' % (gobject, name)
3495 # use two dashes and ev. an extra separator here for the same reason.
3496 sid = common.CreateValidSGMLID('%s--%s%s' % (gobject, id_sep, name))
3498 atype = ArgTypes[i]
3499 type_output = None
3500 arange = ArgRanges[i]
3501 range_output = CreateValidSGML(arange)
3502 default = ArgDefaults[i]
3503 default_output = CreateValidSGML(default)
3505 if atype == "GtkString":
3506 atype = "char&#160;*"
3508 if atype == "GtkSignal":
3509 atype = "GtkSignalFunc, gpointer"
3510 type_output = MakeXRef("GtkSignalFunc") + ", " + MakeXRef("gpointer")
3511 elif re.search(r'^(\w+)\*$', atype):
3512 m = re.search(r'^(\w+)\*$', atype)
3513 type_output = MakeXRef(m.group(1), tagify(m.group(1), "type")) + "&#160;*"
3514 else:
3515 type_output = MakeXRef(atype, tagify(atype, "type"))
3517 if 'r' in flags:
3518 flags_string = "Read"
3520 if 'w' in flags:
3521 if flags_string:
3522 flags_string += " / "
3523 flags_string += "Write"
3525 if 'x' in flags:
3526 if flags_string:
3527 flags_string += " / "
3528 flags_string += "Construct"
3530 if 'X' in flags:
3531 if flags_string:
3532 flags_string += " / "
3533 flags_string += "Construct Only"
3535 AllSymbols[symbol] = 1
3536 blurb = ''
3537 if symbol in SymbolDocs and not IsEmptyDoc(SymbolDocs[symbol]):
3538 blurb = ConvertMarkDown(symbol, SymbolDocs[symbol])
3539 logging.info(".. [%s][%s]", SymbolDocs[symbol], blurb)
3540 AllDocumentedSymbols[symbol] = 1
3542 else:
3543 if ArgBlurbs[i] != '':
3544 blurb = "<para>" + CreateValidSGML(ArgBlurbs[i]) + "</para>"
3545 AllDocumentedSymbols[symbol] = 1
3546 else:
3547 # FIXME: print a warning?
3548 logging.info(".. no description")
3550 pad1 = ''
3551 if len(name) < 24:
3552 pad1 = " " * (24 - len(name))
3554 arg_synop = "<row><entry role=\"property_type\">%s</entry><entry role=\"property_name\"><link linkend=\"%s\">%s</link></entry><entry role=\"property_flags\">%s</entry></row>\n" % (
3555 type_output, sid, name, flags_string)
3556 arg_desc = u"<refsect2 id=\"%s\" role=\"property\"><title>The <literal>“%s”</literal> %s</title>\n" % (
3557 sid, name, kind)
3558 arg_desc += MakeIndexterms(symbol, sid)
3559 arg_desc += "\n"
3560 arg_desc += OutputSymbolExtraLinks(symbol)
3562 arg_desc += u"<programlisting> “%s%s %s</programlisting>\n" % (name, pad1, type_output)
3563 arg_desc += blurb
3564 if symbol in SymbolAnnotations:
3565 param_desc = SymbolAnnotations[symbol]
3566 (param_desc, param_annotations) = ExpandAnnotation(symbol, param_desc)
3567 if param_annotations != '':
3568 arg_desc += "\n<para>%s</para>" % param_annotations
3570 arg_desc += MakeDeprecationNote(symbol)
3572 if flags_string:
3573 arg_desc += "<para>Flags: %s</para>\n" % flags_string
3575 if arange != '':
3576 arg_desc += "<para>Allowed values: %s</para>\n" % range_output
3578 if default != '':
3579 arg_desc += "<para>Default value: %s</para>\n" % default_output
3581 arg_desc += OutputSymbolTraits(symbol)
3582 arg_desc += "</refsect2>\n"
3584 if 'c' in flags:
3585 child_synop += arg_synop
3586 child_desc += arg_desc
3588 elif 's' in flags:
3589 style_synop += arg_synop
3590 style_desc += arg_desc
3592 else:
3593 synop += arg_synop
3594 desc += arg_desc
3596 return (synop, child_synop, style_synop, desc, child_desc, style_desc)
3599 def IgnorePath(path, source_dirs, ignore_files):
3600 for sdir in source_dirs:
3601 # Cut off base directory
3602 m1 = re.search(r'^%s/(.*)$' % re.escape(sdir), path)
3603 if m1:
3604 # Check if the filename is in the ignore list.
3605 m2 = re.search(r'(\s|^)%s(\s|$)' % re.escape(m1.group(1)), ignore_files)
3606 if m2:
3607 logging.info("Skipping path: %s", path)
3608 return True
3609 else:
3610 logging.info("No match for: %s", m1.group(1))
3611 else:
3612 logging.info("No match for: %s", path)
3613 return False
3616 def ReadSourceDocumentation(source_dir, suffix_list, source_dirs, ignore_files):
3617 """Read the documentation embedded in comment blocks in the source code.
3619 It recursively descends the source directory looking for source files and
3620 scans them looking for specially-formatted comment blocks.
3622 Args:
3623 source_dir (str): the directory to scan.
3624 suffix_list (list): extensions to check
3626 if IgnorePath(source_dir, source_dirs, ignore_files):
3627 return
3629 logging.info("Scanning source directory: %s", source_dir)
3631 # This array holds any subdirectories found.
3632 subdirs = []
3634 for ifile in sorted(os.listdir(source_dir)):
3635 logging.debug("... : %s", ifile)
3636 if ifile.startswith('.'):
3637 continue
3638 fname = os.path.join(source_dir, ifile)
3639 if os.path.isdir(fname):
3640 subdirs.append(fname)
3641 else:
3642 for suffix in suffix_list:
3643 if ifile.endswith(suffix):
3644 if not IgnorePath(fname, source_dirs, ignore_files):
3645 ScanSourceFile(fname, ignore_files)
3646 break
3648 # Now recursively scan the subdirectories.
3649 for sdir in subdirs:
3650 ReadSourceDocumentation(sdir, suffix_list, source_dirs, ignore_files)
3653 def ScanSourceFile(ifile, ignore_files):
3654 """Scans one source file looking for specially-formatted comment blocks.
3656 Later MergeSourceDocumentation() is copying over the doc blobs that are not
3657 suppressed/ignored.
3659 Args:
3660 file (str): the file to scan.
3662 m = re.search(r'^.*[\/\\]([^\/\\]*)$', ifile)
3663 if m:
3664 basename = m.group(1)
3665 else:
3666 common.LogWarning(ifile, 1, "Can't find basename for this filename.")
3667 basename = ifile
3669 # Check if the basename is in the list of files to ignore.
3670 if re.search(r'(\s|^)%s(\s|$)' % re.escape(basename), ignore_files):
3671 logging.info("Skipping source file: %s", ifile)
3672 return
3674 logging.info("Scanning source file: %s", ifile)
3676 SRCFILE = open(ifile, 'r', encoding='utf-8')
3677 in_comment_block = False
3678 symbol = None
3679 in_part = ''
3680 description = ''
3681 return_desc = ''
3682 since_desc = stability_desc = deprecated_desc = ''
3683 params = OrderedDict()
3684 param_name = None
3685 line_number = 0
3686 for line in SRCFILE:
3687 line_number += 1
3688 # Look for the start of a comment block.
3689 if not in_comment_block:
3690 if re.search(r'^\s*/\*.*\*/', line):
3691 # one-line comment - not gtkdoc
3692 pass
3693 elif re.search(r'^\s*/\*\*\s', line):
3694 logging.info("Found comment block start")
3696 in_comment_block = True
3698 # Reset all the symbol data.
3699 symbol = ''
3700 in_part = ''
3701 description = ''
3702 return_desc = ''
3703 since_desc = ''
3704 deprecated_desc = ''
3705 stability_desc = ''
3706 params = OrderedDict()
3707 param_name = None
3709 continue
3711 # We're in a comment block. Check if we've found the end of it.
3712 if re.search(r'^\s*\*+/', line):
3713 if not symbol:
3714 # maybe its not even meant to be a gtk-doc comment?
3715 common.LogWarning(ifile, line_number, "Symbol name not found at the start of the comment block.")
3716 else:
3717 # Add the return value description onto the end of the params.
3718 if return_desc:
3719 # TODO(ensonic): check for duplicated Return docs
3720 # common.LogWarning(file, line_number, "Multiple Returns for %s." % symbol)
3721 params['Returns'] = return_desc
3723 # Convert special characters
3724 description = ConvertSGMLChars(symbol, description)
3725 for (param_name, param_desc) in params.items():
3726 params[param_name] = ConvertSGMLChars(symbol, param_desc)
3728 # Handle Section docs
3729 m = re.search(r'SECTION:\s*(.*)', symbol)
3730 m2 = re.search(r'PROGRAM:\s*(.*)', symbol)
3731 if m:
3732 real_symbol = m.group(1)
3733 long_descr = real_symbol + ":Long_Description"
3735 if long_descr not in KnownSymbols or KnownSymbols[long_descr] != 1:
3736 common.LogWarning(
3737 ifile, line_number, "Section %s is not defined in the %s-sections.txt file." % (real_symbol, MODULE))
3739 logging.info("SECTION DOCS found in source for : '%s'", real_symbol)
3740 for param_name, param_desc in params.items():
3741 logging.info(" '" + param_name + "'")
3742 param_name = param_name.lower()
3743 key = None
3744 if param_name == "short_description":
3745 key = real_symbol + ":Short_Description"
3746 elif param_name == "see_also":
3747 key = real_symbol + ":See_Also"
3748 elif param_name == "title":
3749 key = real_symbol + ":Title"
3750 elif param_name == "stability":
3751 key = real_symbol + ":Stability_Level"
3752 elif param_name == "section_id":
3753 key = real_symbol + ":Section_Id"
3754 elif param_name == "include":
3755 key = real_symbol + ":Include"
3756 elif param_name == "image":
3757 key = real_symbol + ":Image"
3759 if key:
3760 SourceSymbolDocs[key] = param_desc
3761 SourceSymbolSourceFile[key] = ifile
3762 SourceSymbolSourceLine[key] = line_number
3764 SourceSymbolDocs[long_descr] = description
3765 SourceSymbolSourceFile[long_descr] = ifile
3766 SourceSymbolSourceLine[long_descr] = line_number
3767 elif m2:
3768 real_symbol = m2.group(1)
3769 key = None
3770 section_id = None
3772 logging.info("PROGRAM DOCS found in source for '%s'", real_symbol)
3773 for param_name, param_desc in params.items():
3774 logging.info("PROGRAM key %s: '%s'", real_symbol, param_name)
3775 param_name = param_name.lower()
3776 key = None
3777 if param_name == "short_description":
3778 key = real_symbol + ":Short_Description"
3779 elif param_name == "see_also":
3780 key = real_symbol + ":See_Also"
3781 elif param_name == "section_id":
3782 key = real_symbol + ":Section_Id"
3783 elif param_name == "synopsis":
3784 key = real_symbol + ":Synopsis"
3785 elif param_name == "returns":
3786 key = real_symbol + ":Returns"
3787 elif re.search(r'^(-.*)', param_name):
3788 logging.info("PROGRAM opts: '%s': '%s'", param_name, param_desc)
3789 key = real_symbol + ":Options"
3790 opts = []
3791 opts_str = SourceSymbolDocs.get(key)
3792 if opts_str:
3793 opts = opts_str.split('\t')
3794 opts.append(param_name)
3795 opts.append(param_desc)
3797 logging.info("Setting options for symbol: %s: '%s'", real_symbol, '\t'.join(opts))
3798 SourceSymbolDocs[key] = '\t'.join(opts)
3799 continue
3801 if key:
3802 logging.info("PROGRAM value %s: '%s'", real_symbol, param_desc.rstrip())
3803 SourceSymbolDocs[key] = param_desc.rstrip()
3804 SourceSymbolSourceFile[key] = ifile
3805 SourceSymbolSourceLine[key] = line_number
3807 long_descr = real_symbol + ":Long_Description"
3808 SourceSymbolDocs[long_descr] = description
3809 SourceSymbolSourceFile[long_descr] = ifile
3810 SourceSymbolSourceLine[long_descr] = line_number
3812 section_id = SourceSymbolDocs.get(real_symbol + ":Section_Id")
3813 if section_id and section_id.strip() != '':
3814 # Remove trailing blanks and use as is
3815 section_id = section_id.rstrip()
3816 else:
3817 section_id = common.CreateValidSGMLID('%s-%s' % (MODULE, real_symbol))
3818 OutputProgramDBFile(real_symbol, section_id)
3820 else:
3821 logging.info("SYMBOL DOCS found in source for : '%s'", symbol)
3822 SourceSymbolDocs[symbol] = description
3823 SourceSymbolParams[symbol] = params
3824 SourceSymbolSourceFile[symbol] = ifile
3825 SourceSymbolSourceLine[symbol] = line_number
3827 if since_desc:
3828 arr = since_desc.splitlines()
3829 since_desc = arr[0].strip()
3830 extra_lines = arr[1:]
3831 logging.info("Since(%s) : [%s]", symbol, since_desc)
3832 Since[symbol] = ConvertSGMLChars(symbol, since_desc)
3833 if len(extra_lines) > 1:
3834 common.LogWarning(ifile, line_number, "multi-line since docs found")
3836 if stability_desc:
3837 stability_desc = ParseStabilityLevel(
3838 stability_desc, ifile, line_number, "Stability level for %s" % symbol)
3839 StabilityLevel[symbol] = ConvertSGMLChars(symbol, stability_desc)
3841 if deprecated_desc:
3842 if symbol not in Deprecated:
3843 # don't warn for signals and properties
3844 # if ($symbol !~ m/::?(.*)/)
3845 if symbol in DeclarationTypes:
3846 common.LogWarning(ifile, line_number,
3847 "%s is deprecated in the inline comments, but no deprecation guards were found around the declaration. (See the --deprecated-guards option for gtkdoc-scan.)" % symbol)
3849 Deprecated[symbol] = ConvertSGMLChars(symbol, deprecated_desc)
3851 in_comment_block = False
3852 continue
3854 # Get rid of ' * ' at start of every line in the comment block.
3855 line = re.sub(r'^\s*\*\s?', '', line)
3856 # But make sure we don't get rid of the newline at the end.
3857 if not line.endswith('\n'):
3858 line = line + "\n"
3860 logging.info("scanning :%s", line.strip())
3862 # If we haven't found the symbol name yet, look for it.
3863 if not symbol:
3864 m1 = re.search(r'^\s*(SECTION:\s*\S+)', line)
3865 m2 = re.search(r'^\s*(PROGRAM:\s*\S+)', line)
3866 m3 = re.search(r'^\s*([\w:-]*\w)\s*:?\s*(\(.+?\)\s*)*$', line)
3867 if m1:
3868 symbol = m1.group(1)
3869 logging.info("SECTION DOCS found in source for : '%s'", symbol)
3870 elif m2:
3871 symbol = m2.group(1)
3872 logging.info("PROGRAM DOCS found in source for : '%s'", symbol)
3873 elif m3:
3874 symbol = m3.group(1)
3875 annotation = m3.group(2)
3876 logging.info("SYMBOL DOCS found in source for : '%s'", symbol)
3877 if annotation:
3878 annotation = annotation.strip()
3879 if annotation != '':
3880 SymbolAnnotations[symbol] = annotation
3881 logging.info("remaining text for %s: '%s'", symbol, annotation)
3883 continue
3885 if in_part == "description":
3886 # Get rid of 'Description:'
3887 line = re.sub(r'^\s*Description:', '', line)
3889 m1 = re.search(r'^\s*(returns|return\s+value):', line, flags=re.I)
3890 m2 = re.search(r'^\s*since:', line, flags=re.I)
3891 m3 = re.search(r'^\s*deprecated:', line, flags=re.I)
3892 m4 = re.search(r'^\s*stability:', line, flags=re.I)
3894 if m1:
3895 # we're in param section and have not seen the blank line
3896 if in_part != '':
3897 return_desc = line[m1.end():]
3898 in_part = "return"
3899 continue
3901 if m2:
3902 # we're in param section and have not seen the blank line
3903 if in_part != "param":
3904 since_desc = line[m2.end():]
3905 in_part = "since"
3906 continue
3908 elif m3:
3909 # we're in param section and have not seen the blank line
3910 if in_part != "param":
3911 deprecated_desc = line[m3.end():]
3912 in_part = "deprecated"
3913 continue
3915 elif m4:
3916 stability_desc = line[m4.end():]
3917 in_part = "stability"
3918 continue
3920 if in_part == "description":
3921 description += line
3922 continue
3923 elif in_part == "return":
3924 return_desc += line
3925 continue
3926 elif in_part == "since":
3927 since_desc += line
3928 continue
3929 elif in_part == "stability":
3930 stability_desc += line
3931 continue
3932 elif in_part == "deprecated":
3933 deprecated_desc += line
3934 continue
3936 # We must be in the parameters. Check for the empty line below them.
3937 if re.search(r'^\s*$', line):
3938 in_part = "description"
3939 continue
3941 # Look for a parameter name.
3942 m = re.search(r'^\s*@(.+?)\s*:\s*', line)
3943 if m:
3944 param_name = m.group(1)
3945 param_desc = line[m.end():]
3947 logging.info("Found parameter: %s", param_name)
3948 # Allow varargs variations
3949 if re.search(r'^\.\.\.$', param_name):
3950 param_name = "..."
3952 logging.info("Found param for symbol %s : '%s'= '%s'", symbol, param_name, line)
3954 params[param_name] = param_desc
3955 in_part = "param"
3956 continue
3957 elif in_part == '':
3958 logging.info("continuation for %s annotation '%s'", symbol, line)
3959 annotation = re.sub(r'^\s+|\s+$', '', line)
3960 if symbol in SymbolAnnotations:
3961 SymbolAnnotations[symbol] += annotation
3962 else:
3963 SymbolAnnotations[symbol] = annotation
3964 continue
3966 # We must be in the middle of a parameter description, so add it on
3967 # to the last element in @params.
3968 if not param_name:
3969 common.LogWarning(file, line_number, "Parsing comment block file : parameter expected, but got '%s'" % line)
3970 else:
3971 params[param_name] += line
3973 SRCFILE.close()
3976 def OutputMissingDocumentation():
3977 """Outputs report of documentation coverage to a file.
3979 Returns:
3980 bool: True if the report was updated
3982 old_undocumented_file = os.path.join(ROOT_DIR, MODULE + "-undocumented.txt")
3983 new_undocumented_file = os.path.join(ROOT_DIR, MODULE + "-undocumented.new")
3985 n_documented = 0
3986 n_incomplete = 0
3987 total = 0
3988 symbol = None
3989 percent = None
3990 buffer = ''
3991 buffer_deprecated = ''
3992 buffer_descriptions = ''
3994 UNDOCUMENTED = open(new_undocumented_file, 'w', encoding='utf-8')
3996 for symbol in sorted(AllSymbols.keys()):
3997 # FIXME: should we print common.LogWarnings for undocumented stuff?
3998 # DEBUG
3999 # location = "defined at " + GetSymbolSourceFile(symbol) + ":" + GetSymbolSourceLine(symbol) + "\n"
4000 # DEBUG
4001 m = re.search(
4002 r':(Title|Long_Description|Short_Description|See_Also|Stability_Level|Include|Section_Id|Image)', symbol)
4003 m2 = re.search(r':(Long_Description|Short_Description)', symbol)
4004 if not m:
4005 total += 1
4006 if symbol in AllDocumentedSymbols:
4007 n_documented += 1
4008 if symbol in AllIncompleteSymbols:
4009 n_incomplete += 1
4010 buffer += symbol + " (" + AllIncompleteSymbols[symbol] + ")\n"
4012 elif symbol in Deprecated:
4013 if symbol in AllIncompleteSymbols:
4014 n_incomplete += 1
4015 buffer_deprecated += symbol + " (" + AllIncompleteSymbols[symbol] + ")\n"
4016 else:
4017 buffer_deprecated += symbol + "\n"
4019 else:
4020 if symbol in AllIncompleteSymbols:
4021 n_incomplete += 1
4022 buffer += symbol + " (" + AllIncompleteSymbols[symbol] + ")\n"
4023 else:
4024 buffer += symbol + "\n"
4026 elif m2:
4027 total += 1
4028 if (symbol in SymbolDocs and len(SymbolDocs[symbol]) > 0)\
4029 or (symbol in AllDocumentedSymbols and AllDocumentedSymbols[symbol] > 0):
4030 n_documented += 1
4031 else:
4032 buffer_descriptions += symbol + "\n"
4034 if total == 0:
4035 percent = 100
4036 else:
4037 percent = (n_documented / total) * 100.0
4039 UNDOCUMENTED.write("%.0f%% symbol docs coverage.\n" % percent)
4040 UNDOCUMENTED.write("%s symbols documented.\n" % n_documented)
4041 UNDOCUMENTED.write("%s symbols incomplete.\n" % n_incomplete)
4042 UNDOCUMENTED.write("%d not documented.\n" % (total - n_documented))
4044 if buffer_deprecated != '':
4045 buffer += "\n" + buffer_deprecated
4047 if buffer_descriptions != '':
4048 buffer += "\n" + buffer_descriptions
4050 if buffer != '':
4051 UNDOCUMENTED.write("\n\n" + buffer)
4053 UNDOCUMENTED.close()
4055 return common.UpdateFileIfChanged(old_undocumented_file, new_undocumented_file, 0)
4058 def OutputUndeclaredSymbols():
4059 """Reports undeclared symbols.
4061 Outputs symbols that are listed in the section file, but have no declaration
4062 in the sources.
4064 Returns:
4065 bool: True if the report was updated
4067 old_undeclared_file = os.path.join(ROOT_DIR, MODULE + "-undeclared.txt")
4068 new_undeclared_file = os.path.join(ROOT_DIR, MODULE + "-undeclared.new")
4070 with open(new_undeclared_file, 'w', encoding='utf-8') as out:
4071 if UndeclaredSymbols:
4072 out.write("\n".join(sorted(UndeclaredSymbols.keys())))
4073 out.write("\n")
4074 print("See %s-undeclared.txt for the list of undeclared symbols." % MODULE)
4076 return common.UpdateFileIfChanged(old_undeclared_file, new_undeclared_file, 0)
4079 def OutputUnusedSymbols():
4080 """Reports unused documentation.
4082 Outputs symbols that are documented in comments, but not declared in the
4083 sources.
4085 Returns:
4086 bool: True if the report was updated
4088 num_unused = 0
4089 old_unused_file = os.path.join(ROOT_DIR, MODULE + "-unused.txt")
4090 new_unused_file = os.path.join(ROOT_DIR, MODULE + "-unused.new")
4092 with open(new_unused_file, 'w', encoding='utf-8') as out:
4094 for symbol in sorted(Declarations.keys()):
4095 if symbol not in DeclarationOutput:
4096 out.write("%s\n" % symbol)
4097 num_unused += 1
4099 for symbol in sorted(AllUnusedSymbols.keys()):
4100 out.write(symbol + "(" + AllUnusedSymbols[symbol] + ")\n")
4101 num_unused += 1
4103 if num_unused != 0:
4104 common.LogWarning(
4105 old_unused_file, 1, "%d unused declarations. They should be added to %s-sections.txt in the appropriate place." % (num_unused, MODULE))
4107 return common.UpdateFileIfChanged(old_unused_file, new_unused_file, 0)
4110 def OutputAllSymbols():
4111 """Outputs list of all symbols to a file."""
4112 new_symbols_file = os.path.join(ROOT_DIR, MODULE + "-symbols.txt")
4113 with open(new_symbols_file, 'w', encoding='utf-8') as out:
4114 for symbol in sorted(AllSymbols.keys()):
4115 out.write(symbol + "\n")
4118 def OutputSymbolsWithoutSince():
4119 """Outputs list of all symbols without a since tag to a file."""
4120 new_nosince_file = os.path.join(ROOT_DIR, MODULE + "-nosince.txt")
4121 with open(new_nosince_file, 'w', encoding='utf-8') as out:
4122 for symbol in sorted(SourceSymbolDocs.keys()):
4123 if symbol in Since:
4124 out.write(symbol + "\n")
4127 def CheckParamsDocumented(symbol, params):
4128 stype = DeclarationTypes.get(symbol)
4130 item = "Parameter"
4131 if stype:
4132 if stype == 'STRUCT':
4133 item = "Field"
4134 elif stype == 'ENUM':
4135 item = "Value"
4136 elif stype == 'UNION':
4137 item = "Field"
4138 else:
4139 stype = "SIGNAL"
4140 logging.info("Check param docs for %s, params: %s entries, type=%s", symbol, len(params), stype)
4142 if len(params) > 0:
4143 logging.info("params: %s", str(params))
4144 for (param_name, param_desc) in params.items():
4145 # Output a warning if the parameter is empty and remember for stats.
4146 if param_name != "void" and not re.search(r'\S', param_desc):
4147 if symbol in AllIncompleteSymbols:
4148 AllIncompleteSymbols[symbol] += ", " + param_name
4149 else:
4150 AllIncompleteSymbols[symbol] = param_name
4152 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
4153 "%s description for %s::%s is missing in source code comment block." % (item, symbol, param_name))
4155 elif len(params) == 0:
4156 AllIncompleteSymbols[symbol] = "<items>"
4157 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
4158 "%s descriptions for %s are missing in source code comment block." % (item, symbol))
4161 def MergeSourceDocumentation():
4162 """Merges documentation read from a source file.
4164 Parameter descriptions override any in the template files.
4165 Function descriptions are placed before any description from
4166 the template files.
4169 # add whats found in the source
4170 symbols = set(SourceSymbolDocs.keys())
4172 # and add known symbols from -sections.txt
4173 for symbol in KnownSymbols.keys():
4174 if KnownSymbols[symbol] == 1:
4175 symbols.add(symbol)
4177 logging.info("num source entries: %d", len(symbols))
4179 for symbol in symbols:
4180 AllSymbols[symbol] = 1
4182 if symbol in SourceSymbolDocs:
4183 logging.info("merging [%s] from source", symbol)
4185 # remove leading and training whitespaces
4186 src_docs = SourceSymbolDocs[symbol].strip()
4187 if src_docs != '':
4188 AllDocumentedSymbols[symbol] = 1
4190 SymbolDocs[symbol] = src_docs
4192 # merge parameters
4193 if symbol in SourceSymbolParams:
4194 param_docs = SourceSymbolParams[symbol]
4195 SymbolParams[symbol] = param_docs
4196 # if this symbol is documented, check if docs are complete
4197 # remove all xml-tags and whitespaces
4198 check_docs = re.sub(r'\s', '', re.sub(r'<.*?>', '', src_docs))
4199 if check_docs != '' and param_docs:
4200 CheckParamsDocumented(symbol, param_docs)
4201 else:
4202 logging.info("[%s] undocumented", symbol)
4204 logging.info("num doc entries: %d", len(SymbolDocs))
4207 def IsEmptyDoc(doc):
4208 """Check if a doc-string is empty.
4210 It is also regarded as empty if it only consist of whitespace or e.g. FIXME.
4212 Args:
4213 doc (str): the doc-string
4215 Returns:
4216 bool: True if empty
4218 if re.search(r'^\s*$', doc):
4219 return True
4220 if re.search(r'^\s*<para>\s*(FIXME)?\s*<\/para>\s*$', doc):
4221 return True
4222 return False
4225 def ConvertMarkDown(symbol, text):
4226 md_to_db.Init()
4227 return md_to_db.MarkDownParse(text, symbol)
4230 def ReadDeclarationsFile(ifile, override):
4231 """Reads in a file containing the function/macro/enum etc. declarations.
4233 Note that in some cases there are several declarations with
4234 the same name, e.g. for conditional macros. In this case we
4235 set a flag in the DeclarationConditional hash so the
4236 declaration is not shown in the docs.
4238 If a macro and a function have the same name, e.g. for
4239 g_object_ref, the function declaration takes precedence.
4241 Some opaque structs are just declared with 'typedef struct
4242 _name name;' in which case the declaration may be empty.
4243 The structure may have been found later in the header, so
4244 that overrides the empty declaration.
4246 Args:
4247 file (str): the declarations file to read
4248 override (bool): if declarations in this file should override
4249 any current declaration.
4251 if override == 0:
4252 Declarations.clear()
4253 DeclarationTypes.clear()
4254 DeclarationConditional.clear()
4255 DeclarationOutput.clear()
4257 INPUT = open(ifile, 'r', encoding='utf-8')
4258 declaration_type = ''
4259 declaration_name = None
4260 declaration = None
4261 is_deprecated = 0
4262 line_number = 0
4263 for line in INPUT:
4264 line_number += 1
4265 # logging.debug("%s:%d: %s", ifile, line_number, line)
4266 if not declaration_type:
4267 m1 = re.search(r'^<([^>]+)>', line)
4268 if m1:
4269 declaration_type = m1.group(1)
4270 declaration_name = ''
4271 logging.info("Found declaration: %s", declaration_type)
4272 declaration = ''
4273 else:
4274 m2 = re.search(r'^<NAME>(.*)</NAME>', line)
4275 m3 = re.search(r'^<DEPRECATED/>', line)
4276 m4 = re.search(r'^</%s>' % declaration_type, line)
4277 if m2:
4278 declaration_name = m2.group(1)
4279 elif m3:
4280 is_deprecated = True
4281 elif m4:
4282 logging.info("Found end of declaration: %s, %s", declaration_type, declaration_name)
4283 # Check that the declaration has a name
4284 if declaration_name == '':
4285 common.LogWarning(ifile, line_number, declaration_type + " has no name.\n")
4287 # If the declaration is an empty typedef struct _XXX XXX
4288 # set the flag to indicate the struct has a typedef.
4289 if (declaration_type == 'STRUCT' or declaration_type == 'UNION') \
4290 and re.search(r'^\s*$', declaration):
4291 logging.info("Struct has typedef: %s", declaration_name)
4292 StructHasTypedef[declaration_name] = 1
4294 # Check if the symbol is already defined.
4295 if declaration_name in Declarations and override == 0:
4296 # Function declarations take precedence.
4297 if DeclarationTypes[declaration_name] == 'FUNCTION':
4298 # Ignore it.
4299 pass
4300 elif declaration_type == 'FUNCTION':
4301 if is_deprecated:
4302 Deprecated[declaration_name] = ''
4304 Declarations[declaration_name] = declaration
4305 DeclarationTypes[declaration_name] = declaration_type
4306 elif DeclarationTypes[declaration_name] == declaration_type:
4307 # If the existing declaration is empty, or is just a
4308 # forward declaration of a struct, override it.
4309 if declaration_type == 'STRUCT' or declaration_type == 'UNION':
4310 if re.search(r'^\s*((struct|union)\s+\w+\s*;)?\s*$', Declarations[declaration_name]):
4311 if is_deprecated:
4312 Deprecated[declaration_name] = ''
4313 Declarations[declaration_name] = declaration
4314 elif re.search(r'^\s*((struct|union)\s+\w+\s*;)?\s*$', declaration):
4315 # Ignore an empty or forward declaration.
4316 pass
4317 else:
4318 common.LogWarning(
4319 ifile, line_number, "Structure %s has multiple definitions." % declaration_name)
4321 else:
4322 # set flag in %DeclarationConditional hash for
4323 # multiply defined macros/typedefs.
4324 DeclarationConditional[declaration_name] = 1
4326 else:
4327 common.LogWarning(ifile, line_number, declaration_name + " has multiple definitions.")
4329 else:
4330 if is_deprecated:
4331 Deprecated[declaration_name] = ''
4333 Declarations[declaration_name] = declaration
4334 DeclarationTypes[declaration_name] = declaration_type
4335 logging.debug("added declaration: %s, %s, [%s]", declaration_type, declaration_name, declaration)
4337 declaration_type = ''
4338 is_deprecated = False
4339 else:
4340 declaration += line
4341 INPUT.close()
4344 def ReadSignalsFile(ifile):
4345 """Reads information about object signals.
4347 It creates the arrays @SignalNames and @SignalPrototypes containing details
4348 about the signals. The first line of the SignalPrototype is the return type
4349 of the signal handler. The remaining lines are the parameters passed to it.
4350 The last parameter, "gpointer user_data" is always the same so is not included.
4352 Args:
4353 ifile (str): the file containing the signal handler prototype information.
4355 in_signal = 0
4356 signal_object = None
4357 signal_name = None
4358 signal_returns = None
4359 signal_flags = None
4360 signal_prototype = None
4362 # Reset the signal info.
4363 SignalObjects[:] = []
4364 SignalNames[:] = []
4365 SignalReturns[:] = []
4366 SignalFlags[:] = []
4367 SignalPrototypes[:] = []
4369 if not os.path.isfile(ifile):
4370 return
4372 INPUT = open(ifile, 'r', encoding='utf-8')
4373 line_number = 0
4374 for line in INPUT:
4375 line_number += 1
4376 if not in_signal:
4377 if re.search(r'^<SIGNAL>', line):
4378 in_signal = 1
4379 signal_object = ''
4380 signal_name = ''
4381 signal_returns = ''
4382 signal_prototype = ''
4384 else:
4385 m = re.search(r'^<NAME>(.*)<\/NAME>', line)
4386 m2 = re.search(r'^<RETURNS>(.*)<\/RETURNS>', line)
4387 m3 = re.search(r'^<FLAGS>(.*)<\/FLAGS>', line)
4388 if m:
4389 signal_name = m.group(1)
4390 m1_2 = re.search(r'^(.*)::(.*)$', signal_name)
4391 if m1_2:
4392 signal_object = m1_2.group(1)
4393 signal_name = m1_2.group(2).replace('_', '-')
4394 logging.info("Found signal: %s", signal_name)
4395 else:
4396 common.LogWarning(ifile, line_number, "Invalid signal name: %s." % signal_name)
4398 elif m2:
4399 signal_returns = m2.group(1)
4400 elif m3:
4401 signal_flags = m3.group(1)
4402 elif re.search(r'^</SIGNAL>', line):
4403 logging.info("Found end of signal: %s::%s\nReturns: %s\n%s",
4404 signal_object, signal_name, signal_returns, signal_prototype)
4405 SignalObjects.append(signal_object)
4406 SignalNames.append(signal_name)
4407 SignalReturns.append(signal_returns)
4408 SignalFlags.append(signal_flags)
4409 SignalPrototypes.append(signal_prototype)
4410 in_signal = False
4411 else:
4412 signal_prototype += line
4413 INPUT.close()
4416 def ReadObjectHierarchy(ifile):
4417 """Reads the $MODULE-hierarchy.txt file.
4419 This contains all the GObject subclasses described in this module (and their
4420 ancestors).
4421 It places them in the Objects array, and places their level
4422 in the object hierarchy in the ObjectLevels array, at the
4423 same index. GObject, the root object, has a level of 1.
4425 This also generates tree_index.sgml as it goes along.
4427 Args:
4428 ifile (str): the input filename.
4431 Objects[:] = []
4432 ObjectLevels[:] = []
4434 if not os.path.isfile(ifile):
4435 logging.debug('no *-hierarchy.tx')
4436 return
4438 INPUT = open(ifile, 'r', encoding='utf-8')
4440 # Only emit objects if they are supposed to be documented, or if
4441 # they have documented children. To implement this, we maintain a
4442 # stack of pending objects which will be emitted if a documented
4443 # child turns up.
4444 pending_objects = []
4445 pending_levels = []
4446 root = None
4447 tree = []
4448 for line in INPUT:
4449 m1 = re.search(r'\S+', line)
4450 if not m1:
4451 continue
4453 gobject = m1.group(0)
4454 level = len(line[:m1.start()]) // 2 + 1
4456 if level == 1:
4457 root = gobject
4459 while pending_levels and pending_levels[-1] >= level:
4460 pending_objects.pop()
4461 pending_levels.pop()
4463 pending_objects.append(gobject)
4464 pending_levels.append(level)
4466 if gobject in KnownSymbols:
4467 while len(pending_levels) > 0:
4468 gobject = pending_objects.pop(0)
4469 level = pending_levels.pop(0)
4470 xref = MakeXRef(gobject)
4472 tree.append(' ' * (level * 4) + xref)
4473 Objects.append(gobject)
4474 ObjectLevels.append(level)
4475 ObjectRoots[gobject] = root
4476 # else
4477 # common.LogWarning(ifile, line_number, "unknown type %s" % object)
4480 INPUT.close()
4482 # FIXME: use xml
4483 # my $old_tree_index = "$DB_OUTPUT_DIR/tree_index.$xml"
4484 old_tree_index = os.path.join(DB_OUTPUT_DIR, "tree_index.sgml")
4485 new_tree_index = os.path.join(DB_OUTPUT_DIR, "tree_index.new")
4487 logging.debug('got %d entries for hierarchy', len(tree))
4489 with open(new_tree_index, 'w', encoding='utf-8') as out:
4490 out.write(MakeDocHeader("screen"))
4491 out.write("\n<screen>\n")
4492 out.write(AddTreeLineArt(tree))
4493 out.write("\n</screen>\n")
4495 common.UpdateFileIfChanged(old_tree_index, new_tree_index, 0)
4497 OutputObjectList()
4500 def ReadInterfaces(ifile):
4501 """Reads the $MODULE.interfaces file.
4503 Args:
4504 ifile (str): the input filename.
4507 Interfaces.clear()
4509 if not os.path.isfile(ifile):
4510 return
4512 INPUT = open(ifile, 'r', encoding='utf-8')
4514 for line in INPUT:
4515 line = line.strip()
4516 ifaces = line.split()
4517 gobject = ifaces.pop(0)
4518 if gobject in KnownSymbols and KnownSymbols[gobject] == 1:
4519 knownIfaces = []
4521 # filter out private interfaces, but leave foreign interfaces
4522 for iface in ifaces:
4523 if iface not in KnownSymbols or KnownSymbols[iface] == 1:
4524 knownIfaces.append(iface)
4526 Interfaces[gobject] = ' '.join(knownIfaces)
4527 logging.info("Interfaces for %s: %s", gobject, Interfaces[gobject])
4528 else:
4529 logging.info("skipping interfaces for unknown symbol: %s", gobject)
4531 INPUT.close()
4534 def ReadPrerequisites(ifile):
4535 """This reads in the $MODULE.prerequisites file.
4537 Args:
4538 ifile (str): the input filename.
4540 Prerequisites.clear()
4542 if not os.path.isfile(ifile):
4543 return
4545 INPUT = open(ifile, 'r', encoding='utf-8')
4547 for line in INPUT:
4548 line = line.strip()
4549 prereqs = line.split()
4550 iface = prereqs.pop(0)
4551 if iface in KnownSymbols and KnownSymbols[iface] == 1:
4552 knownPrereqs = []
4554 # filter out private prerequisites, but leave foreign prerequisites
4555 for prereq in prereqs:
4556 if prereq not in KnownSymbols or KnownSymbols[prereq] == 1:
4557 knownPrereqs.append(prereq)
4559 Prerequisites[iface] = ' '.join(knownPrereqs)
4561 INPUT.close()
4564 def ReadArgsFile(ifile):
4565 """Reads information about object properties
4567 It creates the arrays ArgObjects, ArgNames, ArgTypes, ArgFlags, ArgNicks and
4568 ArgBlurbs containing info on the args.
4570 Args:
4571 ifile (str): the input filename.
4573 in_arg = False
4574 arg_object = None
4575 arg_name = None
4576 arg_type = None
4577 arg_flags = None
4578 arg_nick = None
4579 arg_blurb = None
4580 arg_default = None
4581 arg_range = None
4583 # Reset the args info.
4584 ArgObjects[:] = []
4585 ArgNames[:] = []
4586 ArgTypes[:] = []
4587 ArgFlags[:] = []
4588 ArgNicks[:] = []
4589 ArgBlurbs[:] = []
4590 ArgDefaults[:] = []
4591 ArgRanges[:] = []
4593 if not os.path.isfile(ifile):
4594 return
4596 INPUT = open(ifile, 'r', encoding='utf-8')
4597 line_number = 0
4598 for line in INPUT:
4599 line_number += 1
4600 if not in_arg:
4601 if line.startswith('<ARG>'):
4602 in_arg = True
4603 arg_object = ''
4604 arg_name = ''
4605 arg_type = ''
4606 arg_flags = ''
4607 arg_nick = ''
4608 arg_blurb = ''
4609 arg_default = ''
4610 arg_range = ''
4612 else:
4613 m1 = re.search(r'^<NAME>(.*)</NAME>', line)
4614 m2 = re.search(r'^<TYPE>(.*)</TYPE>', line)
4615 m3 = re.search(r'^<RANGE>(.*)</RANGE>', line)
4616 m4 = re.search(r'^<FLAGS>(.*)</FLAGS>', line)
4617 m5 = re.search(r'^<NICK>(.*)</NICK>', line)
4618 m6 = re.search(r'^<BLURB>(.*)</BLURB>', line)
4619 m7 = re.search(r'^<DEFAULT>(.*)</DEFAULT>', line)
4620 if m1:
4621 arg_name = m1.group(1)
4622 m1_1 = re.search(r'^(.*)::(.*)$', arg_name)
4623 if m1_1:
4624 arg_object = m1_1.group(1)
4625 arg_name = m1_1.group(2).replace('_', '-')
4626 logging.info("Found arg: %s", arg_name)
4627 else:
4628 common.LogWarning(ifile, line_number, "Invalid argument name: " + arg_name)
4630 elif m2:
4631 arg_type = m2.group(1)
4632 elif m3:
4633 arg_range = m3.group(1)
4634 elif m4:
4635 arg_flags = m4.group(1)
4636 elif m5:
4637 arg_nick = m5.group(1)
4638 elif m6:
4639 arg_blurb = m6.group(1)
4640 if arg_blurb == "(null)":
4641 arg_blurb = ''
4642 common.LogWarning(
4643 ifile, line_number, "Property %s:%s has no documentation." % (arg_object, arg_name))
4645 elif m7:
4646 arg_default = m7.group(1)
4647 elif re.search(r'^</ARG>', line):
4648 logging.info("Found end of arg: %s::%s\n%s : %s", arg_object, arg_name, arg_type, arg_flags)
4649 ArgObjects.append(arg_object)
4650 ArgNames.append(arg_name)
4651 ArgTypes.append(arg_type)
4652 ArgRanges.append(arg_range)
4653 ArgFlags.append(arg_flags)
4654 ArgNicks.append(arg_nick)
4655 ArgBlurbs.append(arg_blurb)
4656 ArgDefaults.append(arg_default)
4657 in_arg = False
4659 INPUT.close()
4662 def AddTreeLineArt(tree):
4663 """Generate a line art tree.
4665 Add unicode lineart to a pre-indented string array and returns
4666 it as as multiline string.
4668 Args:
4669 tree (list): of indented strings.
4671 Returns:
4672 str: multiline string with tree line art
4674 # iterate bottom up over the tree
4675 for i in range(len(tree) - 1, -1, -1):
4676 # count leading spaces
4677 m = re.search(r'^([^<A-Za-z]*)', tree[i])
4678 indent = len(m.group(1))
4679 # replace with ╰───, if place of ╰ is not space insert ├
4680 if indent > 4:
4681 if tree[i][indent - 4] == " ":
4682 tree[i] = tree[i][:indent - 4] + "--- " + tree[i][indent:]
4683 else:
4684 tree[i] = tree[i][:indent - 4] + "+-- " + tree[i][indent:]
4686 # go lines up while space and insert |
4687 j = i - 1
4688 while j >= 0 and tree[j][indent - 4] == ' ':
4689 tree[j] = tree[j][:indent - 4] + '|' + tree[j][indent - 3:]
4690 j -= 1
4692 res = "\n".join(tree)
4693 # unicode chars for: ╰──
4694 res = re.sub(r'---', '<phrase role=\"lineart\">&#9584;&#9472;&#9472;</phrase>', res)
4695 # unicde chars for: ├──
4696 res = re.sub(r'\+--', '<phrase role=\"lineart\">&#9500;&#9472;&#9472;</phrase>', res)
4697 # unicode char for: │
4698 res = re.sub(r'\|', '<phrase role=\"lineart\">&#9474;</phrase>', res)
4700 return res
4703 def CheckIsObject(name):
4704 """Check if symbols is an object.
4706 It uses the global Objects array. Note that the Objects array only contains
4707 classes in the current module and their ancestors - not all GObject classes.
4709 Args:
4710 name (str): the object name to check.
4712 Returns:
4713 bool: True if the given name is a GObject or a subclass.
4715 root = ObjectRoots.get(name)
4716 # Let GBoxed pass as an object here to get -struct appended to the id
4717 # and prevent conflicts with sections.
4718 return root and root != 'GEnum' and root != 'GFlags'
4721 def GetSymbolSourceFile(symbol):
4722 """Get the filename where the symbol docs where taken from."""
4723 return SourceSymbolSourceFile.get(symbol, '')
4726 def GetSymbolSourceLine(symbol):
4727 """Get the file line where the symbol docs where taken from."""
4728 return SourceSymbolSourceLine.get(symbol, 0)