mkhtml2: add tag converters for sect2/sect3.
[gtk-doc.git] / gtkdoc / mkdb.py
blobf5e8995f96dd378137ba89ab07b3e546ab7bb638
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 __future__ import print_function
27 from six import iteritems, iterkeys
29 from collections import OrderedDict
30 import logging
31 import os
32 import re
33 import string
35 from . import common, md_to_db
37 # Options
38 MODULE = None
39 DB_OUTPUT_DIR = None
40 INLINE_MARKUP_MODE = None
41 DEFAULT_STABILITY = None
42 NAME_SPACE = ''
43 ROOT_DIR = '.'
45 # These global arrays store information on signals. Each signal has an entry
46 # in each of these arrays at the same index, like a multi-dimensional array.
47 SignalObjects = [] # The GtkObject which emits the signal.
48 SignalNames = [] # The signal name.
49 SignalReturns = [] # The return type.
50 SignalFlags = [] # Flags for the signal
51 SignalPrototypes = [] # The rest of the prototype of the signal handler.
53 # These global arrays store information on Args. Each Arg has an entry
54 # in each of these arrays at the same index, like a multi-dimensional array.
55 ArgObjects = [] # The GtkObject which has the Arg.
56 ArgNames = [] # The Arg name.
57 ArgTypes = [] # The Arg type - gint, GtkArrowType etc.
58 ArgFlags = [] # How the Arg can be used - readable/writable etc.
59 ArgNicks = [] # The nickname of the Arg.
60 ArgBlurbs = [] # Docstring of the Arg.
61 ArgDefaults = [] # Default value of the Arg.
62 ArgRanges = [] # The range of the Arg type
64 # These global hashes store declaration info keyed on a symbol name.
65 Declarations = {}
66 DeclarationTypes = {}
67 DeclarationConditional = {}
68 DeclarationOutput = {}
69 Deprecated = {}
70 Since = {}
71 StabilityLevel = {}
72 StructHasTypedef = {}
74 # These global hashes store the existing documentation.
75 SymbolDocs = {}
76 SymbolParams = {}
77 SymbolAnnotations = {}
79 # These global hashes store documentation scanned from the source files.
80 SourceSymbolDocs = {}
81 SourceSymbolParams = {}
82 SourceSymbolSourceFile = {}
83 SourceSymbolSourceLine = {}
85 # all documentation goes in here, so we can do coverage analysis
86 AllSymbols = {}
87 AllIncompleteSymbols = {}
88 AllUnusedSymbols = {}
89 AllDocumentedSymbols = {}
91 # Undeclared yet documented symbols
92 UndeclaredSymbols = {}
94 # These global arrays store GObject, subclasses and the hierarchy (also of
95 # non-object derived types).
96 Objects = []
97 ObjectLevels = []
98 ObjectRoots = {}
100 Interfaces = {}
101 Prerequisites = {}
103 # holds the symbols which are mentioned in <MODULE>-sections.txt and in which
104 # section they are defined
105 KnownSymbols = {}
106 SymbolSection = {}
107 SymbolSectionId = {}
109 # collects index entries
110 IndexEntriesFull = {}
111 IndexEntriesSince = {}
112 IndexEntriesDeprecated = {}
114 # Standard C preprocessor directives, which we ignore for '#' abbreviations.
115 PreProcessorDirectives = {
116 'assert', 'define', 'elif', 'else', 'endif', 'error', 'if', 'ifdef', 'ifndef',
117 'include', 'line', 'pragma', 'unassert', 'undef', 'warning'
120 # remember used annotation (to write minimal glossary)
121 AnnotationsUsed = {}
123 # the regexp that parses the annotation is in ScanSourceFile()
124 AnnotationDefinition = {
125 # the GObjectIntrospection annotations are defined at:
126 # https://live.gnome.org/GObjectIntrospection/Annotations
127 'allow-none': "NULL is OK, both for passing and for returning.",
128 'nullable': "NULL may be passed as the value in, out, in-out; or as a return value.",
129 'not nullable': "NULL must not be passed as the value in, out, in-out; or as a return value.",
130 'optional': "NULL may be passed instead of a pointer to a location.",
131 'not optional': "NULL must not be passed as the pointer to a location.",
132 'array': "Parameter points to an array of items.",
133 'attribute': "Deprecated free-form custom annotation, replaced by (attributes) annotation.",
134 'attributes': "Free-form key-value pairs.",
135 'closure': "This parameter is a 'user_data', for callbacks; many bindings can pass NULL here.",
136 'constructor': "This symbol is a constructor, not a static method.",
137 'destroy': "This parameter is a 'destroy_data', for callbacks.",
138 'default': "Default parameter value (for in case the <acronym>shadows</acronym>-to function has less parameters).",
139 'element-type': "Generics and defining elements of containers and arrays.",
140 'error-domains': "Typed errors. Similar to throws in Java.",
141 'foreign': "This is a foreign struct.",
142 'get-value-func': "The specified function is used to convert a struct from a GValue, must be a GTypeInstance.",
143 'in': "Parameter for input. Default is <acronym>transfer none</acronym>.",
144 'inout': "Parameter for input and for returning results. Default is <acronym>transfer full</acronym>.",
145 'in-out': "Parameter for input and for returning results. Default is <acronym>transfer full</acronym>.",
146 'method': "This is a method",
147 'not-error': "A GError parameter is not to be handled like a normal GError.",
148 'out': "Parameter for returning results. Default is <acronym>transfer full</acronym>.",
149 'out caller-allocates': "Out parameter, where caller must allocate storage.",
150 'out callee-allocates': "Out parameter, where caller must allocate storage.",
151 'ref-func': "The specified function is used to ref a struct, must be a GTypeInstance.",
152 'rename-to': "Rename the original symbol's name to SYMBOL.",
153 'scope call': "The callback is valid only during the call to the method.",
154 'scope async': "The callback is valid until first called.",
155 'scope notified': "The callback is valid until the GDestroyNotify argument is called.",
156 'set-value-func': "The specified function is used to convert from a struct to a GValue, must be a GTypeInstance.",
157 'skip': "Exposed in C code, not necessarily available in other languages.",
158 'transfer container': "Free data container after the code is done.",
159 'transfer floating': "Alias for <acronym>transfer none</acronym>, used for objects with floating refs.",
160 'transfer full': "Free data after the code is done.",
161 'transfer none': "Don't free data after the code is done.",
162 'type': "Override the parsed C type with given type.",
163 'unref-func': "The specified function is used to unref a struct, must be a GTypeInstance.",
164 'virtual': "This is the invoker for a virtual method.",
165 'value': "The specified value overrides the evaluated value of the constant.",
166 # Stability Level definition
167 # https://bugzilla.gnome.org/show_bug.cgi?id=170860
168 'Stable': '''The intention of a Stable interface is to enable arbitrary third parties to
169 develop applications to these interfaces, release them, and have confidence that
170 they will run on all minor releases of the product (after the one in which the
171 interface was introduced, and within the same major release). Even at a major
172 release, incompatible changes are expected to be rare, and to have strong
173 justifications.
174 ''',
175 'Unstable': '''Unstable interfaces are experimental or transitional. They are typically used to
176 give outside developers early access to new or rapidly changing technology, or
177 to provide an interim solution to a problem where a more general solution is
178 anticipated. No claims are made about either source or binary compatibility from
179 one minor release to the next.
181 The Unstable interface level is a warning that these interfaces are subject to
182 change without warning and should not be used in unbundled products.
184 Given such caveats, customer impact need not be a factor when considering
185 incompatible changes to an Unstable interface in a major or minor release.
186 Nonetheless, when such changes are introduced, the changes should still be
187 mentioned in the release notes for the affected release.
188 ''',
189 'Private': '''An interface that can be used within the GNOME stack itself, but that is not
190 documented for end-users. Such functions should only be used in specified and
191 documented ways.
192 ''',
195 # Function and other declaration output settings.
196 RETURN_TYPE_FIELD_WIDTH = 20
197 MAX_SYMBOL_FIELD_WIDTH = 40
199 # XML header
200 doctype_header = None
202 # refentry template
203 REFENTRY = string.Template('''${header}
204 <refentry id="${section_id}">
205 <refmeta>
206 <refentrytitle role="top_of_page" id="${section_id}.top_of_page">${title}</refentrytitle>
207 <manvolnum>3</manvolnum>
208 <refmiscinfo>${MODULE} Library${image}</refmiscinfo>
209 </refmeta>
210 <refnamediv>
211 <refname>${title}</refname>
212 <refpurpose>${short_desc}</refpurpose>
213 </refnamediv>
214 ${stability}
215 ${functions_synop}${args_synop}${signals_synop}${object_anchors}${other_synop}${hierarchy}${prerequisites}${derived}${interfaces}${implementations}
216 ${include_output}
217 <refsect1 id="${section_id}.description" role="desc">
218 <title role="desc.title">Description</title>
219 ${extralinks}${long_desc}
220 </refsect1>
221 <refsect1 id="${section_id}.functions_details" role="details">
222 <title role="details.title">Functions</title>
223 ${functions_details}
224 </refsect1>
225 <refsect1 id="${section_id}.other_details" role="details">
226 <title role="details.title">Types and Values</title>
227 ${other_details}
228 </refsect1>
229 ${args_desc}${signals_desc}${see_also}
230 </refentry>
231 ''')
234 def Run(options):
235 global MODULE, INLINE_MARKUP_MODE, DEFAULT_STABILITY, NAME_SPACE, \
236 DB_OUTPUT_DIR, doctype_header
238 logging.info('options: %s', str(options.__dict__))
240 # We should pass the options variable around instead of this global variable horror
241 # but too much of the code expects these to be around. Fix this once the transition is done.
242 MODULE = options.module
243 INLINE_MARKUP_MODE = options.xml_mode or options.sgml_mode
244 DEFAULT_STABILITY = options.default_stability
245 NAME_SPACE = options.name_space
247 main_sgml_file = options.main_sgml_file
248 if not main_sgml_file:
249 # backwards compatibility
250 if os.path.exists(MODULE + "-docs.sgml"):
251 main_sgml_file = MODULE + "-docs.sgml"
252 else:
253 main_sgml_file = MODULE + "-docs.xml"
255 # extract docbook header or define default
256 doctype_header = GetDocbookHeader(main_sgml_file)
258 # This is where we put all the DocBook output.
259 DB_OUTPUT_DIR = DB_OUTPUT_DIR if DB_OUTPUT_DIR else os.path.join(ROOT_DIR, "xml")
260 if not os.path.isdir(DB_OUTPUT_DIR):
261 os.mkdir(DB_OUTPUT_DIR)
263 ReadKnownSymbols(os.path.join(ROOT_DIR, MODULE + "-sections.txt"))
264 ReadSignalsFile(os.path.join(ROOT_DIR, MODULE + ".signals"))
265 ReadArgsFile(os.path.join(ROOT_DIR, MODULE + ".args"))
266 ReadObjectHierarchy(os.path.join(ROOT_DIR, MODULE + ".hierarchy"))
267 ReadInterfaces(os.path.join(ROOT_DIR, MODULE + ".interfaces"))
268 ReadPrerequisites(os.path.join(ROOT_DIR, MODULE + ".prerequisites"))
270 ReadDeclarationsFile(os.path.join(ROOT_DIR, MODULE + "-decl.txt"), 0)
271 if os.path.isfile(os.path.join(ROOT_DIR, MODULE + "-overrides.txt")):
272 ReadDeclarationsFile(os.path.join(ROOT_DIR, MODULE + "-overrides.txt"), 1)
274 # Scan sources
275 if options.source_suffixes:
276 suffix_list = ['.' + ext for ext in options.source_suffixes.split(',')]
277 else:
278 suffix_list = ['.c', '.h']
280 source_dirs = options.source_dir
281 ignore_files = options.ignore_files
282 logging.info(" ignore files: " + ignore_files)
283 for sdir in source_dirs:
284 ReadSourceDocumentation(sdir, suffix_list, source_dirs, ignore_files)
286 changed, book_top, book_bottom = OutputDB(os.path.join(ROOT_DIR, MODULE + "-sections.txt"), options)
287 OutputBook(main_sgml_file, book_top, book_bottom)
289 logging.info("All files created: %d", changed)
291 # If any of the DocBook files have changed, update the timestamp file (so
292 # it can be used for Makefile dependencies).
293 if changed or not os.path.exists(os.path.join(ROOT_DIR, "sgml.stamp")):
295 # try to detect the common prefix
296 # GtkWidget, GTK_WIDGET, gtk_widget -> gtk
297 if NAME_SPACE == '':
298 NAME_SPACE = DetermineNamespace()
300 logging.info('namespace prefix ="%s"', NAME_SPACE)
302 OutputIndex("api-index-full", IndexEntriesFull)
303 OutputIndex("api-index-deprecated", IndexEntriesDeprecated)
304 OutputSinceIndexes()
305 OutputAnnotationGlossary()
307 with open(os.path.join(ROOT_DIR, 'sgml.stamp'), 'w') as h:
308 h.write('timestamp')
311 def OutputObjectList():
312 """This outputs the alphabetical list of objects, in a columned table."""
313 # FIXME: Currently this also outputs ancestor objects which may not actually
314 # be in this module.
315 cols = 3
317 # FIXME: use .xml
318 old_object_index = os.path.join(DB_OUTPUT_DIR, "object_index.sgml")
319 new_object_index = os.path.join(DB_OUTPUT_DIR, "object_index.new")
321 OUTPUT = common.open_text(new_object_index, 'w')
323 OUTPUT.write('''%s
324 <informaltable pgwide="1" frame="none">
325 <tgroup cols="%s">
326 <colspec colwidth="1*"/>
327 <colspec colwidth="1*"/>
328 <colspec colwidth="1*"/>
329 <tbody>
330 ''' % (MakeDocHeader("informaltable"), cols))
332 count = 0
333 object = None
334 for object in sorted(Objects):
335 xref = MakeXRef(object)
336 if count % cols == 0:
337 OUTPUT.write("<row>\n")
338 OUTPUT.write("<entry>%s</entry>\n" % xref)
339 if count % cols == cols - 1:
340 OUTPUT.write("</row>\n")
341 count += 1
343 if count == 0:
344 # emit an empty row, since empty tables are invalid
345 OUTPUT.write("<row><entry> </entry></row>\n")
347 else:
348 if count % cols > 0:
349 OUTPUT.write("</row>\n")
351 OUTPUT.write('''</tbody></tgroup></informaltable>\n''')
352 OUTPUT.close()
354 common.UpdateFileIfChanged(old_object_index, new_object_index, 0)
357 def TrimTextBlock(desc):
358 """Trims extra whitespace.
360 Empty lines inside a block are preserved.
361 Args:
362 desc (str): the text block to trim. May contain newlines.
365 # strip trailing spaces on every line
366 return re.sub(r'\s+$', '\n', desc.lstrip(), flags=re.MULTILINE)
369 def OutputDB(file, options):
370 """Generate docbook files.
372 This collects the output for each section of the docs, and outputs each file
373 when the end of the section is found.
375 Args:
376 file (str): the $MODULE-sections.txt file which contains all of the
377 functions/macros/structs etc. being documented, organised
378 into sections and subsections.
379 options: commandline options
382 logging.info("Reading: %s", file)
383 INPUT = common.open_text(file)
384 filename = ''
385 book_top = ''
386 book_bottom = ''
387 includes = options.default_includes or ''
388 section_includes = ''
389 in_section = 0
390 title = ''
391 section_id = ''
392 subsection = ''
393 num_symbols = 0
394 changed = 0
395 functions_synop = ''
396 other_synop = ''
397 functions_details = ''
398 other_details = ''
399 signals_synop = ''
400 signals_desc = ''
401 args_synop = ''
402 child_args_synop = ''
403 style_args_synop = ''
404 args_desc = ''
405 child_args_desc = ''
406 style_args_desc = ''
407 hierarchy_str = ''
408 hierarchy = []
409 interfaces = ''
410 implementations = ''
411 prerequisites = ''
412 derived = ''
413 file_objects = []
414 file_def_line = {}
415 symbol_def_line = {}
417 MergeSourceDocumentation()
419 line_number = 0
420 for line in INPUT:
421 line_number += 1
423 if line.startswith('#'):
424 continue
426 logging.info("section file data: %d: %s", line_number, line)
428 m1 = re.search(r'^<SUBSECTION\s*(.*)>', line, re.I)
429 m2 = re.search(r'^<TITLE>(.*)<\/TITLE', line)
430 m3 = re.search(r'^<FILE>(.*)<\/FILE>', line)
431 m4 = re.search(r'^<INCLUDE>(.*)<\/INCLUDE>', line)
432 m5 = re.search(r'^(\S+)', line)
434 if line.startswith('<SECTION>'):
435 num_symbols = 0
436 in_section = False
437 file_objects = []
438 symbol_def_line = {}
440 elif m1:
441 other_synop += "\n"
442 functions_synop += "\n"
443 subsection = m1.group(1)
445 elif line.startswith('<SUBSECTION>'):
446 continue
447 elif m2:
448 title = m2.group(1)
449 logging.info("Section: %s", title)
451 # We don't want warnings if object & class structs aren't used.
452 DeclarationOutput[title] = 1
453 DeclarationOutput["%sClass" % title] = 1
454 DeclarationOutput["%sIface" % title] = 1
455 DeclarationOutput["%sInterface" % title] = 1
457 elif m3:
458 filename = m3.group(1)
459 if filename not in file_def_line:
460 file_def_line[filename] = line_number
461 else:
462 common.LogWarning(file, line_number, "Double <FILE>%s</FILE> entry. Previous occurrence on line %s." %
463 (filename, file_def_line[filename]))
464 if title == '':
465 key = filename + ":Title"
466 if key in SourceSymbolDocs:
467 title = SourceSymbolDocs[key].rstrip()
469 elif m4:
470 if in_section:
471 section_includes = m4.group(1)
472 else:
473 if options.default_includes:
474 common.LogWarning(file, line_number, "Default <INCLUDE> being overridden by command line option.")
475 else:
476 includes = m4.group(1)
478 elif re.search(r'^<\/SECTION>', line):
479 logging.info("End of section: %s", title)
480 # TODO: also output if we have sections docs?
481 # long_desc = SymbolDocs.get(filename + ":Long_Description")
482 if num_symbols > 0:
483 # collect documents
484 book_bottom += " <xi:include href=\"xml/%s.xml\"/>\n" % filename
486 key = filename + ":Include"
487 if key in SourceSymbolDocs:
488 if section_includes:
489 common.LogWarning(file, line_number, "Section <INCLUDE> being overridden by inline comments.")
490 section_includes = SourceSymbolDocs[key]
492 if section_includes == '':
493 section_includes = includes
495 signals_synop = re.sub(r'^\n*', '', signals_synop)
496 signals_synop = re.sub(r'\n+$', '\n', signals_synop)
498 if signals_synop != '':
499 signals_synop = '''<refsect1 id="%s.signals" role="signal_proto">
500 <title role="signal_proto.title">Signals</title>
501 <informaltable frame="none">
502 <tgroup cols="3">
503 <colspec colname="signals_return" colwidth="150px"/>
504 <colspec colname="signals_name" colwidth="300px"/>
505 <colspec colname="signals_flags" colwidth="200px"/>
506 <tbody>
508 </tbody>
509 </tgroup>
510 </informaltable>
511 </refsect1>
512 ''' % (section_id, signals_synop)
513 signals_desc = TrimTextBlock(signals_desc)
514 signals_desc = '''<refsect1 id="%s.signal-details" role="signals">
515 <title role="signals.title">Signal Details</title>
517 </refsect1>
518 ''' % (section_id, signals_desc)
520 args_synop = re.sub(r'^\n*', '', args_synop)
521 args_synop = re.sub(r'\n+$', '\n', args_synop)
522 if args_synop != '':
523 args_synop = '''<refsect1 id="%s.properties" role="properties">
524 <title role="properties.title">Properties</title>
525 <informaltable frame="none">
526 <tgroup cols="3">
527 <colspec colname="properties_type" colwidth="150px"/>
528 <colspec colname="properties_name" colwidth="300px"/>
529 <colspec colname="properties_flags" colwidth="200px"/>
530 <tbody>
532 </tbody>
533 </tgroup>
534 </informaltable>
535 </refsect1>
536 ''' % (section_id, args_synop)
537 args_desc = TrimTextBlock(args_desc)
538 args_desc = '''<refsect1 id="%s.property-details" role="property_details">
539 <title role="property_details.title">Property Details</title>
541 </refsect1>
542 ''' % (section_id, args_desc)
544 child_args_synop = re.sub(r'^\n*', '', child_args_synop)
545 child_args_synop = re.sub(r'\n+$', '\n', child_args_synop)
546 if child_args_synop != '':
547 args_synop += '''<refsect1 id="%s.child-properties" role="child_properties">
548 <title role="child_properties.title">Child Properties</title>
549 <informaltable frame="none">
550 <tgroup cols="3">
551 <colspec colname="child_properties_type" colwidth="150px"/>
552 <colspec colname="child_properties_name" colwidth="300px"/>
553 <colspec colname="child_properties_flags" colwidth="200px"/>
554 <tbody>
556 </tbody>
557 </tgroup>
558 </informaltable>
559 </refsect1>
560 ''' % (section_id, child_args_synop)
561 child_args_desc = TrimTextBlock(child_args_desc)
562 args_desc += '''<refsect1 id="%s.child-property-details" role="child_property_details">
563 <title role="child_property_details.title">Child Property Details</title>
565 </refsect1>
566 ''' % (section_id, child_args_desc)
568 style_args_synop = re.sub(r'^\n*', '', style_args_synop)
569 style_args_synop = re.sub(r'\n+$', '\n', style_args_synop)
570 if style_args_synop != '':
571 args_synop += '''<refsect1 id="%s.style-properties" role="style_properties">
572 <title role="style_properties.title">Style Properties</title>
573 <informaltable frame="none">
574 <tgroup cols="3">
575 <colspec colname="style_properties_type" colwidth="150px"/>
576 <colspec colname="style_properties_name" colwidth="300px"/>
577 <colspec colname="style_properties_flags" colwidth="200px"/>
578 <tbody>
580 </tbody>
581 </tgroup>
582 </informaltable>
583 </refsect1>
584 ''' % (section_id, style_args_synop)
585 style_args_desc = TrimTextBlock(style_args_desc)
586 args_desc += '''<refsect1 id="%s.style-property-details" role="style_properties_details">
587 <title role="style_properties_details.title">Style Property Details</title>
589 </refsect1>
590 ''' % (section_id, style_args_desc)
592 hierarchy_str = AddTreeLineArt(hierarchy)
593 if hierarchy_str != '':
594 hierarchy_str = '''<refsect1 id="%s.object-hierarchy" role="object_hierarchy">
595 <title role="object_hierarchy.title">Object Hierarchy</title>
596 <screen>%s
597 </screen>
598 </refsect1>
599 ''' % (section_id, hierarchy_str)
601 interfaces = TrimTextBlock(interfaces)
602 if interfaces != '':
603 interfaces = '''<refsect1 id="%s.implemented-interfaces" role="impl_interfaces">
604 <title role="impl_interfaces.title">Implemented Interfaces</title>
606 </refsect1>
607 ''' % (section_id, interfaces)
609 implementations = TrimTextBlock(implementations)
610 if implementations != '':
611 implementations = '''<refsect1 id="%s.implementations" role="implementations">
612 <title role="implementations.title">Known Implementations</title>
614 </refsect1>
615 ''' % (section_id, implementations)
617 prerequisites = TrimTextBlock(prerequisites)
618 if prerequisites != '':
619 prerequisites = '''<refsect1 id="%s.prerequisites" role="prerequisites">
620 <title role="prerequisites.title">Prerequisites</title>
622 </refsect1>
623 ''' % (section_id, prerequisites)
625 derived = TrimTextBlock(derived)
626 if derived != '':
627 derived = '''<refsect1 id="%s.derived-interfaces" role="derived_interfaces">
628 <title role="derived_interfaces.title">Known Derived Interfaces</title>
630 </refsect1>
631 ''' % (section_id, derived)
633 functions_synop = re.sub(r'^\n*', '', functions_synop)
634 functions_synop = re.sub(r'\n+$', '\n', functions_synop)
635 if functions_synop != '':
636 functions_synop = '''<refsect1 id="%s.functions" role="functions_proto">
637 <title role="functions_proto.title">Functions</title>
638 <informaltable pgwide="1" frame="none">
639 <tgroup cols="2">
640 <colspec colname="functions_return" colwidth="150px"/>
641 <colspec colname="functions_name"/>
642 <tbody>
644 </tbody>
645 </tgroup>
646 </informaltable>
647 </refsect1>
648 ''' % (section_id, functions_synop)
650 other_synop = re.sub(r'^\n*', '', other_synop)
651 other_synop = re.sub(r'\n+$', '\n', other_synop)
652 if other_synop != '':
653 other_synop = '''<refsect1 id="%s.other" role="other_proto">
654 <title role="other_proto.title">Types and Values</title>
655 <informaltable role="enum_members_table" pgwide="1" frame="none">
656 <tgroup cols="2">
657 <colspec colname="name" colwidth="150px"/>
658 <colspec colname="description"/>
659 <tbody>
661 </tbody>
662 </tgroup>
663 </informaltable>
664 </refsect1>
665 ''' % (section_id, other_synop)
667 file_changed = OutputDBFile(filename, title, section_id,
668 section_includes,
669 functions_synop, other_synop,
670 functions_details, other_details,
671 signals_synop, signals_desc,
672 args_synop, args_desc,
673 hierarchy_str, interfaces,
674 implementations,
675 prerequisites, derived,
676 file_objects)
677 if file_changed:
678 changed = True
680 title = ''
681 section_id = ''
682 subsection = ''
683 in_section = 0
684 section_includes = ''
685 functions_synop = ''
686 other_synop = ''
687 functions_details = ''
688 other_details = ''
689 signals_synop = ''
690 signals_desc = ''
691 args_synop = ''
692 child_args_synop = ''
693 style_args_synop = ''
694 args_desc = ''
695 child_args_desc = ''
696 style_args_desc = ''
697 hierarchy_str = ''
698 hierarchy = []
699 interfaces = ''
700 implementations = ''
701 prerequisites = ''
702 derived = ''
704 elif m5:
705 symbol = m5.group(1)
706 logging.info(' Symbol: "%s" in subsection: "%s"', symbol, subsection)
708 # check for duplicate entries
709 if symbol not in symbol_def_line:
710 declaration = Declarations.get(symbol)
711 # FIXME: with this we'll output empty declaration
712 if declaration is not None:
713 if CheckIsObject(symbol):
714 file_objects.append(symbol)
716 # We don't want standard macros/functions of GObjects,
717 # or private declarations.
718 if subsection != "Standard" and subsection != "Private":
719 synop, desc = OutputDeclaration(symbol, declaration)
720 type = DeclarationTypes[symbol]
722 if type == 'FUNCTION' or type == 'USER_FUNCTION':
723 functions_synop += synop
724 functions_details += desc
725 elif type == 'MACRO' and re.search(symbol + r'\(', declaration):
726 functions_synop += synop
727 functions_details += desc
728 else:
729 other_synop += synop
730 other_details += desc
732 sig_synop, sig_desc = GetSignals(symbol)
733 arg_synop, child_arg_synop, style_arg_synop, arg_desc, child_arg_desc, style_arg_desc = GetArgs(
734 symbol)
735 ifaces = GetInterfaces(symbol)
736 impls = GetImplementations(symbol)
737 prereqs = GetPrerequisites(symbol)
738 der = GetDerived(symbol)
739 hierarchy = GetHierarchy(symbol, hierarchy)
741 signals_synop += sig_synop
742 signals_desc += sig_desc
743 args_synop += arg_synop
744 child_args_synop += child_arg_synop
745 style_args_synop += style_arg_synop
746 args_desc += arg_desc
747 child_args_desc += child_arg_desc
748 style_args_desc += style_arg_desc
749 interfaces += ifaces
750 implementations += impls
751 prerequisites += prereqs
752 derived += der
754 # Note that the declaration has been output.
755 DeclarationOutput[symbol] = True
756 elif subsection != "Standard" and subsection != "Private":
757 UndeclaredSymbols[symbol] = True
758 common.LogWarning(file, line_number, "No declaration found for %s." % symbol)
760 num_symbols += 1
761 symbol_def_line[symbol] = line_number
763 if section_id == '':
764 if title == '' and filename == '':
765 common.LogWarning(file, line_number, "Section has no title and no file.")
767 # FIXME: one of those would be enough
768 # filename should be an internal detail for gtk-doc
769 if title == '':
770 title = filename
771 elif filename == '':
772 filename = title
774 filename = filename.replace(' ', '_')
776 section_id = SourceSymbolDocs.get(filename + ":Section_Id")
777 if section_id and section_id.strip() != '':
778 # Remove trailing blanks and use as is
779 section_id = section_id.rstrip()
780 elif CheckIsObject(title):
781 # GObjects use their class name as the ID.
782 section_id = common.CreateValidSGMLID(title)
783 else:
784 section_id = common.CreateValidSGMLID(MODULE + '-' + title)
786 SymbolSection[symbol] = title
787 SymbolSectionId[symbol] = section_id
789 else:
790 common.LogWarning(file, line_number, "Double symbol entry for %s. "
791 "Previous occurrence on line %d." % (symbol, symbol_def_line[symbol]))
792 INPUT.close()
794 OutputMissingDocumentation()
795 OutputUndeclaredSymbols()
796 OutputUnusedSymbols()
798 if options.outputallsymbols:
799 OutputAllSymbols()
801 if options.outputsymbolswithoutsince:
802 OutputSymbolsWithoutSince()
804 for filename in options.expand_content_files.split():
805 file_changed = OutputExtraFile(filename)
806 if file_changed:
807 changed = True
809 return (changed, book_top, book_bottom)
812 def DetermineNamespace():
813 """Find common set of characters."""
814 name_space = ''
815 pos = 0
816 ratio = 0.0
817 while True:
818 prefix = {}
819 letter = ''
820 for symbol in iterkeys(IndexEntriesFull):
821 if name_space == '' or name_space.lower() in symbol.lower():
822 if len(symbol) > pos:
823 letter = symbol[pos:pos + 1]
824 # stop prefix scanning
825 if letter == "_":
826 # stop on "_"
827 break
828 # Should we also stop on a uppercase char, if last was lowercase
829 # GtkWidget, if we have the 'W' and had the 't' before
830 # or should we count upper and lowercase, and stop one 2nd uppercase, if we already had a lowercase
831 # GtkWidget, the 'W' would be the 2nd uppercase and with 't','k' we had lowercase chars before
832 # need to recound each time as this is per symbol
833 ul = letter.upper()
834 if ul in prefix:
835 prefix[ul] += 1
836 else:
837 prefix[ul] = 1
839 if letter != '' and letter != "_":
840 maxletter = ''
841 maxsymbols = 0
842 for letter in iterkeys(prefix):
843 logging.debug("ns prefix: %s: %s", letter, prefix[letter])
844 if prefix[letter] > maxsymbols:
845 maxletter = letter
846 maxsymbols = prefix[letter]
848 ratio = float(len(IndexEntriesFull)) / prefix[maxletter]
849 logging.debug('most symbols start with %s, that is %f', maxletter, (100 * ratio))
850 if ratio > 0.9:
851 # do another round
852 name_space += maxletter
854 pos += 1
856 else:
857 ratio = 0.0
859 if ratio < 0.9:
860 break
861 return name_space
864 def OutputIndex(basename, apiindex):
865 """Writes an index that can be included into the main-document into an <index> tag.
867 Args:
868 basename (str): name of the index file without extension
869 apiindex (dict): the index data
871 old_index = os.path.join(DB_OUTPUT_DIR, basename + '.xml')
872 new_index = os.path.join(DB_OUTPUT_DIR, basename + '.new')
873 lastletter = " "
874 divopen = 0
875 symbol = None
876 short_symbol = None
878 OUTPUT = open(new_index, 'w')
880 OUTPUT.write(MakeDocHeader("indexdiv") + "\n<indexdiv id=\"%s\">\n" % basename)
882 logging.info("generate %s index (%d entries) with namespace %s", basename, len(apiindex), NAME_SPACE)
884 # do a case insensitive sort while chopping off the prefix
885 mapped_keys = [
887 'original': x,
888 'short': re.sub(r'^' + NAME_SPACE + r'\_?(.*)', r'\1', x.upper(), flags=re.I),
889 } for x in iterkeys(apiindex)]
890 sorted_keys = sorted(mapped_keys, key=lambda d: (d['short'], d['original']))
892 for key in sorted_keys:
893 symbol = key['original']
894 short = key['short']
895 if short != '':
896 short_symbol = short
897 else:
898 short_symbol = symbol
900 # generate a short symbol description
901 symbol_desc = ''
902 symbol_section = ''
903 symbol_section_id = ''
904 symbol_type = ''
905 if symbol in DeclarationTypes:
906 symbol_type = DeclarationTypes[symbol].lower()
908 if symbol_type == '':
909 logging.info("trying symbol %s", symbol)
910 m1 = re.search(r'(.*)::(.*)', symbol)
911 m2 = re.search(r'(.*):(.*)', symbol)
912 if m1:
913 oname = m1.group(1)
914 osym = m1.group(2)
915 logging.info(" trying object signal %s:%s in %d signals", oname, osym, len(SignalNames))
916 for name in SignalNames:
917 logging.info(" " + name)
918 if name == osym:
919 symbol_type = "object signal"
920 if oname in SymbolSection:
921 symbol_section = SymbolSection[oname]
922 symbol_section_id = SymbolSectionId[oname]
923 break
924 elif m2:
925 oname = m2.group(1)
926 osym = m2.group(2)
927 logging.info(" trying object property %s::%s in %d properties", oname, osym, len(ArgNames))
928 for name in ArgNames:
929 logging.info(" " + name)
930 if name == osym:
931 symbol_type = "object property"
932 if oname in SymbolSection:
933 symbol_section = SymbolSection[oname]
934 symbol_section_id = SymbolSectionId[oname]
935 break
936 else:
937 if symbol in SymbolSection:
938 symbol_section = SymbolSection[symbol]
939 symbol_section_id = SymbolSectionId[symbol]
941 if symbol_type != '':
942 symbol_desc = ", " + symbol_type
943 if symbol_section != '':
944 symbol_desc += " in <link linkend=\"%s\">%s</link>" % (symbol_section_id, symbol_section)
945 # symbol_desc +=" in " + ExpandAbbreviations(symbol, "#symbol_section")
947 curletter = short_symbol[0].upper()
948 ixid = apiindex[symbol]
950 logging.info(" add symbol %s with %s to index in section '%s' (derived from %s)",
951 symbol, ixid, curletter, short_symbol)
953 if curletter != lastletter:
954 lastletter = curletter
956 if divopen:
957 OUTPUT.write("</indexdiv>\n")
959 OUTPUT.write("<indexdiv><title>%s</title>\n" % curletter)
960 divopen = True
962 OUTPUT.write('<indexentry><primaryie linkends="%s"><link linkend="%s">%s</link>%s</primaryie></indexentry>\n' %
963 (ixid, ixid, symbol, symbol_desc))
965 if divopen:
966 OUTPUT.write("</indexdiv>\n")
968 OUTPUT.write("</indexdiv>\n")
969 OUTPUT.close()
971 common.UpdateFileIfChanged(old_index, new_index, 0)
974 def OutputSinceIndexes():
975 """Generate the 'since' api index files."""
976 for version in set(Since.values()):
977 logging.info("Since : [%s]", version)
978 index = {x: IndexEntriesSince[x] for x in iterkeys(IndexEntriesSince) if Since[x] == version}
979 OutputIndex("api-index-" + version, index)
982 def OutputAnnotationGlossary():
983 """Writes a glossary of the used annotation terms.
985 The glossary file can be included into the main document.
987 # if there are no annotations used return
988 if not AnnotationsUsed:
989 return
991 old_glossary = os.path.join(DB_OUTPUT_DIR, "annotation-glossary.xml")
992 new_glossary = os.path.join(DB_OUTPUT_DIR, "annotation-glossary.new")
993 lastletter = " "
994 divopen = False
996 # add acronyms that are referenced from acronym text
997 rerun = True
998 while rerun:
999 rerun = False
1000 for annotation in AnnotationsUsed:
1001 if annotation not in AnnotationDefinition:
1002 continue
1003 m = re.search(r'<acronym>([\w ]+)<\/acronym>', AnnotationDefinition[annotation])
1004 if m and m.group(1) not in AnnotationsUsed:
1005 AnnotationsUsed[m.group(1)] = 1
1006 rerun = True
1007 break
1009 OUTPUT = common.open_text(new_glossary, 'w')
1011 OUTPUT.write('''%s
1012 <glossary id="annotation-glossary">
1013 <title>Annotation Glossary</title>
1014 ''' % MakeDocHeader("glossary"))
1016 for annotation in sorted(iterkeys(AnnotationsUsed), key=str.lower):
1017 if annotation in AnnotationDefinition:
1018 definition = AnnotationDefinition[annotation]
1019 curletter = annotation[0].upper()
1021 if curletter != lastletter:
1022 lastletter = curletter
1024 if divopen:
1025 OUTPUT.write("</glossdiv>\n")
1027 OUTPUT.write("<glossdiv><title>%s</title>\n" % curletter)
1028 divopen = True
1030 OUTPUT.write(''' <glossentry>
1031 <glossterm><anchor id="annotation-glossterm-%s"/>%s</glossterm>
1032 <glossdef>
1033 <para>%s</para>
1034 </glossdef>
1035 </glossentry>
1036 ''' % (annotation, annotation, definition))
1038 if divopen:
1039 OUTPUT.write("</glossdiv>\n")
1041 OUTPUT.write("</glossary>\n")
1042 OUTPUT.close()
1044 common.UpdateFileIfChanged(old_glossary, new_glossary, 0)
1047 def ReadKnownSymbols(file):
1048 """Collect the names of non-private symbols from the $MODULE-sections.txt file.
1050 Args:
1051 file: the $MODULE-sections.txt file
1054 subsection = ''
1056 logging.info("Reading: %s", file)
1057 INPUT = common.open_text(file)
1058 for line in INPUT:
1059 if line.startswith('#'):
1060 continue
1062 if line.startswith('<SECTION>'):
1063 subsection = ''
1064 continue
1066 m = re.search(r'^<SUBSECTION\s*(.*)>', line, flags=re.I)
1067 if m:
1068 subsection = m.group(1)
1069 continue
1071 if line.startswith('<SUBSECTION>'):
1072 continue
1074 if re.search(r'^<TITLE>(.*)<\/TITLE>', line):
1075 continue
1077 m = re.search(r'^<FILE>(.*)<\/FILE>', line)
1078 if m:
1079 KnownSymbols[m.group(1) + ":Long_Description"] = 1
1080 KnownSymbols[m.group(1) + ":Short_Description"] = 1
1081 continue
1083 m = re.search(r'^<INCLUDE>(.*)<\/INCLUDE>', line)
1084 if m:
1085 continue
1087 m = re.search(r'^<\/SECTION>', line)
1088 if m:
1089 continue
1091 m = re.search(r'^(\S+)', line)
1092 if m:
1093 symbol = m.group(1)
1094 if subsection != "Standard" and subsection != "Private":
1095 KnownSymbols[symbol] = 1
1096 else:
1097 KnownSymbols[symbol] = 0
1098 INPUT.close()
1101 def OutputDeclaration(symbol, declaration):
1102 """Returns the formatted documentation block for a symbol.
1104 Args:
1105 symbol (str): the name of the function/macro/...
1106 declaration (str): the declaration of the function/macro.
1108 Returns:
1109 str: the formatted documentation
1112 dtype = DeclarationTypes[symbol]
1113 if dtype == 'MACRO':
1114 return OutputMacro(symbol, declaration)
1115 elif dtype == 'TYPEDEF':
1116 return OutputTypedef(symbol, declaration)
1117 elif dtype == 'STRUCT':
1118 return OutputStruct(symbol, declaration)
1119 elif dtype == 'ENUM':
1120 return OutputEnum(symbol, declaration)
1121 elif dtype == 'UNION':
1122 return OutputUnion(symbol, declaration)
1123 elif dtype == 'VARIABLE':
1124 return OutputVariable(symbol, declaration)
1125 elif dtype == 'FUNCTION':
1126 return OutputFunction(symbol, declaration, dtype)
1127 elif dtype == 'USER_FUNCTION':
1128 return OutputFunction(symbol, declaration, dtype)
1129 else:
1130 logging.warning("Unknown symbol type %s for symbol %s", dtype, symbol)
1131 return ('', '')
1134 def OutputSymbolTraits(symbol):
1135 """Returns the Since and StabilityLevel paragraphs for a symbol.
1137 Args:
1138 symbol (str): the name to describe
1140 Returns:
1141 str: paragraph or empty string
1144 desc = ''
1146 if symbol in Since:
1147 link_id = "api-index-" + Since[symbol]
1148 desc += "<para role=\"since\">Since: <link linkend=\"%s\">%s</link></para>" % (link_id, Since[symbol])
1150 if symbol in StabilityLevel:
1151 stability = StabilityLevel[symbol]
1152 if stability in AnnotationDefinition:
1153 AnnotationsUsed[stability] = True
1154 stability = "<acronym>%s</acronym>" % stability
1155 desc += "<para role=\"stability\">Stability Level: %s</para>" % stability
1156 return desc
1159 def uri_escape(text):
1160 if text is None:
1161 return None
1163 # Build a char to hex map
1164 escapes = {chr(i): ("%%%02X" % i) for i in range(256)}
1166 # Default unsafe characters. RFC 2732 ^(uric - reserved)
1167 def do_escape(char):
1168 return escapes[char]
1169 return re.sub(r"([^A-Za-z0-9\-_.!~*'()]", do_escape, text)
1172 def OutputSymbolExtraLinks(symbol):
1173 """Returns extralinks for the symbol (if enabled).
1175 Args:
1176 symbol (str): the name to describe
1178 Returns:
1179 str: paragraph or empty string
1181 desc = ''
1183 if False: # NEW FEATURE: needs configurability
1184 sstr = uri_escape(symbol)
1185 mstr = uri_escape(MODULE)
1186 desc += '''<ulink role="extralinks" url="http://www.google.com/codesearch?q=%s">code search</ulink>
1187 <ulink role="extralinks" url="http://library.gnome.org/edit?module=%s&amp;symbol=%s">edit documentation</ulink>
1188 ''' % (sstr, mstr, sstr)
1190 return desc
1193 def OutputSectionExtraLinks(symbol, docsymbol):
1194 desc = ''
1196 if False: # NEW FEATURE: needs configurability
1197 sstr = uri_escape(symbol)
1198 mstr = uri_escape(MODULE)
1199 dsstr = uri_escape(docsymbol)
1200 desc += '''<ulink role="extralinks" url="http://www.google.com/codesearch?q=%s">code search</ulink>
1201 <ulink role="extralinks" url="http://library.gnome.org/edit?module=%s&amp;symbol=%s">edit documentation</ulink>
1202 ''' % (sstr, mstr, dsstr)
1203 return desc
1206 def OutputMacro(symbol, declaration):
1207 """Returns the synopsis and detailed description of a macro.
1209 Args:
1210 symbol (str): the macro name.
1211 declaration (str): the declaration of the macro.
1213 Returns:
1214 str: the formated docs
1216 sid = common.CreateValidSGMLID(symbol)
1217 condition = MakeConditionDescription(symbol)
1218 synop = "<row><entry role=\"define_keyword\">#define</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link>" % (
1219 sid, symbol)
1221 fields = common.ParseMacroDeclaration(declaration, CreateValidSGML)
1222 title = symbol
1223 if len(fields) > 0:
1224 title += '()'
1226 desc = '<refsect2 id="%s" role="macro"%s>\n<title>%s</title>\n' % (sid, condition, title)
1227 desc += MakeIndexterms(symbol, sid)
1228 desc += "\n"
1229 desc += OutputSymbolExtraLinks(symbol)
1231 if len(fields) > 0:
1232 synop += "<phrase role=\"c_punctuation\">()</phrase>"
1234 synop += "</entry></row>\n"
1236 # Don't output the macro definition if is is a conditional macro or it
1237 # looks like a function, i.e. starts with "g_" or "_?gnome_", or it is
1238 # longer than 2 lines, otherwise we get lots of complicated macros like
1239 # g_assert.
1240 if symbol not in DeclarationConditional and not symbol.startswith('g_') \
1241 and not re.search(r'^_?gnome_', symbol) and declaration.count('\n') < 2:
1242 decl_out = CreateValidSGML(declaration)
1243 desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out
1244 else:
1245 desc += "<programlisting language=\"C\">" + "#define".ljust(RETURN_TYPE_FIELD_WIDTH) + symbol
1246 m = re.search(r'^\s*#\s*define\s+\w+(\([^\)]*\))', declaration)
1247 if m:
1248 args = m.group(1)
1249 pad = ' ' * (RETURN_TYPE_FIELD_WIDTH - len("#define "))
1250 # Align each line so that if should all line up OK.
1251 args = args.replace('\n', '\n' + pad)
1252 desc += CreateValidSGML(args)
1254 desc += "</programlisting>\n"
1256 desc += MakeDeprecationNote(symbol)
1258 parameters = OutputParamDescriptions("MACRO", symbol, fields)
1260 if symbol in SymbolDocs:
1261 symbol_docs = ConvertMarkDown(symbol, SymbolDocs[symbol])
1262 desc += symbol_docs
1264 desc += parameters
1265 desc += OutputSymbolTraits(symbol)
1266 desc += "</refsect2>\n"
1267 return (synop, desc)
1270 def OutputTypedef(symbol, declaration):
1271 """Returns the synopsis and detailed description of a typedef.
1273 Args:
1274 symbol (str): the typedef.
1275 declaration (str): the declaration of the typedef,
1276 e.g. 'typedef unsigned int guint;'
1278 Returns:
1279 str: the formated docs
1281 sid = common.CreateValidSGMLID(symbol)
1282 condition = MakeConditionDescription(symbol)
1283 desc = "<refsect2 id=\"%s\" role=\"typedef\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
1284 synop = "<row><entry role=\"typedef_keyword\">typedef</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1285 sid, symbol)
1287 desc += MakeIndexterms(symbol, sid)
1288 desc += "\n"
1289 desc += OutputSymbolExtraLinks(symbol)
1291 if symbol not in DeclarationConditional:
1292 decl_out = CreateValidSGML(declaration)
1293 desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out
1295 desc += MakeDeprecationNote(symbol)
1297 if symbol in SymbolDocs:
1298 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1300 desc += OutputSymbolTraits(symbol)
1301 desc += "</refsect2>\n"
1302 return (synop, desc)
1305 def OutputStruct(symbol, declaration):
1306 """Returns the synopsis and detailed description of a struct.
1308 We check if it is a object struct, and if so we only output parts of it that
1309 are noted as public fields. We also use a different IDs for object structs,
1310 since the original ID is used for the entire RefEntry.
1312 Args:
1313 symbol (str): the struct.
1314 declaration (str): the declaration of the struct.
1316 Returns:
1317 str: the formated docs
1319 is_gtype = False
1320 default_to_public = True
1321 if CheckIsObject(symbol):
1322 logging.info("Found struct gtype: %s", symbol)
1323 is_gtype = True
1324 default_to_public = ObjectRoots[symbol] == 'GBoxed'
1326 sid = None
1327 condition = None
1328 if is_gtype:
1329 sid = common.CreateValidSGMLID(symbol + "_struct")
1330 condition = MakeConditionDescription(symbol + "_struct")
1331 else:
1332 sid = common.CreateValidSGMLID(symbol)
1333 condition = MakeConditionDescription(symbol)
1335 # Determine if it is a simple struct or it also has a typedef.
1336 has_typedef = False
1337 if symbol in StructHasTypedef or re.search(r'^\s*typedef\s+', declaration):
1338 has_typedef = True
1340 type_output = None
1341 desc = None
1342 if has_typedef:
1343 # For structs with typedefs we just output the struct name.
1344 type_output = ''
1345 desc = "<refsect2 id=\"%s\" role=\"struct\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
1346 else:
1347 type_output = "struct"
1348 desc = "<refsect2 id=\"%s\" role=\"struct\"%s>\n<title>struct %s</title>\n" % (sid, condition, symbol)
1350 synop = "<row><entry role=\"datatype_keyword\">%s</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1351 type_output, sid, symbol)
1353 desc += MakeIndexterms(symbol, sid)
1354 desc += "\n"
1355 desc += OutputSymbolExtraLinks(symbol)
1357 # Form a pretty-printed, private-data-removed form of the declaration
1359 decl_out = ''
1360 if re.search(r'^\s*$', declaration):
1361 logging.info("Found opaque struct: %s", symbol)
1362 decl_out = "typedef struct _%s %s;" % (symbol, symbol)
1363 elif re.search(r'^\s*struct\s+\w+\s*;\s*$', declaration):
1364 logging.info("Found opaque struct: %s", symbol)
1365 decl_out = "struct %s;" % symbol
1366 else:
1367 m = re.search(
1368 r'^\s*(typedef\s+)?struct\s*\w*\s*(?:\/\*.*\*\/)?\s*{(.*)}\s*\w*\s*;\s*$', declaration, flags=re.S)
1369 if m:
1370 struct_contents = m.group(2)
1372 public = default_to_public
1373 new_declaration = ''
1375 for decl_line in struct_contents.splitlines():
1376 logging.info("Struct line: %s", decl_line)
1377 m2 = re.search(r'/\*\s*<\s*public\s*>\s*\*/', decl_line)
1378 m3 = re.search(r'/\*\s*<\s*(private|protected)\s*>\s*\*/', decl_line)
1379 if m2:
1380 public = True
1381 elif m3:
1382 public = False
1383 elif public:
1384 new_declaration += decl_line + "\n"
1386 if new_declaration:
1387 # Strip any blank lines off the ends.
1388 new_declaration = re.sub(r'^\s*\n', '', new_declaration)
1389 new_declaration = re.sub(r'\n\s*$', r'\n', new_declaration)
1391 if has_typedef:
1392 decl_out = "typedef struct {\n%s} %s;\n" % (new_declaration, symbol)
1393 else:
1394 decl_out = "struct %s {\n%s};\n" % (symbol, new_declaration)
1396 else:
1397 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1398 "Couldn't parse struct:\n%s" % declaration)
1400 # If we couldn't parse the struct or it was all private, output an
1401 # empty struct declaration.
1402 if decl_out == '':
1403 if has_typedef:
1404 decl_out = "typedef struct _%s %s;" % (symbol, symbol)
1405 else:
1406 decl_out = "struct %s;" % symbol
1408 decl_out = CreateValidSGML(decl_out)
1409 desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out
1411 desc += MakeDeprecationNote(symbol)
1413 if symbol in SymbolDocs:
1414 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1416 # Create a table of fields and descriptions
1418 # FIXME: Inserting &#160's into the produced type declarations here would
1419 # improve the output in most situations ... except for function
1420 # members of structs!
1421 def pfunc(*args):
1422 return '<structfield id="%s">%s</structfield>' % (common.CreateValidSGMLID(sid + '.' + args[0]), args[0])
1423 fields = common.ParseStructDeclaration(declaration, not default_to_public, 0, MakeXRef, pfunc)
1424 params = SymbolParams.get(symbol)
1426 # If no parameters are filled in, we don't generate the description
1427 # table, for backwards compatibility.
1428 found = False
1429 if params:
1430 found = next((True for p in params.values() if p.strip() != ''), False)
1432 if found:
1433 field_descrs = params
1434 missing_parameters = ''
1435 unused_parameters = ''
1436 sid = common.CreateValidSGMLID(symbol + ".members")
1438 desc += '''<refsect3 id="%s" role="struct_members">\n<title>Members</title>
1439 <informaltable role="struct_members_table" pgwide="1" frame="none">
1440 <tgroup cols="3">
1441 <colspec colname="struct_members_name" colwidth="300px"/>
1442 <colspec colname="struct_members_description"/>
1443 <colspec colname="struct_members_annotations" colwidth="200px"/>
1444 <tbody>
1445 ''' % sid
1447 for field_name, text in iteritems(fields):
1448 param_annotations = ''
1450 desc += "<row role=\"member\"><entry role=\"struct_member_name\"><para>%s</para></entry>\n" % text
1451 if field_name in field_descrs:
1452 (field_descr, param_annotations) = ExpandAnnotation(symbol, field_descrs[field_name])
1453 field_descr = ConvertMarkDown(symbol, field_descr)
1454 # trim
1455 field_descr = re.sub(r'^(\s|\n)+', '', field_descr, flags=re.M | re.S)
1456 field_descr = re.sub(r'(\s|\n)+$', '', field_descr, flags=re.M | re.S)
1457 desc += "<entry role=\"struct_member_description\">%s</entry>\n<entry role=\"struct_member_annotations\">%s</entry>\n" % (
1458 field_descr, param_annotations)
1459 del field_descrs[field_name]
1460 else:
1461 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1462 "Field description for %s::%s is missing in source code comment block." % (symbol, field_name))
1463 if missing_parameters != '':
1464 missing_parameters += ", " + field_name
1465 else:
1466 missing_parameters = field_name
1468 desc += "<entry /><entry />\n"
1470 desc += "</row>\n"
1472 desc += "</tbody></tgroup></informaltable>\n</refsect3>\n"
1473 for field_name in field_descrs:
1474 # Documenting those standard fields is not required anymore, but
1475 # we don't want to warn if they are documented anyway.
1476 m = re.search(r'(g_iface|parent_instance|parent_class)', field_name)
1477 if m:
1478 continue
1480 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1481 "Field description for %s::%s is not used from source code comment block." % (symbol, field_name))
1482 if unused_parameters != '':
1483 unused_parameters += ", " + field_name
1484 else:
1485 unused_parameters = field_name
1487 # remember missing/unused parameters (needed in tmpl-free build)
1488 if missing_parameters != '' and (symbol not in AllIncompleteSymbols):
1489 AllIncompleteSymbols[symbol] = missing_parameters
1491 if unused_parameters != '' and (symbol not in AllUnusedSymbols):
1492 AllUnusedSymbols[symbol] = unused_parameters
1493 else:
1494 if fields:
1495 if symbol not in AllIncompleteSymbols:
1496 AllIncompleteSymbols[symbol] = "<items>"
1497 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1498 "Field descriptions for struct %s are missing in source code comment block." % symbol)
1499 logging.info("Remaining structs fields: " + ':'.join(fields) + "\n")
1501 desc += OutputSymbolTraits(symbol)
1502 desc += "</refsect2>\n"
1503 return (synop, desc)
1506 def OutputUnion(symbol, declaration):
1507 """Returns the synopsis and detailed description of a union.
1509 Args:
1510 symbol (str): the union.
1511 declaration (str): the declaration of the union.
1513 Returns:
1514 str: the formated docs
1516 is_gtype = False
1517 if CheckIsObject(symbol):
1518 logging.info("Found union gtype: %s", symbol)
1519 is_gtype = True
1521 sid = None
1522 condition = None
1523 if is_gtype:
1524 sid = common.CreateValidSGMLID(symbol + "_union")
1525 condition = MakeConditionDescription(symbol + "_union")
1526 else:
1527 sid = common.CreateValidSGMLID(symbol)
1528 condition = MakeConditionDescription(symbol)
1530 # Determine if it is a simple struct or it also has a typedef.
1531 has_typedef = False
1532 if symbol in StructHasTypedef or re.search(r'^\s*typedef\s+', declaration):
1533 has_typedef = True
1535 type_output = None
1536 desc = None
1537 if has_typedef:
1538 # For unions with typedefs we just output the union name.
1539 type_output = ''
1540 desc = "<refsect2 id=\"%s\" role=\"union\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
1541 else:
1542 type_output = "union"
1543 desc = "<refsect2 id=\"%s\" role=\"union\"%s>\n<title>union %s</title>\n" % (sid, condition, symbol)
1545 synop = "<row><entry role=\"datatype_keyword\">%s</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1546 type_output, sid, symbol)
1548 desc += MakeIndexterms(symbol, sid)
1549 desc += "\n"
1550 desc += OutputSymbolExtraLinks(symbol)
1551 desc += MakeDeprecationNote(symbol)
1553 if symbol in SymbolDocs:
1554 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1556 # Create a table of fields and descriptions
1558 # FIXME: Inserting &#160's into the produced type declarations here would
1559 # improve the output in most situations ... except for function
1560 # members of structs!
1561 def pfunc(*args):
1562 return '<structfield id="%s">%s</structfield>' % (common.CreateValidSGMLID(sid + '.' + args[0]), args[0])
1563 fields = common.ParseStructDeclaration(declaration, 0, 0, MakeXRef, pfunc)
1564 params = SymbolParams.get(symbol)
1566 # If no parameters are filled in, we don't generate the description
1567 # table, for backwards compatibility
1568 found = False
1569 if params:
1570 found = next((True for p in params.values() if p.strip() != ''), False)
1572 logging.debug('Union %s has %d entries, found=%d, has_typedef=%d', symbol, len(fields), found, has_typedef)
1574 if found:
1575 field_descrs = params
1576 missing_parameters = ''
1577 unused_parameters = ''
1578 sid = common.CreateValidSGMLID('%s.members' % symbol)
1580 desc += '''<refsect3 id="%s" role="union_members">\n<title>Members</title>
1581 <informaltable role="union_members_table" pgwide="1" frame="none">
1582 <tgroup cols="3">
1583 <colspec colname="union_members_name" colwidth="300px"/>
1584 <colspec colname="union_members_description"/>
1585 <colspec colname="union_members_annotations" colwidth="200px"/>
1586 <tbody>
1587 ''' % sid
1589 for field_name, text in iteritems(fields):
1590 param_annotations = ''
1592 desc += "<row><entry role=\"union_member_name\"><para>%s</para></entry>\n" % text
1593 if field_name in field_descrs:
1594 (field_descr, param_annotations) = ExpandAnnotation(symbol, field_descrs[field_name])
1595 field_descr = ConvertMarkDown(symbol, field_descr)
1597 # trim
1598 field_descr = re.sub(r'^(\s|\n)+', '', field_descr, flags=re.M | re.S)
1599 field_descr = re.sub(r'(\s|\n)+$', '', field_descr, flags=re.M | re.S)
1600 desc += "<entry role=\"union_member_description\">%s</entry>\n<entry role=\"union_member_annotations\">%s</entry>\n" % (
1601 field_descr, param_annotations)
1602 del field_descrs[field_name]
1603 else:
1604 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1605 "Field description for %s::%s is missing in source code comment block." % (symbol, field_name))
1606 if missing_parameters != '':
1607 missing_parameters += ", " + field_name
1608 else:
1609 missing_parameters = field_name
1611 desc += "<entry /><entry />\n"
1613 desc += "</row>\n"
1615 desc += "</tbody></tgroup></informaltable>\n</refsect3>"
1616 for field_name in field_descrs:
1617 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1618 "Field description for %s::%s is not used from source code comment block." % (symbol, field_name))
1619 if unused_parameters != '':
1620 unused_parameters += ", " + field_name
1621 else:
1622 unused_parameters = field_name
1624 # remember missing/unused parameters (needed in tmpl-free build)
1625 if missing_parameters != '' and (symbol not in AllIncompleteSymbols):
1626 AllIncompleteSymbols[symbol] = missing_parameters
1628 if unused_parameters != '' and (symbol not in AllUnusedSymbols):
1629 AllUnusedSymbols[symbol] = unused_parameters
1630 else:
1631 if len(fields) > 0:
1632 if symbol not in AllIncompleteSymbols:
1633 AllIncompleteSymbols[symbol] = "<items>"
1634 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1635 "Field descriptions for union %s are missing in source code comment block." % symbol)
1636 logging.info("Remaining union fields: " + ':'.join(fields) + "\n")
1638 desc += OutputSymbolTraits(symbol)
1639 desc += "</refsect2>\n"
1640 return (synop, desc)
1643 def OutputEnum(symbol, declaration):
1644 """Returns the synopsis and detailed description of a enum.
1646 Args:
1647 symbol (str): the enum.
1648 declaration (str): the declaration of the enum.
1650 Returns:
1651 str: the formated docs
1653 is_gtype = False
1654 if CheckIsObject(symbol):
1655 logging.info("Found enum gtype: %s", symbol)
1656 is_gtype = True
1658 sid = None
1659 condition = None
1660 if is_gtype:
1661 sid = common.CreateValidSGMLID(symbol + "_enum")
1662 condition = MakeConditionDescription(symbol + "_enum")
1663 else:
1664 sid = common.CreateValidSGMLID(symbol)
1665 condition = MakeConditionDescription(symbol)
1667 synop = "<row><entry role=\"datatype_keyword\">enum</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1668 sid, symbol)
1669 desc = "<refsect2 id=\"%s\" role=\"enum\"%s>\n<title>enum %s</title>\n" % (sid, condition, symbol)
1671 desc += MakeIndexterms(symbol, sid)
1672 desc += "\n"
1673 desc += OutputSymbolExtraLinks(symbol)
1674 desc += MakeDeprecationNote(symbol)
1676 if symbol in SymbolDocs:
1677 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1679 # Create a table of fields and descriptions
1681 fields = common.ParseEnumDeclaration(declaration)
1682 params = SymbolParams.get(symbol)
1684 # If nothing at all is documented log a single summary warning at the end.
1685 # Otherwise, warn about each undocumented item.
1687 found = False
1688 if params:
1689 found = next((True for p in params.values() if p.strip() != ''), False)
1690 field_descrs = params
1691 else:
1692 field_descrs = {}
1694 missing_parameters = ''
1695 unused_parameters = ''
1697 sid = common.CreateValidSGMLID("%s.members" % symbol)
1698 desc += '''<refsect3 id="%s" role="enum_members">\n<title>Members</title>
1699 <informaltable role="enum_members_table" pgwide="1" frame="none">
1700 <tgroup cols="3">
1701 <colspec colname="enum_members_name" colwidth="300px"/>
1702 <colspec colname="enum_members_description"/>
1703 <colspec colname="enum_members_annotations" colwidth="200px"/>
1704 <tbody>
1705 ''' % sid
1707 for field_name in fields:
1708 field_descr = field_descrs.get(field_name)
1709 param_annotations = ''
1711 sid = common.CreateValidSGMLID(field_name)
1712 condition = MakeConditionDescription(field_name)
1713 desc += "<row role=\"constant\"><entry role=\"enum_member_name\"><para id=\"%s\">%s</para></entry>\n" % (
1714 sid, field_name)
1715 if field_descr:
1716 field_descr, param_annotations = ExpandAnnotation(symbol, field_descr)
1717 field_descr = ConvertMarkDown(symbol, field_descr)
1718 desc += "<entry role=\"enum_member_description\">%s</entry>\n<entry role=\"enum_member_annotations\">%s</entry>\n" % (
1719 field_descr, param_annotations)
1720 del field_descrs[field_name]
1721 else:
1722 if found:
1723 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1724 "Value description for %s::%s is missing in source code comment block." % (symbol, field_name))
1725 if missing_parameters != '':
1726 missing_parameters += ", " + field_name
1727 else:
1728 missing_parameters = field_name
1729 desc += "<entry /><entry />\n"
1730 desc += "</row>\n"
1732 desc += "</tbody></tgroup></informaltable>\n</refsect3>"
1733 for field_name in field_descrs:
1734 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1735 "Value description for %s::%s is not used from source code comment block." % (symbol, field_name))
1736 if unused_parameters != '':
1737 unused_parameters += ", " + field_name
1738 else:
1739 unused_parameters = field_name
1741 # remember missing/unused parameters (needed in tmpl-free build)
1742 if missing_parameters != '' and (symbol not in AllIncompleteSymbols):
1743 AllIncompleteSymbols[symbol] = missing_parameters
1745 if unused_parameters != '' and (symbol not in AllUnusedSymbols):
1746 AllUnusedSymbols[symbol] = unused_parameters
1748 if not found:
1749 if len(fields) > 0:
1750 if symbol not in AllIncompleteSymbols:
1751 AllIncompleteSymbols[symbol] = "<items>"
1752 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1753 "Value descriptions for %s are missing in source code comment block." % symbol)
1755 desc += OutputSymbolTraits(symbol)
1756 desc += "</refsect2>\n"
1757 return (synop, desc)
1760 def OutputVariable(symbol, declaration):
1761 """Returns the synopsis and detailed description of a variable.
1763 Args:
1764 symbol (str): the extern'ed variable.
1765 declaration (str): the declaration of the variable.
1767 Returns:
1768 str: the formated docs
1770 sid = common.CreateValidSGMLID(symbol)
1771 condition = MakeConditionDescription(symbol)
1773 logging.info("ouputing variable: '%s' '%s'", symbol, declaration)
1775 type_output = None
1776 m1 = re.search(
1777 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)
1778 m2 = re.search(
1779 r'\s*((const\s+|signed\s+|unsigned\s+|long\s+|short\s+)*\w+)(\s+\*+|\*+|\s)(\s*)(const\s+)*([A-Za-z]\w*)\s*=', declaration)
1780 if m1:
1781 mod1 = m1.group(1) or ''
1782 ptr = m1.group(3) or ''
1783 space = m1.group(4) or ''
1784 mod2 = m1.group(5) or ''
1785 type_output = "extern %s%s%s%s" % (mod1, ptr, space, mod2)
1786 elif m2:
1787 mod1 = m2.group(1) or ''
1788 ptr = m2.group(3) or ''
1789 space = m2.group(4) or ''
1790 mod2 = m2.group(5) or ''
1791 type_output = '%s%s%s%s' % (mod1, ptr, space, mod2)
1792 else:
1793 type_output = "extern"
1795 synop = "<row><entry role=\"variable_type\">%s</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1796 type_output, sid, symbol)
1798 desc = "<refsect2 id=\"%s\" role=\"variable\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
1800 desc += MakeIndexterms(symbol, sid)
1801 desc += "\n"
1802 desc += OutputSymbolExtraLinks(symbol)
1804 decl_out = CreateValidSGML(declaration)
1805 desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out
1807 desc += MakeDeprecationNote(symbol)
1809 if symbol in SymbolDocs:
1810 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1812 if symbol in SymbolAnnotations:
1813 param_desc = SymbolAnnotations[symbol]
1814 param_annotations = ''
1815 (param_desc, param_annotations) = ExpandAnnotation(symbol, param_desc)
1816 if param_annotations != '':
1817 desc += "\n<para>%s</para>" % param_annotations
1819 desc += OutputSymbolTraits(symbol)
1820 desc += "</refsect2>\n"
1821 return (synop, desc)
1824 def OutputFunction(symbol, declaration, symbol_type):
1825 """Returns the synopsis and detailed description of a function.
1827 Args:
1828 symbol (str): the function.
1829 declaration (str): the declaration of the function.
1831 Returns:
1832 str: the formated docs
1834 sid = common.CreateValidSGMLID(symbol)
1835 condition = MakeConditionDescription(symbol)
1837 # Take out the return type
1838 # $1 $2 $3
1839 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'
1840 m = re.search(regex, declaration)
1841 declaration = re.sub(regex, '', declaration)
1842 type_modifier = m.group(1) or ''
1843 type = m.group(2)
1844 pointer = m.group(3)
1845 pointer = pointer.rstrip()
1846 xref = MakeXRef(type, tagify(type, "returnvalue"))
1847 start = ''
1848 # if (symbol_type == 'USER_FUNCTION')
1849 # start = "typedef "
1852 # We output const rather than G_CONST_RETURN.
1853 type_modifier = re.sub(r'G_CONST_RETURN', 'const', type_modifier)
1854 pointer = re.sub(r'G_CONST_RETURN', 'const', pointer)
1855 pointer = re.sub(r'^\s+', '&#160;', pointer)
1857 ret_type_output = "%s%s%s%s\n" % (start, type_modifier, xref, pointer)
1859 indent_len = len(symbol) + 2
1860 char1 = char2 = char3 = ''
1861 if symbol_type == 'USER_FUNCTION':
1862 indent_len += 3
1863 char1 = "<phrase role=\"c_punctuation\">(</phrase>"
1864 char2 = "*"
1865 char3 = "<phrase role=\"c_punctuation\">)</phrase>"
1867 symbol_output = "%s<link linkend=\"%s\">%s%s</link>%s" % (char1, sid, char2, symbol, char3)
1868 if indent_len < MAX_SYMBOL_FIELD_WIDTH:
1869 symbol_desc_output = "%s%s%s%s " % (char1, char2, symbol, char3)
1870 else:
1871 indent_len = MAX_SYMBOL_FIELD_WIDTH - 8
1872 symbol_desc_output = ('%s%s%s%s\n' % (char1, char2, symbol, char3)) + (' ' * (indent_len - 1))
1874 synop = "<row><entry role=\"function_type\">%s</entry><entry role=\"function_name\">%s&#160;<phrase role=\"c_punctuation\">()</phrase></entry></row>\n" % (
1875 ret_type_output, symbol_output)
1877 desc = "<refsect2 id=\"%s\" role=\"function\"%s>\n<title>%s&#160;()</title>\n" % (sid, condition, symbol)
1879 desc += MakeIndexterms(symbol, sid)
1880 desc += "\n"
1881 desc += OutputSymbolExtraLinks(symbol)
1883 desc += "<programlisting language=\"C\">%s%s(" % (ret_type_output, symbol_desc_output)
1885 def tagfun(*args):
1886 return tagify(args[0], "parameter")
1888 fields = common.ParseFunctionDeclaration(declaration, MakeXRef, tagfun)
1890 first = True
1891 for field_name in fields.values():
1892 if first:
1893 desc += field_name
1894 first = False
1895 else:
1896 desc += ",\n" + (' ' * indent_len) + field_name
1898 desc += ");</programlisting>\n"
1900 desc += MakeDeprecationNote(symbol)
1902 if symbol in SymbolDocs:
1903 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1905 if symbol in SymbolAnnotations:
1906 param_desc = SymbolAnnotations[symbol]
1907 (param_desc, param_annotations) = ExpandAnnotation(symbol, param_desc)
1908 if param_annotations != '':
1909 desc += "\n<para>%s</para>" % param_annotations
1911 desc += OutputParamDescriptions("FUNCTION", symbol, iterkeys(fields))
1912 desc += OutputSymbolTraits(symbol)
1913 desc += "</refsect2>\n"
1914 return (synop, desc)
1917 def OutputParamDescriptions(symbol_type, symbol, fields):
1918 """Returns the DocBook output describing the parameters of a symbol.
1920 This can be used for functions, macros or signal handlers.
1922 Args:
1923 symbol_type (str): 'FUNCTION', 'MACRO' or 'SIGNAL'. Signal
1924 handlers have an implicit user_data parameter last.
1925 symbol (str): the name of the symbol being described.
1926 fields (list): parsed fields from the declaration, used to determine
1927 undocumented/unused entries
1929 Returns:
1930 str: the formated parameter docs
1932 output = ''
1933 num_params = 0
1934 field_descrs = None
1936 if fields:
1937 field_descrs = [f for f in fields if f not in ['void', 'Returns']]
1938 else:
1939 field_descrs = []
1941 params = SymbolParams.get(symbol)
1942 logging.info("param_desc(%s, %s) = %s", symbol_type, symbol, str(params))
1943 # This might be an empty dict, but for SIGNALS we append the user_data docs.
1944 # TODO(ensonic): maybe create that docstring in GetSignals()
1945 if params is not None:
1946 returns = ''
1947 params_desc = ''
1948 missing_parameters = ''
1949 unused_parameters = ''
1951 for param_name, param_desc in iteritems(params):
1952 (param_desc, param_annotations) = ExpandAnnotation(symbol, param_desc)
1953 param_desc = ConvertMarkDown(symbol, param_desc)
1954 # trim
1955 param_desc = re.sub(r'^(\s|\n)+', '', param_desc, flags=re.M | re.S)
1956 param_desc = re.sub(r'(\s|\n)+$', '', param_desc, flags=re.M | re.S)
1957 if param_name == "Returns":
1958 returns = param_desc
1959 if param_annotations != '':
1960 returns += "\n<para>%s</para>" % param_annotations
1962 elif param_name == "void":
1963 # FIXME: &common.LogWarning()?
1964 logging.info("!!!! void in params for %s?\n", symbol)
1965 else:
1966 if fields:
1967 if param_name not in field_descrs:
1968 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1969 "Parameter description for %s::%s is not used from source code comment block." % (symbol, param_name))
1970 if unused_parameters != '':
1971 unused_parameters += ", " + param_name
1972 else:
1973 unused_parameters = param_name
1974 else:
1975 field_descrs.remove(param_name)
1977 if param_desc != '':
1978 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" % (
1979 param_name, param_desc, param_annotations)
1980 num_params += 1
1982 for param_name in field_descrs:
1983 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1984 "Parameter description for %s::%s is missing in source code comment block." % (symbol, param_name))
1985 if missing_parameters != '':
1986 missing_parameters += ", " + param_name
1987 else:
1988 missing_parameters = param_name
1990 # Signals have an implicit user_data parameter which we describe.
1991 if symbol_type == "SIGNAL":
1992 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"
1994 # Start a table if we need one.
1995 if params_desc != '':
1996 sid = common.CreateValidSGMLID("%s.parameters" % symbol)
1998 output += '''<refsect3 id="%s" role="parameters">\n<title>Parameters</title>
1999 <informaltable role="parameters_table" pgwide="1" frame="none">
2000 <tgroup cols="3">
2001 <colspec colname="parameters_name" colwidth="150px"/>
2002 <colspec colname="parameters_description"/>
2003 <colspec colname="parameters_annotations" colwidth="200px"/>
2004 <tbody>
2005 ''' % sid
2006 output += params_desc
2007 output += "</tbody></tgroup></informaltable>\n</refsect3>"
2009 # Output the returns info last
2010 if returns != '':
2011 sid = common.CreateValidSGMLID("%s.returns" % symbol)
2013 output += '''<refsect3 id="%s" role=\"returns\">\n<title>Returns</title>
2014 ''' % sid
2015 output += returns
2016 output += "\n</refsect3>"
2018 # remember missing/unused parameters (needed in tmpl-free build)
2019 if missing_parameters != '' and (symbol not in AllIncompleteSymbols):
2020 AllIncompleteSymbols[symbol] = missing_parameters
2022 if unused_parameters != '' and (symbol not in AllUnusedSymbols):
2023 AllUnusedSymbols[symbol] = unused_parameters
2025 if num_params == 0 and fields and field_descrs:
2026 if symbol not in AllIncompleteSymbols:
2027 AllIncompleteSymbols[symbol] = "<parameters>"
2028 return output
2031 def ParseStabilityLevel(stability, file, line, message):
2032 """Parses a stability level and outputs a warning if it isn't valid.
2033 Args:
2034 stability (str): the stability text.
2035 file, line: context for error message
2036 message: description of where the level is from, to use in any error message.
2037 Returns:
2038 str: the parsed stability level string.
2040 stability = stability.strip()
2041 sl = stability.strip().lower()
2042 if sl == 'stable':
2043 stability = "Stable"
2044 elif sl == 'unstable':
2045 stability = "Unstable"
2046 elif sl == 'private':
2047 stability = "Private"
2048 else:
2049 common.LogWarning(file, line,
2050 "%s is %s. It should be one of these: Stable, "
2051 "Unstable, or Private." % (
2052 message, stability))
2053 return str(stability)
2056 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):
2057 """Outputs the final DocBook file for one section.
2059 Args:
2060 file (str): the name of the file.
2061 title (str): the title from the $MODULE-sections.txt file
2062 section_id (str): the id to use for the toplevel tag.
2063 includes (str): comma-separates list of include files added at top of
2064 synopsis, with '<' '>' around them (if not already enclosed in '').
2065 functions_synop (str): the DocBook for the Functions Synopsis part.
2066 other_synop (str): the DocBook for the Types and Values Synopsis part.
2067 functions_details (str): the DocBook for the Functions Details part.
2068 other_details (str): the DocBook for the Types and Values Details part.
2069 signal_synop (str): the DocBook for the Signal Synopsis part
2070 signal_desc (str): the DocBook for the Signal Description part
2071 args_synop (str): the DocBook for the Arg Synopsis part
2072 args_desc (str): the DocBook for the Arg Description part
2073 hierarchy (str): the DocBook for the Object Hierarchy part
2074 interfaces (str): the DocBook for the Interfaces part
2075 implementations (str): the DocBook for the Known Implementations part
2076 prerequisites (str): the DocBook for the Prerequisites part
2077 derived (str): the DocBook for the Derived Interfaces part
2078 file_objects (list): objects in this file
2080 Returns:
2081 bool: True if the docs where updated
2084 logging.info("Output docbook for file %s with title '%s'", file, title)
2086 # The edited title overrides the one from the sections file.
2087 new_title = SymbolDocs.get(file + ":Title")
2088 if new_title and not new_title.strip() == '':
2089 title = new_title
2090 logging.info("Found title: %s", title)
2092 short_desc = SymbolDocs.get(file + ":Short_Description")
2093 if not short_desc or short_desc.strip() == '':
2094 short_desc = ''
2095 else:
2096 # Don't use ConvertMarkDown here for now since we don't want blocks
2097 short_desc = ExpandAbbreviations(title + ":Short_description", short_desc)
2098 logging.info("Found short_desc: %s", short_desc)
2100 long_desc = SymbolDocs.get(file + ":Long_Description")
2101 if not long_desc or long_desc.strip() == '':
2102 long_desc = ''
2103 else:
2104 long_desc = ConvertMarkDown(title + ":Long_description", long_desc)
2105 logging.info("Found long_desc: %s", long_desc)
2107 see_also = SymbolDocs.get(file + ":See_Also")
2108 if not see_also or re.search(r'^\s*(<para>)?\s*(</para>)?\s*$', see_also):
2109 see_also = ''
2110 else:
2111 see_also = ConvertMarkDown(title + ":See_Also", see_also)
2112 logging.info("Found see_also: %s", see_also)
2114 if see_also:
2115 see_also = "<refsect1 id=\"%s.see-also\">\n<title>See Also</title>\n%s\n</refsect1>\n" % (section_id, see_also)
2117 stability = SymbolDocs.get(file + ":Stability_Level")
2118 if not stability or re.search(r'^\s*$', stability):
2119 stability = ''
2120 else:
2121 line_number = GetSymbolSourceLine(file + ":Stability_Level")
2122 stability = ParseStabilityLevel(stability, file, line_number, "Section stability level")
2123 logging.info("Found stability: %s", stability)
2125 if not stability:
2126 stability = DEFAULT_STABILITY or ''
2128 if stability:
2129 if stability in AnnotationDefinition:
2130 AnnotationsUsed[stability] = True
2131 stability = "<acronym>%s</acronym>" % stability
2132 stability = "<refsect1 id=\"%s.stability-level\">\n<title>Stability Level</title>\n%s, unless otherwise indicated\n</refsect1>\n" % (
2133 section_id, stability)
2135 image = SymbolDocs.get(file + ":Image")
2136 if not image or re.search(r'^\s*$', image):
2137 image = ''
2138 else:
2139 image = image.strip()
2141 format = None
2143 il = image.lower()
2144 if re.search(r'jpe?g$', il):
2145 format = "format='JPEG'"
2146 elif il.endswith('png'):
2147 format = "format='PNG'"
2148 elif il.endswith('svg'):
2149 format = "format='SVG'"
2150 else:
2151 format = ''
2153 image = " <inlinegraphic fileref='%s' %s/>\n" % (image, format)
2155 include_output = ''
2156 if includes:
2157 include_output += "<refsect1 id=\"%s.includes\"><title>Includes</title><synopsis>" % section_id
2158 for include in includes.split(','):
2159 if re.search(r'^\".+\"$', include):
2160 include_output += "#include %s\n" % include
2161 else:
2162 include = re.sub(r'^\s+|\s+$', '', include, flags=re.S)
2163 include_output += "#include &lt;%s&gt;\n" % include
2165 include_output += "</synopsis></refsect1>\n"
2167 extralinks = OutputSectionExtraLinks(title, "Section:%s" % file)
2169 old_db_file = os.path.join(DB_OUTPUT_DIR, file + '.xml')
2170 new_db_file = os.path.join(DB_OUTPUT_DIR, file + '.xml.new')
2172 OUTPUT = common.open_text(new_db_file, 'w')
2174 object_anchors = ''
2175 for fobject in file_objects:
2176 if fobject == section_id:
2177 continue
2178 sid = common.CreateValidSGMLID(fobject)
2179 logging.info("Adding anchor for %s\n", fobject)
2180 object_anchors += "<anchor id=\"%s\"/>" % sid
2182 # Make sure we produce valid docbook
2183 if not functions_details:
2184 functions_details = "<para />"
2186 # We used to output this, but is messes up our common.UpdateFileIfChanged code
2187 # since it changes every day (and it is only used in the man pages):
2188 # "<refentry id="$section_id" revision="$mday $month $year">"
2190 OUTPUT.write(REFENTRY.substitute({
2191 'args_desc': args_desc,
2192 'args_synop': args_synop,
2193 'derived': derived,
2194 'extralinks': extralinks,
2195 'functions_details': functions_details,
2196 'functions_synop': functions_synop,
2197 'header': MakeDocHeader('refentry'),
2198 'hierarchy': hierarchy,
2199 'image': image,
2200 'include_output': include_output,
2201 'interfaces': interfaces,
2202 'implementations': implementations,
2203 'long_desc': long_desc,
2204 'object_anchors': object_anchors,
2205 'other_details': other_details,
2206 'other_synop': other_synop,
2207 'prerequisites': prerequisites,
2208 'section_id': section_id,
2209 'see_also': see_also,
2210 'signals_desc': signals_desc,
2211 'signals_synop': signals_synop,
2212 'short_desc': short_desc,
2213 'stability': stability,
2214 'title': title,
2215 'MODULE': MODULE.upper(),
2217 OUTPUT.close()
2219 return common.UpdateFileIfChanged(old_db_file, new_db_file, 0)
2222 def OutputProgramDBFile(program, section_id):
2223 """Outputs the final DocBook file for one program.
2225 Args:
2226 file (str): the name of the file.
2227 section_id (str): the id to use for the toplevel tag.
2229 Returns:
2230 bool: True if the docs where updated
2232 logging.info("Output program docbook for %s", program)
2234 short_desc = SourceSymbolDocs.get(program + ":Short_Description")
2235 if not short_desc or short_desc.strip() == '':
2236 short_desc = ''
2237 else:
2238 # Don't use ConvertMarkDown here for now since we don't want blocks
2239 short_desc = ExpandAbbreviations(program, short_desc)
2240 logging.info("Found short_desc: %s", short_desc)
2242 synopsis = SourceSymbolDocs.get(program + ":Synopsis")
2243 if synopsis and synopsis.strip() != '':
2244 items = synopsis.split(' ')
2245 for i in range(0, len(items)):
2246 parameter = items[i]
2247 choice = "plain"
2248 rep = ''
2250 # first parameter is the command name
2251 if i == 0:
2252 synopsis = "<command>%s</command>\n" % parameter
2253 continue
2255 # square brackets indicate optional parameters, curly brackets
2256 # indicate required parameters ("plain" parameters are also
2257 # mandatory, but do not get extra decoration)
2258 m1 = re.search(r'^\[(.+?)\]$', parameter)
2259 m2 = re.search(r'^\{(.+?)\}$', parameter)
2260 if m1:
2261 choice = "opt"
2262 parameter = m1.group(1)
2263 elif m2:
2264 choice = "req"
2265 parameter = m2.group(1)
2267 # parameters ending in "..." are repeatable
2268 if parameter.endswith('...'):
2269 rep = ' rep=\"repeat\"'
2270 parameter = parameter[:-3]
2272 # italic parameters are replaceable parameters
2273 parameter = re.sub(r'\*(.+?)\*', r'<replaceable>\1</replaceable>', parameter)
2275 synopsis += "<arg choice=\"%s\"%s>" % (choice, rep)
2276 synopsis += parameter
2277 synopsis += "</arg>\n"
2279 logging.info("Found synopsis: %s", synopsis)
2280 else:
2281 synopsis = "<command>%s</command>" % program
2283 long_desc = SourceSymbolDocs.get(program + ":Long_Description")
2284 if not long_desc or long_desc.strip() == '':
2285 long_desc = ''
2286 else:
2287 long_desc = ConvertMarkDown("%s:Long_description" % program, long_desc)
2288 logging.info("Found long_desc: %s", long_desc)
2290 options = ''
2291 o = program + ":Options"
2292 if o in SourceSymbolDocs:
2293 opts = SourceSymbolDocs[o].split('\t')
2295 logging.info('options: %d, %s', len(opts), str(opts))
2297 options = "<refsect1>\n<title>Options</title>\n<variablelist>\n"
2298 for k in range(0, len(opts), 2):
2299 opt_desc = opts[k + 1]
2301 opt_desc = re.sub(r'\*(.+?)\*', r'<replaceable>\1</replaceable>', opt_desc)
2303 options += "<varlistentry>\n<term>"
2304 opt_names = opts[k].split(',')
2305 for i in range(len(opt_names)):
2306 prefix = ', ' if i > 0 else ''
2307 # italic parameters are replaceable parameters
2308 opt_name = re.sub(r'\*(.+?)\*', r'<replaceable>\1</replaceable>', opt_names[i])
2310 options += "%s<option>%s</option>\n" % (prefix, opt_name)
2312 options += "</term>\n"
2313 options += "<listitem><para>%s</para></listitem>\n" % opt_desc
2314 options += "</varlistentry>\n"
2316 options += "</variablelist></refsect1>\n"
2318 exit_status = SourceSymbolDocs.get(program + ":Returns")
2319 if exit_status and exit_status != '':
2320 exit_status = ConvertMarkDown("%s:Returns" % program, exit_status)
2321 exit_status = "<refsect1 id=\"%s.exit-status\">\n<title>Exit Status</title>\n%s\n</refsect1>\n" % (
2322 section_id, exit_status)
2323 else:
2324 exit_status = ''
2326 see_also = SourceSymbolDocs.get(program + ":See_Also")
2327 if not see_also or re.search(r'^\s*(<para>)?\s*(</para>)?\s*$', see_also):
2328 see_also = ''
2329 else:
2330 see_also = ConvertMarkDown("%s:See_Also" % program, see_also)
2331 logging.info("Found see_also: %s", see_also)
2333 if see_also:
2334 see_also = "<refsect1 id=\"%s.see-also\">\n<title>See Also</title>\n%s\n</refsect1>\n" % (section_id, see_also)
2336 old_db_file = os.path.join(DB_OUTPUT_DIR, program + ".xml")
2337 new_db_file = os.path.join(DB_OUTPUT_DIR, program + ".xml.new")
2339 OUTPUT = common.open_text(new_db_file, 'w')
2341 OUTPUT.write('''%s
2342 <refentry id="%s">
2343 <refmeta>
2344 <refentrytitle role="top_of_page" id="%s.top_of_page">%s</refentrytitle>
2345 <manvolnum>1</manvolnum>
2346 <refmiscinfo>User Commands</refmiscinfo>
2347 </refmeta>
2348 <refnamediv>
2349 <refname>%s</refname>
2350 <refpurpose>%s</refpurpose>
2351 </refnamediv>
2352 <refsynopsisdiv>
2353 <cmdsynopsis>%s</cmdsynopsis>
2354 </refsynopsisdiv>
2355 <refsect1 id="%s.description" role="desc">
2356 <title role="desc.title">Description</title>
2358 </refsect1>
2359 %s%s%s
2360 </refentry>
2361 ''' % (MakeDocHeader("refentry"), section_id, section_id, program, program, short_desc, synopsis, section_id, long_desc, options, exit_status, see_also))
2362 OUTPUT.close()
2364 return common.UpdateFileIfChanged(old_db_file, new_db_file, 0)
2367 def OutputExtraFile(file):
2368 """Copies an "extra" DocBook file into the output directory, expanding abbreviations.
2370 Args:
2371 file (str): the source file.
2373 Returns:
2374 bool: True if the docs where updated
2377 basename = os.path.basename(file)
2379 old_db_file = os.path.join(DB_OUTPUT_DIR, basename)
2380 new_db_file = os.path.join(DB_OUTPUT_DIR, basename + ".new")
2382 contents = common.open_text(file).read()
2384 OUTPUT = common.open_text(new_db_file, 'w')
2385 OUTPUT.write(ExpandAbbreviations(basename + " file", contents))
2386 OUTPUT.close()
2388 return common.UpdateFileIfChanged(old_db_file, new_db_file, 0)
2391 def GetDocbookHeader(main_file):
2392 if os.path.exists(main_file):
2393 INPUT = common.open_text(main_file)
2394 header = ''
2395 for line in INPUT:
2396 if re.search(r'^\s*<(book|chapter|article)', line):
2397 # check that the top-level tagSYSTEM or the doctype decl contain the xinclude namespace decl
2398 if not re.search(r'http:\/\/www.w3.org\/200[13]\/XInclude', line) and \
2399 not re.search(r'http:\/\/www.w3.org\/200[13]\/XInclude', header, flags=re.MULTILINE):
2400 header = ''
2401 break
2403 # if there are SYSTEM ENTITIES here, we should prepend "../" to the path
2404 # FIXME: not sure if we can do this now, as people already work-around the problem
2405 # r'#<!ENTITY % ([a-zA-Z-]+) SYSTEM \"([^/][a-zA-Z./]+)\">', r'<!ENTITY % \1 SYSTEM \"../\2\">';
2406 line = re.sub(
2407 r'<!ENTITY % gtkdocentities SYSTEM "([^"]*)">', r'<!ENTITY % gtkdocentities SYSTEM "../\1">', line)
2408 header += line
2409 INPUT.close()
2410 header = header.strip()
2411 else:
2412 header = '''<?xml version="1.0"?>
2413 <!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
2414 "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
2416 <!ENTITY % local.common.attrib "xmlns:xi CDATA #FIXED 'http://www.w3.org/2003/XInclude'">
2417 <!ENTITY % gtkdocentities SYSTEM "../xml/gtkdocentities.ent">
2418 %gtkdocentities;
2419 ]>'''
2420 return header
2423 def OutputBook(main_file, book_top, book_bottom):
2424 """Outputs the entities that need to be included into the main docbook file for the module.
2426 Args:
2427 book_top (str): the declarations of the entities, which are added
2428 at the top of the main docbook file.
2429 book_bottom (str): the entities, which are added in the main docbook
2430 file at the desired position.
2433 old_file = os.path.join(DB_OUTPUT_DIR, MODULE + "-doc.top")
2434 new_file = os.path.join(DB_OUTPUT_DIR, MODULE + "-doc.top.new")
2436 OUTPUT = common.open_text(new_file, 'w')
2437 OUTPUT.write(book_top)
2438 OUTPUT.close()
2440 common.UpdateFileIfChanged(old_file, new_file, 0)
2442 old_file = os.path.join(DB_OUTPUT_DIR, MODULE + "-doc.bottom")
2443 new_file = os.path.join(DB_OUTPUT_DIR, MODULE + "-doc.bottom.new")
2445 OUTPUT = common.open_text(new_file, 'w')
2446 OUTPUT.write(book_bottom)
2447 OUTPUT.close()
2449 common.UpdateFileIfChanged(old_file, new_file, 0)
2451 # If the main docbook file hasn't been created yet, we create it here.
2452 # The user can tweak it later.
2453 if main_file and not os.path.exists(main_file):
2454 OUTPUT = common.open_text(main_file, 'w')
2456 logging.info("no master doc, create default one at: " + main_file)
2458 OUTPUT.write('''%s
2459 <book id="index">
2460 <bookinfo>
2461 <title>&package_name; Reference Manual</title>
2462 <releaseinfo>
2463 for &package_string;.
2464 The latest version of this documentation can be found on-line at
2465 <ulink role="online-location" url="http://[SERVER]/&package_name;/index.html">http://[SERVER]/&package_name;/</ulink>.
2466 </releaseinfo>
2467 </bookinfo>
2469 <chapter>
2470 <title>[Insert title here]</title>
2472 </chapter>
2473 ''' % (MakeDocHeader("book"), book_bottom))
2474 if os.path.exists('xml/tree_index.sgml'):
2475 OUTPUT.write(''' <chapter id="object-tree">
2476 <title>Object Hierarchy</title>
2477 <xi:include href="xml/tree_index.sgml"/>
2478 </chapter>
2479 ''')
2480 else:
2481 OUTPUT.write(''' <!-- enable this when you use gobject types
2482 <chapter id="object-tree">
2483 <title>Object Hierarchy</title>
2484 <xi:include href="xml/tree_index.sgml"/>
2485 </chapter>
2487 ''')
2489 OUTPUT.write(''' <index id="api-index-full">
2490 <title>API Index</title>
2491 <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
2492 </index>
2493 <index id="deprecated-api-index" role="deprecated">
2494 <title>Index of deprecated API</title>
2495 <xi:include href="xml/api-index-deprecated.xml"><xi:fallback /></xi:include>
2496 </index>
2497 ''')
2498 for version in set(Since.values()):
2499 dash_version = version.replace('.', '-')
2500 OUTPUT.write(''' <index id="api-index-%s" role="%s">
2501 <title>Index of new API in %s</title>
2502 <xi:include href="xml/api-index-%s.xml"><xi:fallback /></xi:include>
2503 </index>
2504 ''' % (dash_version, version, version, version))
2506 if AnnotationsUsed:
2507 OUTPUT.write(''' <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
2508 ''')
2509 else:
2510 OUTPUT.write(''' <!-- enable this when you use gobject introspection annotations
2511 <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
2513 ''')
2515 OUTPUT.write('''</book>
2516 ''')
2518 OUTPUT.close()
2521 def CreateValidSGML(text):
2522 """Turn any chars which are used in XML into entities.
2524 e.g. '<' into '&lt;'
2526 Args:
2527 text (str): the text to turn into proper XML.
2529 Returns:
2530 str: escaped input
2533 text = text.replace('&', '&amp;') # Do this first, or the others get messed up.
2534 text = text.replace('<', '&lt;')
2535 text = text.replace('>', '&gt;')
2536 # browsers render single tabs inconsistently
2537 text = re.sub(r'([^\s])\t([^\s])', r'\1&#160;\2', text)
2538 return text
2541 def ConvertSGMLChars(symbol, text):
2542 """Escape XML chars.
2544 This is used for text in source code comment blocks, to turn
2545 chars which are used in XML into entities, e.g. '<' into
2546 &lt;'. Depending on INLINE_MARKUP_MODE, this is done
2547 unconditionally or only if the character doesn't seem to be
2548 part of an XML construct (tag or entity reference).
2549 Args:
2550 text (str): the text to turn into proper XML.
2552 Returns:
2553 str: escaped input
2556 if INLINE_MARKUP_MODE:
2557 # For the XML/SGML mode only convert to entities outside CDATA sections.
2558 return ModifyXMLElements(text, symbol,
2559 "<!\\[CDATA\\[|<programlisting[^>]*>",
2560 ConvertSGMLCharsEndTag,
2561 ConvertSGMLCharsCallback)
2562 # For the simple non-sgml mode, convert to entities everywhere.
2564 text = re.sub(r'&(?![a-zA-Z#]+;)', r'&amp;', text) # Do this first, or the others get messed up.
2565 text = re.sub(r'<', r'&lt;', text)
2566 # Allow '>' at beginning of string for blockquote markdown
2567 text = re.sub(r'''(?<=[^\w\n"'\/-])>''', r'&gt;', text)
2569 return text
2572 def ConvertSGMLCharsEndTag(start_tag):
2573 if start_tag == '<![CDATA[':
2574 return "]]>"
2575 return "</programlisting>"
2578 def ConvertSGMLCharsCallback(text, symbol, tag):
2579 if re.search(r'^<programlisting', tag):
2580 logging.debug('call modifyXML')
2581 # We can handle <programlisting> specially here.
2582 return ModifyXMLElements(text, symbol,
2583 "<!\\[CDATA\\[",
2584 ConvertSGMLCharsEndTag,
2585 ConvertSGMLCharsCallback2)
2586 elif tag == '':
2587 logging.debug('replace entities')
2588 # If we're not in CDATA convert to entities.
2589 text = re.sub(r'&(?![a-zA-Z#]+;)', r'&amp;', text) # Do this first, or the others get messed up.
2590 text = re.sub(r'<(?![a-zA-Z\/!])', r'&lt;', text)
2591 # Allow '>' at beginning of string for blockquote markdown
2592 text = re.sub(r'''(?<=[^\w\n"'\/-])>''', r'&gt;', text)
2594 # Handle "#include <xxxxx>"
2595 text = re.sub(r'#include(\s+)<([^>]+)>', r'#include\1&lt;\2&gt;', text)
2597 return text
2600 def ConvertSGMLCharsCallback2(text, symbol, tag):
2601 # If we're not in CDATA convert to entities.
2602 # We could handle <programlisting> differently, though I'm not sure it helps.
2603 if tag == '':
2604 # replace only if its not a tag
2605 text = re.sub(r'&(?![a-zA-Z#]+;)', r'&amp;', text) # Do this first, or the others get messed up.
2606 text = re.sub(r'<(?![a-zA-Z\/!])', r'&lt;', text)
2607 text = re.sub(r'''(?<![a-zA-Z0-9"'\/-])>''', r'&gt;', text)
2608 # Handle "#include <xxxxx>"
2609 text = re.sub(r'/#include(\s+)<([^>]+)>', r'#include\1&lt;\2&gt;', text)
2611 return text
2614 def ExpandAnnotation(symbol, param_desc):
2615 """This turns annotations into acronym tags.
2616 Args:
2617 symbol (str): the symbol being documented, for error messages.
2618 param_desc (str): the text to expand.
2620 Returns:
2621 str: the remaining param_desc
2622 str: the formatted annotations
2624 param_annotations = ''
2626 # look for annotations at the start of the comment part
2627 # function level annotations don't end with a colon ':'
2628 m = re.search(r'^\s*\((.*?)\)(:|$)', param_desc)
2629 if m:
2630 param_desc = param_desc[m.end():]
2632 annotations = re.split(r'\)\s*\(', m.group(1))
2633 logging.info("annotations for %s: '%s'\n", symbol, m.group(1))
2634 for annotation in annotations:
2635 # need to search for the longest key-match in %AnnotationDefinition
2636 match_length = 0
2637 match_annotation = ''
2639 for annotationdef in AnnotationDefinition:
2640 if annotation.startswith(annotationdef):
2641 if len(annotationdef) > match_length:
2642 match_length = len(annotationdef)
2643 match_annotation = annotationdef
2645 annotation_extra = ''
2646 if match_annotation != '':
2647 m = re.search(match_annotation + r'\s+(.*)', annotation)
2648 if m:
2649 annotation_extra = " " + m.group(1)
2651 AnnotationsUsed[match_annotation] = 1
2652 param_annotations += "[<acronym>%s</acronym>%s]" % (match_annotation, annotation_extra)
2653 else:
2654 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
2655 "unknown annotation \"%s\" in documentation for %s." % (annotation, symbol))
2656 param_annotations += "[%s]" % annotation
2658 param_desc = param_desc.strip()
2659 m = re.search(r'^(.*?)\.*\s*$', param_desc, flags=re.S)
2660 param_desc = m.group(1) + '. '
2662 if param_annotations != '':
2663 param_annotations = "<emphasis role=\"annotation\">%s</emphasis>" % param_annotations
2665 return (param_desc, param_annotations)
2668 def ExpandAbbreviations(symbol, text):
2669 """Expand the shortcut notation for symbol references.
2671 This turns the abbreviations function(), macro(), @param, %constant, and #symbol
2672 into appropriate DocBook markup. CDATA sections and <programlisting> parts
2673 are skipped.
2675 Args:
2676 symbol (str): the symbol being documented, for error messages.
2677 text (str): the text to expand.
2679 Returns:
2680 str: the expanded text
2682 # Note: This is a fallback and normally done in the markdown parser
2684 logging.debug('expand abbreviations for "%s", text: [%s]', symbol, text)
2685 m = re.search(r'\|\[[^\n]*\n(.*)\]\|', text, flags=re.M | re.S)
2686 if m:
2687 logging.debug('replaced entities in code block')
2688 text = text[:m.start(1)] + md_to_db.ReplaceEntities(m.group(1)) + text[m.end(1):]
2690 # Convert "|[" and "]|" into the start and end of program listing examples.
2691 # Support \[<!-- language="C" --> modifiers
2692 text = re.sub(r'\|\[<!-- language="([^"]+)" -->',
2693 r'<informalexample><programlisting role="example" language="\1"><![CDATA[', text)
2694 text = re.sub(r'\|\[', r'<informalexample><programlisting role="example"><![CDATA[', text)
2695 text = re.sub(r'\]\|', r']]></programlisting></informalexample>', text)
2697 # keep CDATA unmodified, preserve ulink tags (ideally we preseve all tags
2698 # as such)
2699 return ModifyXMLElements(text, symbol,
2700 "<!\\[CDATA\\[|<ulink[^>]*>|<programlisting[^>]*>|<!DOCTYPE",
2701 ExpandAbbreviationsEndTag,
2702 ExpandAbbreviationsCallback)
2705 def ExpandAbbreviationsEndTag(start_tag):
2706 # Returns the end tag (as a regexp) corresponding to the given start tag.
2707 if start_tag == r'<!\[CDATA\[':
2708 return "]]>"
2709 if start_tag == "<!DOCTYPE":
2710 return '>'
2711 m = re.search(r'<(\w+)', start_tag)
2712 if m:
2713 return "</%s>" % m.group(1)
2715 logging.warning('no end tag for "%s"', start_tag)
2716 return ''
2719 def ExpandAbbreviationsCallback(text, symbol, tag):
2720 # Called inside or outside each CDATA or <programlisting> section.
2721 if tag.startswith(r'^<programlisting'):
2722 # Handle any embedded CDATA sections.
2723 return ModifyXMLElements(text, symbol,
2724 "<!\\[CDATA\\[",
2725 ExpandAbbreviationsEndTag,
2726 ExpandAbbreviationsCallback2)
2727 elif tag == '':
2728 # NOTE: this is a fallback. It is normally done by the Markdown parser.
2729 # but is also used for OutputExtraFile
2731 # We are outside any CDATA or <programlisting> sections, so we expand
2732 # any gtk-doc abbreviations.
2734 # Convert '@param()'
2735 # FIXME: we could make those also links ($symbol.$2), but that would be less
2736 # useful as the link target is a few lines up or down
2737 text = re.sub(r'(\A|[^\\])\@(\w+((\.|->)\w+)*)\s*\(\)', r'\1<parameter>\2()</parameter>', text)
2739 # Convert 'function()' or 'macro()'.
2740 # if there is abc_*_def() we don't want to make a link to _def()
2741 # FIXME: also handle abc(def(....)) : but that would need to be done recursively :/
2742 def f1(m):
2743 return m.group(1) + MakeXRef(m.group(2), tagify(m.group(2) + "()", "function"))
2744 text = re.sub(r'([^\*.\w])(\w+)\s*\(\)', f1, text)
2745 # handle #Object.func()
2746 text = re.sub(r'(\A|[^\\])#([\w\-:\.]+[\w]+)\s*\(\)', f1, text)
2748 # Convert '@param', but not '\@param'.
2749 text = re.sub(r'(\A|[^\\])\@(\w+((\.|->)\w+)*)', r'\1<parameter>\2</parameter>', text)
2750 text = re.sub(r'/\\\@', r'\@', text)
2752 # Convert '%constant', but not '\%constant'.
2753 # Also allow negative numbers, e.g. %-1.
2754 def f2(m):
2755 return m.group(1) + MakeXRef(m.group(2), tagify(m.group(2), "literal"))
2756 text = re.sub(r'(\A|[^\\])\%(-?\w+)', f2, text)
2757 text = re.sub(r'\\\%', r'\%', text)
2759 # Convert '#symbol', but not '\#symbol'.
2760 def f3(m):
2761 return m.group(1) + MakeHashXRef(m.group(2), "type")
2762 text = re.sub(r'(\A|[^\\])#([\w\-:\.]+[\w]+)', f3, text)
2763 text = re.sub(r'\\#', '#', text)
2765 return text
2768 def ExpandAbbreviationsCallback2(text, symbol, tag):
2769 # This is called inside a <programlisting>
2770 if tag == '':
2771 # We are inside a <programlisting> but outside any CDATA sections,
2772 # so we expand any gtk-doc abbreviations.
2773 # FIXME: why is this different from &ExpandAbbreviationsCallback(),
2774 # why not just call it
2775 text = re.sub(r'#(\w+)', lambda m: '%s;' % MakeHashXRef(m.group(1), ''), text)
2776 elif tag == "<![CDATA[":
2777 # NOTE: this is a fallback. It is normally done by the Markdown parser.
2778 text = ReplaceEntities(text, symbol)
2780 return text
2783 def MakeHashXRef(symbol, tag):
2784 text = symbol
2786 # Check for things like '#include', '#define', and skip them.
2787 if symbol in PreProcessorDirectives:
2788 return "#%s" % symbol
2790 # Get rid of special suffixes ('-struct','-enum').
2791 text = re.sub(r'-struct$', '', text)
2792 text = re.sub(r'-enum$', '', text)
2794 # If the symbol is in the form "Object::signal", then change the symbol to
2795 # "Object-signal" and use "signal" as the text.
2796 if '::' in symbol:
2797 o, s = symbol.split('::', 1)
2798 symbol = '%s-%s' % (o, s)
2799 text = u'“' + s + u'”'
2801 # If the symbol is in the form "Object:property", then change the symbol to
2802 # "Object--property" and use "property" as the text.
2803 if ':' in symbol:
2804 o, p = symbol.split(':', 1)
2805 symbol = '%s--%s' % (o, p)
2806 text = u'“' + p + u'”'
2808 if tag != '':
2809 text = tagify(text, tag)
2811 return MakeXRef(symbol, text)
2814 def ModifyXMLElements(text, symbol, start_tag_regexp, end_tag_func, callback):
2815 """Rewrite XML blocks.
2817 Looks for given XML element tags within the text, and calls
2818 the callback on pieces of text inside & outside those elements.
2819 Used for special handling of text inside things like CDATA
2820 and <programlisting>.
2822 Args:
2823 text (str): the text.
2824 symbol (str): the symbol currently being documented (only used for
2825 error messages).
2826 start_tag_regexp (str): the regular expression to match start tags.
2827 e.g. "<!\\[CDATA\\[|<programlisting[^>]*>" to
2828 match CDATA sections or programlisting elements.
2829 end_tag_func (func): function which is passed the matched start tag
2830 and should return the appropriate end tag string
2831 regexp.
2832 callback - callback called with each part of the text. It is
2833 called with a piece of text, the symbol being
2834 documented, and the matched start tag or '' if the text
2835 is outside the XML elements being matched.
2837 Returns:
2838 str: modified text
2840 before_tag = start_tag = end_tag_regexp = end_tag = None
2841 result = ''
2843 logging.debug('modify xml for symbol: %s, regex: %s, text: [%s]', symbol, start_tag_regexp, text)
2845 m = re.search(start_tag_regexp, text, flags=re.S)
2846 while m:
2847 before_tag = text[:m.start()] # Prematch for last successful match string
2848 start_tag = m.group(0) # Last successful match
2849 text = text[m.end():] # Postmatch for last successful match string
2850 # get the matching end-tag for current tag
2851 end_tag_regexp = end_tag_func(start_tag)
2853 logging.debug('symbol: %s matched start: %s, end_tag: %s, text: [%s]', symbol, start_tag, end_tag_regexp, text)
2855 logging.debug('converting before tag: [%s]', before_tag)
2856 result += callback(before_tag, symbol, '')
2857 result += start_tag
2859 m2 = re.search(end_tag_regexp, text, flags=re.S)
2860 if m2:
2861 before_tag = text[:m2.start()]
2862 end_tag = m2.group(0)
2863 text = text[m2.end():]
2865 logging.debug('symbol: %s matched end %s: text: [%s]', symbol, end_tag, text)
2867 result += callback(before_tag, symbol, start_tag)
2868 result += end_tag
2869 else:
2870 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
2871 "Can't find tag end: %s in docs for: %s." % (end_tag_regexp, symbol))
2872 # Just assume it is all inside the tag.
2873 result += callback(text, symbol, start_tag)
2874 text = ''
2875 m = re.search(start_tag_regexp, text, flags=re.S)
2877 # Handle any remaining text outside the tags.
2878 logging.debug('converting after tag: [%s]', text)
2879 result += callback(text, symbol, '')
2880 logging.debug('results for symbol: %s, text: [%s]', symbol, result)
2882 return result
2885 def tagify(text, elem):
2886 # Adds a tag around some text.
2887 # e.g tagify("Text", "literal") => "<literal>Text</literal>".
2888 return '<' + elem + '>' + text + '</' + elem + '>'
2891 def MakeDocHeader(tag):
2892 """Builds a docbook header for the given tag.
2894 Args:
2895 tag (str): doctype tag
2897 Returns:
2898 str: the docbook header
2900 header = re.sub(r'<!DOCTYPE \w+', r'<!DOCTYPE ' + tag, doctype_header)
2901 # fix the path for book since this is one level up
2902 if tag == 'book':
2903 header = re.sub(
2904 r'<!ENTITY % gtkdocentities SYSTEM "../([a-zA-Z./]+)">', r'<!ENTITY % gtkdocentities SYSTEM "\1">', header)
2905 return header
2908 def MakeXRef(symbol, text=None):
2909 """This returns a cross-reference link to the given symbol.
2911 Though it doesn't try to do this for a few standard C types that it knows
2912 won't be in the documentation.
2914 Args:
2915 symbol (str): the symbol to try to create a XRef to.
2916 text (str): text to put inside the XRef, defaults to symbol
2918 Returns:
2919 str: a docbook link
2921 symbol = symbol.strip()
2922 if not text:
2923 text = symbol
2925 # Get rid of special suffixes ('-struct','-enum').
2926 text = re.sub(r'-struct$', '', text)
2927 text = re.sub(r'-enum$', '', text)
2929 if ' ' in symbol:
2930 return text
2932 logging.info("Getting type link for %s -> %s", symbol, text)
2934 symbol_id = common.CreateValidSGMLID(symbol)
2935 return "<link linkend=\"%s\">%s</link>" % (symbol_id, text)
2938 def MakeIndexterms(symbol, sid):
2939 """This returns a indexterm elements for the given symbol
2941 Args:
2942 symbol (str): the symbol to create indexterms for
2944 Returns:
2945 str: doxbook index terms
2947 terms = ''
2948 sortas = ''
2950 # make the index useful, by ommiting the namespace when sorting
2951 if NAME_SPACE != '':
2952 m = re.search(r'^%s\_?(.*)' % NAME_SPACE, symbol, flags=re.I)
2953 if m:
2954 sortas = ' sortas="%s"' % m.group(1)
2956 if symbol in Deprecated:
2957 terms += "<indexterm zone=\"%s\" role=\"deprecated\"><primary%s>%s</primary></indexterm>" % (
2958 sid, sortas, symbol)
2959 IndexEntriesDeprecated[symbol] = sid
2960 IndexEntriesFull[symbol] = sid
2961 if symbol in Since:
2962 since = Since[symbol].strip()
2963 if since != '':
2964 terms += "<indexterm zone=\"%s\" role=\"%s\"><primary%s>%s</primary></indexterm>" % (
2965 sid, since, sortas, symbol)
2966 IndexEntriesSince[symbol] = sid
2967 IndexEntriesFull[symbol] = sid
2968 if terms == '':
2969 terms += "<indexterm zone=\"%s\"><primary%s>%s</primary></indexterm>" % (sid, sortas, symbol)
2970 IndexEntriesFull[symbol] = sid
2971 return terms
2974 def MakeDeprecationNote(symbol):
2975 """This returns a deprecation warning for the given symbol.
2977 Args:
2978 symbol (str): the symbol to try to create a warning for.
2980 Returns:
2981 str: formatted warning or empty string if symbol is not deprecated
2983 desc = ''
2984 if symbol in Deprecated:
2985 desc += "<warning><para><literal>%s</literal> " % symbol
2986 note = Deprecated[symbol]
2988 m = re.search(r'^\s*([0-9\.]+)\s*:?', note)
2989 if m:
2990 desc += "has been deprecated since version %s and should not be used in newly-written code.</para>" % m.group(
2992 else:
2993 desc += "is deprecated and should not be used in newly-written code.</para>"
2995 note = re.sub(r'^\s*([0-9\.]+)\s*:?\s*', '', note)
2996 note = note.strip()
2998 if note != '':
2999 note = ConvertMarkDown(symbol, note)
3000 desc += " " + note
3002 desc += "</warning>\n"
3004 return desc
3007 def MakeConditionDescription(symbol):
3008 """This returns a sumary of conditions for the given symbol.
3010 Args:
3011 symbol (str): the symbol to create the sumary for.
3013 Returns:
3014 str: formatted text or empty string if no special conditions apply.
3016 desc = ''
3017 if symbol in Deprecated:
3018 if desc != '':
3019 desc += "|"
3020 m = re.search(r'^\s*(.*?)\s*$', Deprecated[symbol])
3021 if m:
3022 desc += "deprecated:%s" % m.group(1)
3023 else:
3024 desc += "deprecated"
3026 if symbol in Since:
3027 if desc != '':
3028 desc += "|"
3029 m = re.search(r'^\s*(.*?)\s*$', Since[symbol])
3030 if m:
3031 desc += "since:%s" % m.group(1)
3032 else:
3033 desc += "since"
3035 if symbol in StabilityLevel:
3036 if desc != '':
3037 desc += "|"
3039 desc += "stability:" + StabilityLevel[symbol]
3041 if desc != '':
3042 cond = re.sub(r'"', r'&quot;', desc)
3043 desc = ' condition=\"%s\"' % cond
3044 logging.info("condition for '%s' = '%s'", symbol, desc)
3046 return desc
3049 def GetHierarchy(gobject, hierarchy):
3050 """Generate the object inheritance graph.
3052 Returns the DocBook output describing the ancestors and
3053 immediate children of a GObject subclass. It uses the
3054 global Objects and ObjectLevels arrays to walk the tree.
3056 Args:
3057 object (str): the GtkObject subclass.
3058 hierarchy (list) - previous hierarchy
3060 Returns:
3061 list: lines of docbook describing the hierarchy
3063 # Find object in the objects array.
3064 found = False
3065 children = []
3066 level = 0
3067 j = 0
3068 for i in range(len(Objects)):
3069 if found:
3070 if ObjectLevels[i] <= level:
3071 break
3073 elif ObjectLevels[i] == level + 1:
3074 children.append(Objects[i])
3076 elif Objects[i] == gobject:
3077 found = True
3078 j = i
3079 level = ObjectLevels[i]
3081 if not found:
3082 return hierarchy
3084 logging.info("=== Hierachy for: %s (%d existing entries) ===", gobject, len(hierarchy))
3086 # Walk up the hierarchy, pushing ancestors onto the ancestors array.
3087 ancestors = [gobject]
3088 logging.info("Level: %s", level)
3089 while level > 1:
3090 j -= 1
3091 if ObjectLevels[j] < level:
3092 ancestors.append(Objects[j])
3093 level = ObjectLevels[j]
3094 logging.info("Level: %s", level)
3096 # Output the ancestors, indented and with links.
3097 logging.info('%d ancestors', len(ancestors))
3098 last_index = 0
3099 level = 1
3100 for i in range(len(ancestors) - 1, -1, -1):
3101 ancestor = ancestors[i]
3102 ancestor_id = common.CreateValidSGMLID(ancestor)
3103 indent = ' ' * (level * 4)
3104 # Don't add a link to the current object, i.e. when i == 0.
3105 if i > 0:
3106 entry_text = indent + "<link linkend=\"%s\">%s</link>" % (ancestor_id, ancestor)
3107 alt_text = indent + ancestor
3108 else:
3109 entry_text = indent + ancestor
3110 alt_text = indent + "<link linkend=\"%s\">%s</link>" % (ancestor_id, ancestor)
3112 logging.info("Checking for '%s' or '%s'", entry_text, alt_text)
3113 # Check if we already have this object
3114 index = -1
3115 for j in range(len(hierarchy)):
3116 if hierarchy[j] == entry_text or (hierarchy[j] == alt_text):
3117 index = j
3118 break
3119 if index == -1:
3120 # We have a new entry, find insert position in alphabetical order
3121 found = False
3122 for j in range(last_index, len(hierarchy)):
3123 if not re.search(r'^' + indent, hierarchy[j]):
3124 last_index = j
3125 found = True
3126 break
3127 elif re.search(r'^%s[^ ]' % indent, hierarchy[j]):
3128 stripped_text = hierarchy[j]
3129 if r'<link linkend' not in entry_text:
3130 stripped_text = re.sub(r'<link linkend="[A-Za-z]*">', '', stripped_text)
3131 stripped_text = re.sub(r'</link>', '', stripped_text)
3133 if entry_text < stripped_text:
3134 last_index = j
3135 found = True
3136 break
3138 # Append to bottom
3139 if not found:
3140 last_index = len(hierarchy)
3142 logging.debug('insert at %d: %s', last_index, entry_text)
3143 hierarchy.insert(last_index, entry_text)
3144 last_index += 1
3145 else:
3146 # Already have this one, make sure we use the not linked version
3147 if r'<link linkend' not in entry_text:
3148 hierarchy[j] = entry_text
3150 # Remember index as base insert point
3151 last_index = index + 1
3153 level += 1
3155 # Output the children, indented and with links.
3156 logging.info('%d children', len(children))
3157 for i in range(len(children)):
3158 sid = common.CreateValidSGMLID(children[i])
3159 indented_text = ' ' * (level * 4) + "<link linkend=\"%s\">%s</link>" % (sid, children[i])
3160 logging.debug('insert at %d: %s', last_index, indented_text)
3161 hierarchy.insert(last_index, indented_text)
3162 last_index += 1
3163 return hierarchy
3166 def GetInterfaces(gobject):
3167 """Generate interface implementation graph.
3169 Returns the DocBook output describing the interfaces
3170 implemented by a class. It uses the global Interfaces hash.
3172 Args:
3173 object (str): the GObject subclass.
3175 Returns:
3176 str: implemented interfaces
3178 text = ''
3179 # Find object in the objects array.
3180 if gobject in Interfaces:
3181 ifaces = Interfaces[gobject].split()
3182 text = '''<para>
3183 %s implements
3184 ''' % gobject
3185 count = len(ifaces)
3186 for i in range(count):
3187 sid = common.CreateValidSGMLID(ifaces[i])
3188 text += " <link linkend=\"%s\">%s</link>" % (sid, ifaces[i])
3189 if i < count - 2:
3190 text += ', '
3191 elif i < count - 1:
3192 text += ' and '
3193 else:
3194 text += '.'
3195 text += '</para>\n'
3196 return text
3199 def GetImplementations(gobject):
3200 """Generate interface usage graph.
3202 Returns the DocBook output describing the implementations
3203 of an interface. It uses the global Interfaces hash.
3205 Args:
3206 object (str): the GObject subclass.
3208 Returns:
3209 str: interface implementations
3211 text = ''
3212 impls = []
3213 for key in Interfaces:
3214 if re.search(r'\b%s\b' % gobject, Interfaces[key]):
3215 impls.append(key)
3217 count = len(impls)
3218 if count > 0:
3219 impls.sort()
3220 text = '''<para>
3221 %s is implemented by
3222 ''' % gobject
3223 for i in range(count):
3224 sid = common.CreateValidSGMLID(impls[i])
3225 text += " <link linkend=\"%s\">%s</link>" % (sid, impls[i])
3226 if i < count - 2:
3227 text += ', '
3228 elif i < count - 1:
3229 text += ' and '
3230 else:
3231 text += '.'
3232 text += '</para>\n'
3233 return text
3236 def GetPrerequisites(iface):
3237 """Generates interface requirements.
3239 Returns the DocBook output describing the prerequisites
3240 of an interface. It uses the global Prerequisites hash.
3241 Args:
3242 iface (str): the interface.
3244 Returns:
3245 str: required interfaces
3248 text = ''
3249 if iface in Prerequisites:
3250 text = '''<para>
3251 %s requires
3252 ''' % iface
3253 prereqs = Prerequisites[iface].split()
3254 count = len(prereqs)
3255 for i in range(count):
3256 sid = common.CreateValidSGMLID(prereqs[i])
3257 text += " <link linkend=\"%s\">%s</link>" % (sid, prereqs[i])
3258 if i < count - 2:
3259 text += ', '
3260 elif i < count - 1:
3261 text += ' and '
3262 else:
3263 text += '.'
3264 text += '</para>\n'
3265 return text
3268 def GetDerived(iface):
3270 Returns the DocBook output describing the derived interfaces
3271 of an interface. It uses the global %Prerequisites hash.
3273 Args:
3274 iface (str): the interface.
3276 Returns:
3277 str: derived interfaces
3279 text = ''
3280 derived = []
3281 for key in Prerequisites:
3282 if re.search(r'\b%s\b' % iface, Prerequisites[key]):
3283 derived.append(key)
3285 count = len(derived)
3286 if count > 0:
3287 derived.sort()
3288 text = '''<para>
3289 %s is required by
3290 ''' % iface
3291 for i in range(count):
3292 sid = common.CreateValidSGMLID(derived[i])
3293 text += " <link linkend=\"%s\">%s</link>" % (sid, derived[i])
3294 if i < count - 2:
3295 text += ', '
3296 elif i < count - 1:
3297 text += ' and '
3298 else:
3299 text += '.'
3300 text += '</para>\n'
3301 return text
3304 def GetSignals(gobject):
3305 """Generate signal docs.
3307 Returns the synopsis and detailed description DocBook output
3308 for the signal handlers of a given GObject subclass.
3310 Args:
3311 object (str): the GObject subclass, e.g. 'GtkButton'.
3313 Returns:
3314 str: signal docs
3316 synop = ''
3317 desc = ''
3319 for i in range(len(SignalObjects)):
3320 if SignalObjects[i] == gobject:
3321 logging.info("Found signal: %s", SignalNames[i])
3322 name = SignalNames[i]
3323 symbol = '%s::%s' % (gobject, name)
3324 sid = common.CreateValidSGMLID('%s-%s' % (gobject, name))
3326 desc += u"<refsect2 id=\"%s\" role=\"signal\"><title>The <literal>“%s”</literal> signal</title>\n" % (
3327 sid, name)
3328 desc += MakeIndexterms(symbol, sid)
3329 desc += "\n"
3330 desc += OutputSymbolExtraLinks(symbol)
3332 desc += "<programlisting language=\"C\">"
3334 m = re.search(r'\s*(const\s+)?(\w+)\s*(\**)', SignalReturns[i])
3335 type_modifier = m.group(1) or ''
3336 gtype = m.group(2)
3337 pointer = m.group(3)
3338 xref = MakeXRef(gtype, tagify(gtype, "returnvalue"))
3340 ret_type_output = '%s%s%s' % (type_modifier, xref, pointer)
3341 callback_name = "user_function"
3342 desc += '%s\n%s (' % (ret_type_output, callback_name)
3344 indentation = ' ' * (len(callback_name) + 2)
3346 sourceparams = SourceSymbolParams.get(symbol)
3347 sourceparam_names = None
3348 if sourceparams:
3349 sourceparam_names = list(sourceparams) # keys as list
3350 params = SignalPrototypes[i].splitlines()
3351 type_len = len("gpointer")
3352 name_len = len("user_data")
3353 # do two passes, the first one is to calculate padding
3354 for l in range(2):
3355 for j in range(len(params)):
3356 param_name = None
3357 # allow alphanumerics, '_', '[' & ']' in param names
3358 m = re.search(r'^\s*(\w+)\s*(\**)\s*([\w\[\]]+)\s*$', params[j])
3359 if m:
3360 gtype = m.group(1)
3361 pointer = m.group(2)
3362 if sourceparam_names:
3363 if j < len(sourceparam_names):
3364 param_name = sourceparam_names[j]
3365 logging.info('from sourceparams: "%s" (%d: %s)', param_name, j, params[j])
3366 # we're mssing the docs for this param, don't warn here though
3367 else:
3368 param_name = m.group(3)
3369 logging.info('from params: "%s" (%d: %s)', param_name, j, params[j])
3371 if not param_name:
3372 param_name = "arg%d" % j
3374 if l == 0:
3375 if len(gtype) + len(pointer) > type_len:
3376 type_len = len(gtype) + len(pointer)
3377 if len(param_name) > name_len:
3378 name_len = len(param_name)
3379 else:
3380 logging.info("signal arg[%d]: '%s'", j, param_name)
3381 xref = MakeXRef(gtype, tagify(gtype, "type"))
3382 pad = ' ' * (type_len - len(gtype) - len(pointer))
3383 desc += '%s%s %s%s,\n' % (xref, pad, pointer, param_name)
3384 desc += indentation
3386 else:
3387 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
3388 "Can't parse arg: %s\nArgs:%s" % (params[j], SignalPrototypes[i]))
3390 xref = MakeXRef("gpointer", tagify("gpointer", "type"))
3391 pad = ' ' * (type_len - len("gpointer"))
3392 desc += '%s%s user_data)' % (xref, pad)
3393 desc += "</programlisting>\n"
3395 flags = SignalFlags[i]
3396 flags_string = ''
3397 if flags:
3398 if 'f' in flags:
3399 flags_string = "<link linkend=\"G-SIGNAL-RUN-FIRST:CAPS\">Run First</link>"
3401 elif 'l' in flags:
3402 flags_string = "<link linkend=\"G-SIGNAL-RUN-LAST:CAPS\">Run Last</link>"
3404 elif 'c' in flags:
3405 flags_string = "<link linkend=\"G-SIGNAL-RUN-CLEANUP:CAPS\">Cleanup</link>"
3406 flags_string = "Cleanup"
3408 if 'r' in flags:
3409 if flags_string:
3410 flags_string += " / "
3411 flags_string = "<link linkend=\"G-SIGNAL-NO-RECURSE:CAPS\">No Recursion</link>"
3413 if 'd' in flags:
3414 if flags_string:
3415 flags_string += " / "
3416 flags_string = "<link linkend=\"G-SIGNAL-DETAILED:CAPS\">Has Details</link>"
3418 if 'a' in flags:
3419 if flags_string:
3420 flags_string += " / "
3421 flags_string = "<link linkend=\"G-SIGNAL-ACTION:CAPS\">Action</link>"
3423 if 'h' in flags:
3424 if flags_string:
3425 flags_string += " / "
3426 flags_string = "<link linkend=\"G-SIGNAL-NO-HOOKS:CAPS\">No Hooks</link>"
3428 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" % (
3429 ret_type_output, sid, name, flags_string)
3431 parameters = OutputParamDescriptions("SIGNAL", symbol, None)
3432 logging.info("formatted signal params: '%s' -> '%s'", symbol, parameters)
3434 AllSymbols[symbol] = 1
3435 if symbol in SymbolDocs:
3436 symbol_docs = ConvertMarkDown(symbol, SymbolDocs[symbol])
3438 desc += symbol_docs
3440 if not IsEmptyDoc(SymbolDocs[symbol]):
3441 AllDocumentedSymbols[symbol] = 1
3443 if symbol in SymbolAnnotations:
3444 param_desc = SymbolAnnotations[symbol]
3445 (param_desc, param_annotations) = ExpandAnnotation(symbol, param_desc)
3446 if param_annotations != '':
3447 desc += "\n<para>%s</para>" % param_annotations
3449 desc += MakeDeprecationNote(symbol)
3451 desc += parameters
3452 if flags_string:
3453 desc += "<para>Flags: %s</para>\n" % flags_string
3455 desc += OutputSymbolTraits(symbol)
3456 desc += "</refsect2>"
3458 return (synop, desc)
3461 def GetArgs(gobject):
3462 """Generate property docs.
3464 Returns the synopsis and detailed description DocBook output
3465 for the Args of a given GtkObject subclass.
3467 Args:
3468 object (str): the GObject subclass, e.g. 'GtkButton'.
3470 Returns:
3471 str: property docs
3473 synop = ''
3474 desc = ''
3475 child_synop = ''
3476 child_desc = ''
3477 style_synop = ''
3478 style_desc = ''
3480 for i in range(len(ArgObjects)):
3481 if ArgObjects[i] == gobject:
3482 logging.info("Found arg: %s", ArgNames[i])
3483 name = ArgNames[i]
3484 flags = ArgFlags[i]
3485 flags_string = ''
3486 kind = ''
3487 id_sep = ''
3489 if 'c' in flags:
3490 kind = "child property"
3491 id_sep = "c-"
3492 elif 's' in flags:
3493 kind = "style property"
3494 id_sep = "s-"
3495 else:
3496 kind = "property"
3498 # Remember only one colon so we don't clash with signals.
3499 symbol = '%s:%s' % (gobject, name)
3500 # use two dashes and ev. an extra separator here for the same reason.
3501 sid = common.CreateValidSGMLID('%s--%s%s' % (gobject, id_sep, name))
3503 atype = ArgTypes[i]
3504 type_output = None
3505 arange = ArgRanges[i]
3506 range_output = CreateValidSGML(arange)
3507 default = ArgDefaults[i]
3508 default_output = CreateValidSGML(default)
3510 if atype == "GtkString":
3511 atype = "char&#160;*"
3513 if atype == "GtkSignal":
3514 atype = "GtkSignalFunc, gpointer"
3515 type_output = MakeXRef("GtkSignalFunc") + ", " + MakeXRef("gpointer")
3516 elif re.search(r'^(\w+)\*$', atype):
3517 m = re.search(r'^(\w+)\*$', atype)
3518 type_output = MakeXRef(m.group(1), tagify(m.group(1), "type")) + "&#160;*"
3519 else:
3520 type_output = MakeXRef(atype, tagify(atype, "type"))
3522 if 'r' in flags:
3523 flags_string = "Read"
3525 if 'w' in flags:
3526 if flags_string:
3527 flags_string += " / "
3528 flags_string += "Write"
3530 if 'x' in flags:
3531 if flags_string:
3532 flags_string += " / "
3533 flags_string += "Construct"
3535 if 'X' in flags:
3536 if flags_string:
3537 flags_string += " / "
3538 flags_string += "Construct Only"
3540 AllSymbols[symbol] = 1
3541 blurb = ''
3542 if symbol in SymbolDocs and not IsEmptyDoc(SymbolDocs[symbol]):
3543 blurb = ConvertMarkDown(symbol, SymbolDocs[symbol])
3544 logging.info(".. [%s][%s]", SymbolDocs[symbol], blurb)
3545 AllDocumentedSymbols[symbol] = 1
3547 else:
3548 if ArgBlurbs[i] != '':
3549 blurb = "<para>" + CreateValidSGML(ArgBlurbs[i]) + "</para>"
3550 AllDocumentedSymbols[symbol] = 1
3551 else:
3552 # FIXME: print a warning?
3553 logging.info(".. no description")
3555 pad1 = ''
3556 if len(name) < 24:
3557 pad1 = " " * (24 - len(name))
3559 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" % (
3560 type_output, sid, name, flags_string)
3561 arg_desc = u"<refsect2 id=\"%s\" role=\"property\"><title>The <literal>“%s”</literal> %s</title>\n" % (
3562 sid, name, kind)
3563 arg_desc += MakeIndexterms(symbol, sid)
3564 arg_desc += "\n"
3565 arg_desc += OutputSymbolExtraLinks(symbol)
3567 arg_desc += u"<programlisting> “%s%s %s</programlisting>\n" % (name, pad1, type_output)
3568 arg_desc += blurb
3569 if symbol in SymbolAnnotations:
3570 param_desc = SymbolAnnotations[symbol]
3571 (param_desc, param_annotations) = ExpandAnnotation(symbol, param_desc)
3572 if param_annotations != '':
3573 arg_desc += "\n<para>%s</para>" % param_annotations
3575 arg_desc += MakeDeprecationNote(symbol)
3577 if flags_string:
3578 arg_desc += "<para>Flags: %s</para>\n" % flags_string
3580 if arange != '':
3581 arg_desc += "<para>Allowed values: %s</para>\n" % range_output
3583 if default != '':
3584 arg_desc += "<para>Default value: %s</para>\n" % default_output
3586 arg_desc += OutputSymbolTraits(symbol)
3587 arg_desc += "</refsect2>\n"
3589 if 'c' in flags:
3590 child_synop += arg_synop
3591 child_desc += arg_desc
3593 elif 's' in flags:
3594 style_synop += arg_synop
3595 style_desc += arg_desc
3597 else:
3598 synop += arg_synop
3599 desc += arg_desc
3601 return (synop, child_synop, style_synop, desc, child_desc, style_desc)
3604 def IgnorePath(path, source_dirs, ignore_files):
3605 for sdir in source_dirs:
3606 # Cut off base directory
3607 m1 = re.search(r'^%s/(.*)$' % re.escape(sdir), path)
3608 if m1:
3609 # Check if the filename is in the ignore list.
3610 m2 = re.search(r'(\s|^)%s(\s|$)' % re.escape(m1.group(1)), ignore_files)
3611 if m2:
3612 logging.info("Skipping path: %s", path)
3613 return True
3614 else:
3615 logging.info("No match for: %s", m1.group(1))
3616 else:
3617 logging.info("No match for: %s", path)
3618 return False
3621 def ReadSourceDocumentation(source_dir, suffix_list, source_dirs, ignore_files):
3622 """Read the documentation embedded in comment blocks in the source code.
3624 It recursively descends the source directory looking for source files and
3625 scans them looking for specially-formatted comment blocks.
3627 Args:
3628 source_dir (str): the directory to scan.
3629 suffix_list (list): extensions to check
3631 if IgnorePath(source_dir, source_dirs, ignore_files):
3632 return
3634 logging.info("Scanning source directory: %s", source_dir)
3636 # This array holds any subdirectories found.
3637 subdirs = []
3639 for ifile in sorted(os.listdir(source_dir)):
3640 logging.debug("... : %s", ifile)
3641 if ifile.startswith('.'):
3642 continue
3643 fname = os.path.join(source_dir, ifile)
3644 if os.path.isdir(fname):
3645 subdirs.append(fname)
3646 else:
3647 for suffix in suffix_list:
3648 if ifile.endswith(suffix):
3649 if not IgnorePath(fname, source_dirs, ignore_files):
3650 ScanSourceFile(fname, ignore_files)
3651 break
3653 # Now recursively scan the subdirectories.
3654 for sdir in subdirs:
3655 ReadSourceDocumentation(sdir, suffix_list, source_dirs, ignore_files)
3658 def ScanSourceFile(ifile, ignore_files):
3659 """Scans one source file looking for specially-formatted comment blocks.
3661 Later MergeSourceDocumentation() is copying over the doc blobs that are not
3662 suppressed/ignored.
3664 Args:
3665 file (str): the file to scan.
3667 m = re.search(r'^.*[\/\\]([^\/\\]*)$', ifile)
3668 if m:
3669 basename = m.group(1)
3670 else:
3671 common.LogWarning(ifile, 1, "Can't find basename for this filename.")
3672 basename = ifile
3674 # Check if the basename is in the list of files to ignore.
3675 if re.search(r'(\s|^)%s(\s|$)' % re.escape(basename), ignore_files):
3676 logging.info("Skipping source file: %s", ifile)
3677 return
3679 logging.info("Scanning source file: %s", ifile)
3681 SRCFILE = common.open_text(ifile)
3682 in_comment_block = False
3683 symbol = None
3684 in_part = ''
3685 description = ''
3686 return_desc = ''
3687 since_desc = stability_desc = deprecated_desc = ''
3688 params = OrderedDict()
3689 param_name = None
3690 line_number = 0
3691 for line in SRCFILE:
3692 line_number += 1
3693 # Look for the start of a comment block.
3694 if not in_comment_block:
3695 if re.search(r'^\s*/\*.*\*/', line):
3696 # one-line comment - not gtkdoc
3697 pass
3698 elif re.search(r'^\s*/\*\*\s', line):
3699 logging.info("Found comment block start")
3701 in_comment_block = True
3703 # Reset all the symbol data.
3704 symbol = ''
3705 in_part = ''
3706 description = ''
3707 return_desc = ''
3708 since_desc = ''
3709 deprecated_desc = ''
3710 stability_desc = ''
3711 params = OrderedDict()
3712 param_name = None
3714 continue
3716 # We're in a comment block. Check if we've found the end of it.
3717 if re.search(r'^\s*\*+/', line):
3718 if not symbol:
3719 # maybe its not even meant to be a gtk-doc comment?
3720 common.LogWarning(ifile, line_number, "Symbol name not found at the start of the comment block.")
3721 else:
3722 # Add the return value description onto the end of the params.
3723 if return_desc:
3724 # TODO(ensonic): check for duplicated Return docs
3725 # common.LogWarning(file, line_number, "Multiple Returns for %s." % symbol)
3726 params['Returns'] = return_desc
3728 # Convert special characters
3729 description = ConvertSGMLChars(symbol, description)
3730 for (param_name, param_desc) in iteritems(params):
3731 params[param_name] = ConvertSGMLChars(symbol, param_desc)
3733 # Handle Section docs
3734 m = re.search(r'SECTION:\s*(.*)', symbol)
3735 m2 = re.search(r'PROGRAM:\s*(.*)', symbol)
3736 if m:
3737 real_symbol = m.group(1)
3738 long_descr = real_symbol + ":Long_Description"
3740 if long_descr not in KnownSymbols or KnownSymbols[long_descr] != 1:
3741 common.LogWarning(
3742 ifile, line_number, "Section %s is not defined in the %s-sections.txt file." % (real_symbol, MODULE))
3744 logging.info("SECTION DOCS found in source for : '%s'", real_symbol)
3745 for param_name, param_desc in iteritems(params):
3746 logging.info(" '" + param_name + "'")
3747 param_name = param_name.lower()
3748 key = None
3749 if param_name == "short_description":
3750 key = real_symbol + ":Short_Description"
3751 elif param_name == "see_also":
3752 key = real_symbol + ":See_Also"
3753 elif param_name == "title":
3754 key = real_symbol + ":Title"
3755 elif param_name == "stability":
3756 key = real_symbol + ":Stability_Level"
3757 elif param_name == "section_id":
3758 key = real_symbol + ":Section_Id"
3759 elif param_name == "include":
3760 key = real_symbol + ":Include"
3761 elif param_name == "image":
3762 key = real_symbol + ":Image"
3764 if key:
3765 SourceSymbolDocs[key] = param_desc
3766 SourceSymbolSourceFile[key] = ifile
3767 SourceSymbolSourceLine[key] = line_number
3769 SourceSymbolDocs[long_descr] = description
3770 SourceSymbolSourceFile[long_descr] = ifile
3771 SourceSymbolSourceLine[long_descr] = line_number
3772 elif m2:
3773 real_symbol = m2.group(1)
3774 key = None
3775 section_id = None
3777 logging.info("PROGRAM DOCS found in source for '%s'", real_symbol)
3778 for param_name, param_desc in iteritems(params):
3779 logging.info("PROGRAM key %s: '%s'", real_symbol, param_name)
3780 param_name = param_name.lower()
3781 key = None
3782 if param_name == "short_description":
3783 key = real_symbol + ":Short_Description"
3784 elif param_name == "see_also":
3785 key = real_symbol + ":See_Also"
3786 elif param_name == "section_id":
3787 key = real_symbol + ":Section_Id"
3788 elif param_name == "synopsis":
3789 key = real_symbol + ":Synopsis"
3790 elif param_name == "returns":
3791 key = real_symbol + ":Returns"
3792 elif re.search(r'^(-.*)', param_name):
3793 logging.info("PROGRAM opts: '%s': '%s'", param_name, param_desc)
3794 key = real_symbol + ":Options"
3795 opts = []
3796 opts_str = SourceSymbolDocs.get(key)
3797 if opts_str:
3798 opts = opts_str.split('\t')
3799 opts.append(param_name)
3800 opts.append(param_desc)
3802 logging.info("Setting options for symbol: %s: '%s'", real_symbol, '\t'.join(opts))
3803 SourceSymbolDocs[key] = '\t'.join(opts)
3804 continue
3806 if key:
3807 logging.info("PROGRAM value %s: '%s'", real_symbol, param_desc.rstrip())
3808 SourceSymbolDocs[key] = param_desc.rstrip()
3809 SourceSymbolSourceFile[key] = ifile
3810 SourceSymbolSourceLine[key] = line_number
3812 long_descr = real_symbol + ":Long_Description"
3813 SourceSymbolDocs[long_descr] = description
3814 SourceSymbolSourceFile[long_descr] = ifile
3815 SourceSymbolSourceLine[long_descr] = line_number
3817 section_id = SourceSymbolDocs.get(real_symbol + ":Section_Id")
3818 if section_id and section_id.strip() != '':
3819 # Remove trailing blanks and use as is
3820 section_id = section_id.rstrip()
3821 else:
3822 section_id = common.CreateValidSGMLID('%s-%s' % (MODULE, real_symbol))
3823 OutputProgramDBFile(real_symbol, section_id)
3825 else:
3826 logging.info("SYMBOL DOCS found in source for : '%s'", symbol)
3827 SourceSymbolDocs[symbol] = description
3828 SourceSymbolParams[symbol] = params
3829 SourceSymbolSourceFile[symbol] = ifile
3830 SourceSymbolSourceLine[symbol] = line_number
3832 if since_desc:
3833 arr = since_desc.splitlines()
3834 since_desc = arr[0].strip()
3835 extra_lines = arr[1:]
3836 logging.info("Since(%s) : [%s]", symbol, since_desc)
3837 Since[symbol] = ConvertSGMLChars(symbol, since_desc)
3838 if len(extra_lines) > 1:
3839 common.LogWarning(ifile, line_number, "multi-line since docs found")
3841 if stability_desc:
3842 stability_desc = ParseStabilityLevel(
3843 stability_desc, ifile, line_number, "Stability level for %s" % symbol)
3844 StabilityLevel[symbol] = ConvertSGMLChars(symbol, stability_desc)
3846 if deprecated_desc:
3847 if symbol not in Deprecated:
3848 # don't warn for signals and properties
3849 # if ($symbol !~ m/::?(.*)/)
3850 if symbol in DeclarationTypes:
3851 common.LogWarning(ifile, line_number,
3852 "%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)
3854 Deprecated[symbol] = ConvertSGMLChars(symbol, deprecated_desc)
3856 in_comment_block = False
3857 continue
3859 # Get rid of ' * ' at start of every line in the comment block.
3860 line = re.sub(r'^\s*\*\s?', '', line)
3861 # But make sure we don't get rid of the newline at the end.
3862 if not line.endswith('\n'):
3863 line = line + "\n"
3865 logging.info("scanning :%s", line.strip())
3867 # If we haven't found the symbol name yet, look for it.
3868 if not symbol:
3869 m1 = re.search(r'^\s*(SECTION:\s*\S+)', line)
3870 m2 = re.search(r'^\s*(PROGRAM:\s*\S+)', line)
3871 m3 = re.search(r'^\s*([\w:-]*\w)\s*:?\s*(\(.+?\)\s*)*$', line)
3872 if m1:
3873 symbol = m1.group(1)
3874 logging.info("SECTION DOCS found in source for : '%s'", symbol)
3875 elif m2:
3876 symbol = m2.group(1)
3877 logging.info("PROGRAM DOCS found in source for : '%s'", symbol)
3878 elif m3:
3879 symbol = m3.group(1)
3880 annotation = m3.group(2)
3881 logging.info("SYMBOL DOCS found in source for : '%s'", symbol)
3882 if annotation:
3883 annotation = annotation.strip()
3884 if annotation != '':
3885 SymbolAnnotations[symbol] = annotation
3886 logging.info("remaining text for %s: '%s'", symbol, annotation)
3888 continue
3890 if in_part == "description":
3891 # Get rid of 'Description:'
3892 line = re.sub(r'^\s*Description:', '', line)
3894 m1 = re.search(r'^\s*(returns|return\s+value):', line, flags=re.I)
3895 m2 = re.search(r'^\s*since:', line, flags=re.I)
3896 m3 = re.search(r'^\s*deprecated:', line, flags=re.I)
3897 m4 = re.search(r'^\s*stability:', line, flags=re.I)
3899 if m1:
3900 # we're in param section and have not seen the blank line
3901 if in_part != '':
3902 return_desc = line[m1.end():]
3903 in_part = "return"
3904 continue
3906 if m2:
3907 # we're in param section and have not seen the blank line
3908 if in_part != "param":
3909 since_desc = line[m2.end():]
3910 in_part = "since"
3911 continue
3913 elif m3:
3914 # we're in param section and have not seen the blank line
3915 if in_part != "param":
3916 deprecated_desc = line[m3.end():]
3917 in_part = "deprecated"
3918 continue
3920 elif m4:
3921 stability_desc = line[m4.end():]
3922 in_part = "stability"
3923 continue
3925 if in_part == "description":
3926 description += line
3927 continue
3928 elif in_part == "return":
3929 return_desc += line
3930 continue
3931 elif in_part == "since":
3932 since_desc += line
3933 continue
3934 elif in_part == "stability":
3935 stability_desc += line
3936 continue
3937 elif in_part == "deprecated":
3938 deprecated_desc += line
3939 continue
3941 # We must be in the parameters. Check for the empty line below them.
3942 if re.search(r'^\s*$', line):
3943 in_part = "description"
3944 continue
3946 # Look for a parameter name.
3947 m = re.search(r'^\s*@(.+?)\s*:\s*', line)
3948 if m:
3949 param_name = m.group(1)
3950 param_desc = line[m.end():]
3952 logging.info("Found parameter: %s", param_name)
3953 # Allow varargs variations
3954 if re.search(r'^\.\.\.$', param_name):
3955 param_name = "..."
3957 logging.info("Found param for symbol %s : '%s'= '%s'", symbol, param_name, line)
3959 params[param_name] = param_desc
3960 in_part = "param"
3961 continue
3962 elif in_part == '':
3963 logging.info("continuation for %s annotation '%s'", symbol, line)
3964 annotation = re.sub(r'^\s+|\s+$', '', line)
3965 if symbol in SymbolAnnotations:
3966 SymbolAnnotations[symbol] += annotation
3967 else:
3968 SymbolAnnotations[symbol] = annotation
3969 continue
3971 # We must be in the middle of a parameter description, so add it on
3972 # to the last element in @params.
3973 if not param_name:
3974 common.LogWarning(file, line_number, "Parsing comment block file : parameter expected, but got '%s'" % line)
3975 else:
3976 params[param_name] += line
3978 SRCFILE.close()
3981 def OutputMissingDocumentation():
3982 """Outputs report of documentation coverage to a file.
3984 Returns:
3985 bool: True if the report was updated
3987 old_undocumented_file = os.path.join(ROOT_DIR, MODULE + "-undocumented.txt")
3988 new_undocumented_file = os.path.join(ROOT_DIR, MODULE + "-undocumented.new")
3990 n_documented = 0
3991 n_incomplete = 0
3992 total = 0
3993 symbol = None
3994 percent = None
3995 buffer = ''
3996 buffer_deprecated = ''
3997 buffer_descriptions = ''
3999 UNDOCUMENTED = common.open_text(new_undocumented_file, 'w')
4001 for symbol in sorted(iterkeys(AllSymbols)):
4002 # FIXME: should we print common.LogWarnings for undocumented stuff?
4003 # DEBUG
4004 # location = "defined at " + GetSymbolSourceFile(symbol) + ":" + GetSymbolSourceLine(symbol) + "\n"
4005 # DEBUG
4006 m = re.search(
4007 r':(Title|Long_Description|Short_Description|See_Also|Stability_Level|Include|Section_Id|Image)', symbol)
4008 m2 = re.search(r':(Long_Description|Short_Description)', symbol)
4009 if not m:
4010 total += 1
4011 if symbol in AllDocumentedSymbols:
4012 n_documented += 1
4013 if symbol in AllIncompleteSymbols:
4014 n_incomplete += 1
4015 buffer += symbol + " (" + AllIncompleteSymbols[symbol] + ")\n"
4017 elif symbol in Deprecated:
4018 if symbol in AllIncompleteSymbols:
4019 n_incomplete += 1
4020 buffer_deprecated += symbol + " (" + AllIncompleteSymbols[symbol] + ")\n"
4021 else:
4022 buffer_deprecated += symbol + "\n"
4024 else:
4025 if symbol in AllIncompleteSymbols:
4026 n_incomplete += 1
4027 buffer += symbol + " (" + AllIncompleteSymbols[symbol] + ")\n"
4028 else:
4029 buffer += symbol + "\n"
4031 elif m2:
4032 total += 1
4033 if (symbol in SymbolDocs and len(SymbolDocs[symbol]) > 0)\
4034 or (symbol in AllDocumentedSymbols and AllDocumentedSymbols[symbol] > 0):
4035 n_documented += 1
4036 else:
4037 buffer_descriptions += symbol + "\n"
4039 if total == 0:
4040 percent = 100
4041 else:
4042 percent = (n_documented / total) * 100.0
4044 UNDOCUMENTED.write("%.0f%% symbol docs coverage.\n" % percent)
4045 UNDOCUMENTED.write("%s symbols documented.\n" % n_documented)
4046 UNDOCUMENTED.write("%s symbols incomplete.\n" % n_incomplete)
4047 UNDOCUMENTED.write("%d not documented.\n" % (total - n_documented))
4049 if buffer_deprecated != '':
4050 buffer += "\n" + buffer_deprecated
4052 if buffer_descriptions != '':
4053 buffer += "\n" + buffer_descriptions
4055 if buffer != '':
4056 UNDOCUMENTED.write("\n\n" + buffer)
4058 UNDOCUMENTED.close()
4060 return common.UpdateFileIfChanged(old_undocumented_file, new_undocumented_file, 0)
4063 def OutputUndeclaredSymbols():
4064 """Reports undeclared symbols.
4066 Outputs symbols that are listed in the section file, but have no declaration
4067 in the sources.
4069 Returns:
4070 bool: True if the report was updated
4072 old_undeclared_file = os.path.join(ROOT_DIR, MODULE + "-undeclared.txt")
4073 new_undeclared_file = os.path.join(ROOT_DIR, MODULE + "-undeclared.new")
4075 UNDECLARED = common.open_text(new_undeclared_file, 'w')
4077 if UndeclaredSymbols:
4078 UNDECLARED.write("\n".join(sorted(iterkeys(UndeclaredSymbols))))
4079 UNDECLARED.write("\n")
4080 print("See %s-undeclared.txt for the list of undeclared symbols." % MODULE)
4082 UNDECLARED.close()
4084 return common.UpdateFileIfChanged(old_undeclared_file, new_undeclared_file, 0)
4087 def OutputUnusedSymbols():
4088 """Reports unused documentation.
4090 Outputs symbols that are documented in comments, but not declared in the
4091 sources.
4093 Returns:
4094 bool: True if the report was updated
4096 num_unused = 0
4097 old_unused_file = os.path.join(ROOT_DIR, MODULE + "-unused.txt")
4098 new_unused_file = os.path.join(ROOT_DIR, MODULE + "-unused.new")
4100 UNUSED = common.open_text(new_unused_file, 'w')
4102 for symbol in sorted(iterkeys(Declarations)):
4103 if symbol not in DeclarationOutput:
4104 UNUSED.write("%s\n" % symbol)
4105 num_unused += 1
4107 for symbol in sorted(iterkeys(AllUnusedSymbols)):
4108 UNUSED.write(symbol + "(" + AllUnusedSymbols[symbol] + ")\n")
4109 num_unused += 1
4111 UNUSED.close()
4112 if num_unused != 0:
4113 common.LogWarning(
4114 old_unused_file, 1, "%d unused declarations. They should be added to %s-sections.txt in the appropriate place." % (num_unused, MODULE))
4116 return common.UpdateFileIfChanged(old_unused_file, new_unused_file, 0)
4119 def OutputAllSymbols():
4120 """Outputs list of all symbols to a file."""
4121 SYMBOLS = common.open_text(os.path.join(ROOT_DIR, MODULE + "-symbols.txt"), 'w')
4123 for symbol in sorted(iterkeys(AllSymbols)):
4124 SYMBOLS.write(symbol + "\n")
4125 SYMBOLS.close()
4128 def OutputSymbolsWithoutSince():
4129 """Outputs list of all symbols without a since tag to a file."""
4130 SYMBOLS = common.open_text(os.path.join(ROOT_DIR, MODULE + "-nosince.txt"), 'w')
4132 for symbol in sorted(iterkeys(SourceSymbolDocs)):
4133 if symbol in Since:
4134 SYMBOLS.write(symbol + "\n")
4135 SYMBOLS.close()
4138 def CheckParamsDocumented(symbol, params):
4139 stype = DeclarationTypes.get(symbol)
4141 item = "Parameter"
4142 if stype:
4143 if stype == 'STRUCT':
4144 item = "Field"
4145 elif stype == 'ENUM':
4146 item = "Value"
4147 elif stype == 'UNION':
4148 item = "Field"
4149 else:
4150 stype = "SIGNAL"
4151 logging.info("Check param docs for %s, params: %s entries, type=%s", symbol, len(params), stype)
4153 if len(params) > 0:
4154 logging.info("params: %s", str(params))
4155 for (param_name, param_desc) in iteritems(params):
4156 # Output a warning if the parameter is empty and remember for stats.
4157 if param_name != "void" and not re.search(r'\S', param_desc):
4158 if symbol in AllIncompleteSymbols:
4159 AllIncompleteSymbols[symbol] += ", " + param_name
4160 else:
4161 AllIncompleteSymbols[symbol] = param_name
4163 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
4164 "%s description for %s::%s is missing in source code comment block." % (item, symbol, param_name))
4166 elif len(params) == 0:
4167 AllIncompleteSymbols[symbol] = "<items>"
4168 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
4169 "%s descriptions for %s are missing in source code comment block." % (item, symbol))
4172 def MergeSourceDocumentation():
4173 """Merges documentation read from a source file.
4175 Parameter descriptions override any in the template files.
4176 Function descriptions are placed before any description from
4177 the template files.
4180 # add whats found in the source
4181 symbols = set(iterkeys(SourceSymbolDocs))
4183 # and add known symbols from -sections.txt
4184 for symbol in iterkeys(KnownSymbols):
4185 if KnownSymbols[symbol] == 1:
4186 symbols.add(symbol)
4188 logging.info("num source entries: %d", len(symbols))
4190 for symbol in symbols:
4191 AllSymbols[symbol] = 1
4193 if symbol in SourceSymbolDocs:
4194 logging.info("merging [%s] from source", symbol)
4196 # remove leading and training whitespaces
4197 src_docs = SourceSymbolDocs[symbol].strip()
4198 if src_docs != '':
4199 AllDocumentedSymbols[symbol] = 1
4201 SymbolDocs[symbol] = src_docs
4203 # merge parameters
4204 if symbol in SourceSymbolParams:
4205 param_docs = SourceSymbolParams[symbol]
4206 SymbolParams[symbol] = param_docs
4207 # if this symbol is documented, check if docs are complete
4208 # remove all xml-tags and whitespaces
4209 check_docs = re.sub(r'\s', '', re.sub(r'<.*?>', '', src_docs))
4210 if check_docs != '' and param_docs:
4211 CheckParamsDocumented(symbol, param_docs)
4212 else:
4213 logging.info("[%s] undocumented", symbol)
4215 logging.info("num doc entries: %d", len(SymbolDocs))
4218 def IsEmptyDoc(doc):
4219 """Check if a doc-string is empty.
4221 It is also regarded as empty if it only consist of whitespace or e.g. FIXME.
4223 Args:
4224 doc (str): the doc-string
4226 Returns:
4227 bool: True if empty
4229 if re.search(r'^\s*$', doc):
4230 return True
4231 if re.search(r'^\s*<para>\s*(FIXME)?\s*<\/para>\s*$', doc):
4232 return True
4233 return False
4236 def ConvertMarkDown(symbol, text):
4237 md_to_db.Init()
4238 return md_to_db.MarkDownParse(text, symbol)
4241 def ReadDeclarationsFile(ifile, override):
4242 """Reads in a file containing the function/macro/enum etc. declarations.
4244 Note that in some cases there are several declarations with
4245 the same name, e.g. for conditional macros. In this case we
4246 set a flag in the DeclarationConditional hash so the
4247 declaration is not shown in the docs.
4249 If a macro and a function have the same name, e.g. for
4250 g_object_ref, the function declaration takes precedence.
4252 Some opaque structs are just declared with 'typedef struct
4253 _name name;' in which case the declaration may be empty.
4254 The structure may have been found later in the header, so
4255 that overrides the empty declaration.
4257 Args:
4258 file (str): the declarations file to read
4259 override (bool): if declarations in this file should override
4260 any current declaration.
4262 if override == 0:
4263 Declarations.clear()
4264 DeclarationTypes.clear()
4265 DeclarationConditional.clear()
4266 DeclarationOutput.clear()
4268 INPUT = common.open_text(ifile)
4269 declaration_type = ''
4270 declaration_name = None
4271 declaration = None
4272 is_deprecated = 0
4273 line_number = 0
4274 for line in INPUT:
4275 line_number += 1
4276 # logging.debug("%s:%d: %s", ifile, line_number, line)
4277 if not declaration_type:
4278 m1 = re.search(r'^<([^>]+)>', line)
4279 if m1:
4280 declaration_type = m1.group(1)
4281 declaration_name = ''
4282 logging.info("Found declaration: %s", declaration_type)
4283 declaration = ''
4284 else:
4285 m2 = re.search(r'^<NAME>(.*)</NAME>', line)
4286 m3 = re.search(r'^<DEPRECATED/>', line)
4287 m4 = re.search(r'^</%s>' % declaration_type, line)
4288 if m2:
4289 declaration_name = m2.group(1)
4290 elif m3:
4291 is_deprecated = True
4292 elif m4:
4293 logging.info("Found end of declaration: %s, %s", declaration_type, declaration_name)
4294 # Check that the declaration has a name
4295 if declaration_name == '':
4296 common.LogWarning(ifile, line_number, declaration_type + " has no name.\n")
4298 # If the declaration is an empty typedef struct _XXX XXX
4299 # set the flag to indicate the struct has a typedef.
4300 if (declaration_type == 'STRUCT' or declaration_type == 'UNION') \
4301 and re.search(r'^\s*$', declaration):
4302 logging.info("Struct has typedef: %s", declaration_name)
4303 StructHasTypedef[declaration_name] = 1
4305 # Check if the symbol is already defined.
4306 if declaration_name in Declarations and override == 0:
4307 # Function declarations take precedence.
4308 if DeclarationTypes[declaration_name] == 'FUNCTION':
4309 # Ignore it.
4310 pass
4311 elif declaration_type == 'FUNCTION':
4312 if is_deprecated:
4313 Deprecated[declaration_name] = ''
4315 Declarations[declaration_name] = declaration
4316 DeclarationTypes[declaration_name] = declaration_type
4317 elif DeclarationTypes[declaration_name] == declaration_type:
4318 # If the existing declaration is empty, or is just a
4319 # forward declaration of a struct, override it.
4320 if declaration_type == 'STRUCT' or declaration_type == 'UNION':
4321 if re.search(r'^\s*((struct|union)\s+\w+\s*;)?\s*$', Declarations[declaration_name]):
4322 if is_deprecated:
4323 Deprecated[declaration_name] = ''
4324 Declarations[declaration_name] = declaration
4325 elif re.search(r'^\s*((struct|union)\s+\w+\s*;)?\s*$', declaration):
4326 # Ignore an empty or forward declaration.
4327 pass
4328 else:
4329 common.LogWarning(
4330 ifile, line_number, "Structure %s has multiple definitions." % declaration_name)
4332 else:
4333 # set flag in %DeclarationConditional hash for
4334 # multiply defined macros/typedefs.
4335 DeclarationConditional[declaration_name] = 1
4337 else:
4338 common.LogWarning(ifile, line_number, declaration_name + " has multiple definitions.")
4340 else:
4341 if is_deprecated:
4342 Deprecated[declaration_name] = ''
4344 Declarations[declaration_name] = declaration
4345 DeclarationTypes[declaration_name] = declaration_type
4346 logging.debug("added declaration: %s, %s, [%s]", declaration_type, declaration_name, declaration)
4348 declaration_type = ''
4349 is_deprecated = False
4350 else:
4351 declaration += line
4352 INPUT.close()
4355 def ReadSignalsFile(ifile):
4356 """Reads information about object signals.
4358 It creates the arrays @SignalNames and @SignalPrototypes containing details
4359 about the signals. The first line of the SignalPrototype is the return type
4360 of the signal handler. The remaining lines are the parameters passed to it.
4361 The last parameter, "gpointer user_data" is always the same so is not included.
4363 Args:
4364 ifile (str): the file containing the signal handler prototype information.
4366 in_signal = 0
4367 signal_object = None
4368 signal_name = None
4369 signal_returns = None
4370 signal_flags = None
4371 signal_prototype = None
4373 # Reset the signal info.
4374 SignalObjects[:] = []
4375 SignalNames[:] = []
4376 SignalReturns[:] = []
4377 SignalFlags[:] = []
4378 SignalPrototypes[:] = []
4380 if not os.path.isfile(ifile):
4381 return
4383 INPUT = common.open_text(ifile)
4384 line_number = 0
4385 for line in INPUT:
4386 line_number += 1
4387 if not in_signal:
4388 if re.search(r'^<SIGNAL>', line):
4389 in_signal = 1
4390 signal_object = ''
4391 signal_name = ''
4392 signal_returns = ''
4393 signal_prototype = ''
4395 else:
4396 m = re.search(r'^<NAME>(.*)<\/NAME>', line)
4397 m2 = re.search(r'^<RETURNS>(.*)<\/RETURNS>', line)
4398 m3 = re.search(r'^<FLAGS>(.*)<\/FLAGS>', line)
4399 if m:
4400 signal_name = m.group(1)
4401 m1_2 = re.search(r'^(.*)::(.*)$', signal_name)
4402 if m1_2:
4403 signal_object = m1_2.group(1)
4404 signal_name = m1_2.group(2).replace('_', '-')
4405 logging.info("Found signal: %s", signal_name)
4406 else:
4407 common.LogWarning(ifile, line_number, "Invalid signal name: %s." % signal_name)
4409 elif m2:
4410 signal_returns = m2.group(1)
4411 elif m3:
4412 signal_flags = m3.group(1)
4413 elif re.search(r'^</SIGNAL>', line):
4414 logging.info("Found end of signal: %s::%s\nReturns: %s\n%s",
4415 signal_object, signal_name, signal_returns, signal_prototype)
4416 SignalObjects.append(signal_object)
4417 SignalNames.append(signal_name)
4418 SignalReturns.append(signal_returns)
4419 SignalFlags.append(signal_flags)
4420 SignalPrototypes.append(signal_prototype)
4421 in_signal = False
4422 else:
4423 signal_prototype += line
4424 INPUT.close()
4427 def ReadObjectHierarchy(ifile):
4428 """Reads the $MODULE-hierarchy.txt file.
4430 This contains all the GObject subclasses described in this module (and their
4431 ancestors).
4432 It places them in the Objects array, and places their level
4433 in the object hierarchy in the ObjectLevels array, at the
4434 same index. GObject, the root object, has a level of 1.
4436 This also generates tree_index.sgml as it goes along.
4438 Args:
4439 ifile (str): the input filename.
4442 Objects[:] = []
4443 ObjectLevels[:] = []
4445 if not os.path.isfile(ifile):
4446 logging.debug('no *-hierarchy.tx')
4447 return
4449 INPUT = common.open_text(ifile)
4451 # Only emit objects if they are supposed to be documented, or if
4452 # they have documented children. To implement this, we maintain a
4453 # stack of pending objects which will be emitted if a documented
4454 # child turns up.
4455 pending_objects = []
4456 pending_levels = []
4457 root = None
4458 tree = []
4459 for line in INPUT:
4460 m1 = re.search(r'\S+', line)
4461 if not m1:
4462 continue
4464 gobject = m1.group(0)
4465 level = len(line[:m1.start()]) // 2 + 1
4467 if level == 1:
4468 root = gobject
4470 while pending_levels and pending_levels[-1] >= level:
4471 pending_objects.pop()
4472 pending_levels.pop()
4474 pending_objects.append(gobject)
4475 pending_levels.append(level)
4477 if gobject in KnownSymbols:
4478 while len(pending_levels) > 0:
4479 gobject = pending_objects.pop(0)
4480 level = pending_levels.pop(0)
4481 xref = MakeXRef(gobject)
4483 tree.append(' ' * (level * 4) + xref)
4484 Objects.append(gobject)
4485 ObjectLevels.append(level)
4486 ObjectRoots[gobject] = root
4487 # else
4488 # common.LogWarning(ifile, line_number, "unknown type %s" % object)
4491 INPUT.close()
4493 # FIXME: use xml
4494 # my $old_tree_index = "$DB_OUTPUT_DIR/tree_index.$xml"
4495 old_tree_index = os.path.join(DB_OUTPUT_DIR, "tree_index.sgml")
4496 new_tree_index = os.path.join(DB_OUTPUT_DIR, "tree_index.new")
4498 logging.debug('got %d entries for hierarchy', len(tree))
4500 OUTPUT = common.open_text(new_tree_index, 'w')
4501 OUTPUT.write(MakeDocHeader("screen") + "\n<screen>\n" + AddTreeLineArt(tree) + "\n</screen>\n")
4502 OUTPUT.close()
4504 common.UpdateFileIfChanged(old_tree_index, new_tree_index, 0)
4506 OutputObjectList()
4509 def ReadInterfaces(ifile):
4510 """Reads the $MODULE.interfaces file.
4512 Args:
4513 ifile (str): the input filename.
4516 Interfaces.clear()
4518 if not os.path.isfile(ifile):
4519 return
4521 INPUT = common.open_text(ifile)
4523 for line in INPUT:
4524 line = line.strip()
4525 ifaces = line.split()
4526 gobject = ifaces.pop(0)
4527 if gobject in KnownSymbols and KnownSymbols[gobject] == 1:
4528 knownIfaces = []
4530 # filter out private interfaces, but leave foreign interfaces
4531 for iface in ifaces:
4532 if iface not in KnownSymbols or KnownSymbols[iface] == 1:
4533 knownIfaces.append(iface)
4535 Interfaces[gobject] = ' '.join(knownIfaces)
4536 logging.info("Interfaces for %s: %s", gobject, Interfaces[gobject])
4537 else:
4538 logging.info("skipping interfaces for unknown symbol: %s", gobject)
4540 INPUT.close()
4543 def ReadPrerequisites(ifile):
4544 """This reads in the $MODULE.prerequisites file.
4546 Args:
4547 ifile (str): the input filename.
4549 Prerequisites.clear()
4551 if not os.path.isfile(ifile):
4552 return
4554 INPUT = common.open_text(ifile)
4556 for line in INPUT:
4557 line = line.strip()
4558 prereqs = line.split()
4559 iface = prereqs.pop(0)
4560 if iface in KnownSymbols and KnownSymbols[iface] == 1:
4561 knownPrereqs = []
4563 # filter out private prerequisites, but leave foreign prerequisites
4564 for prereq in prereqs:
4565 if prereq not in KnownSymbols or KnownSymbols[prereq] == 1:
4566 knownPrereqs.append(prereq)
4568 Prerequisites[iface] = ' '.join(knownPrereqs)
4570 INPUT.close()
4573 def ReadArgsFile(ifile):
4574 """Reads information about object properties
4576 It creates the arrays ArgObjects, ArgNames, ArgTypes, ArgFlags, ArgNicks and
4577 ArgBlurbs containing info on the args.
4579 Args:
4580 ifile (str): the input filename.
4582 in_arg = False
4583 arg_object = None
4584 arg_name = None
4585 arg_type = None
4586 arg_flags = None
4587 arg_nick = None
4588 arg_blurb = None
4589 arg_default = None
4590 arg_range = None
4592 # Reset the args info.
4593 ArgObjects[:] = []
4594 ArgNames[:] = []
4595 ArgTypes[:] = []
4596 ArgFlags[:] = []
4597 ArgNicks[:] = []
4598 ArgBlurbs[:] = []
4599 ArgDefaults[:] = []
4600 ArgRanges[:] = []
4602 if not os.path.isfile(ifile):
4603 return
4605 INPUT = common.open_text(ifile)
4606 line_number = 0
4607 for line in INPUT:
4608 line_number += 1
4609 if not in_arg:
4610 if line.startswith('<ARG>'):
4611 in_arg = True
4612 arg_object = ''
4613 arg_name = ''
4614 arg_type = ''
4615 arg_flags = ''
4616 arg_nick = ''
4617 arg_blurb = ''
4618 arg_default = ''
4619 arg_range = ''
4621 else:
4622 m1 = re.search(r'^<NAME>(.*)</NAME>', line)
4623 m2 = re.search(r'^<TYPE>(.*)</TYPE>', line)
4624 m3 = re.search(r'^<RANGE>(.*)</RANGE>', line)
4625 m4 = re.search(r'^<FLAGS>(.*)</FLAGS>', line)
4626 m5 = re.search(r'^<NICK>(.*)</NICK>', line)
4627 m6 = re.search(r'^<BLURB>(.*)</BLURB>', line)
4628 m7 = re.search(r'^<DEFAULT>(.*)</DEFAULT>', line)
4629 if m1:
4630 arg_name = m1.group(1)
4631 m1_1 = re.search(r'^(.*)::(.*)$', arg_name)
4632 if m1_1:
4633 arg_object = m1_1.group(1)
4634 arg_name = m1_1.group(2).replace('_', '-')
4635 logging.info("Found arg: %s", arg_name)
4636 else:
4637 common.LogWarning(ifile, line_number, "Invalid argument name: " + arg_name)
4639 elif m2:
4640 arg_type = m2.group(1)
4641 elif m3:
4642 arg_range = m3.group(1)
4643 elif m4:
4644 arg_flags = m4.group(1)
4645 elif m5:
4646 arg_nick = m5.group(1)
4647 elif m6:
4648 arg_blurb = m6.group(1)
4649 if arg_blurb == "(null)":
4650 arg_blurb = ''
4651 common.LogWarning(
4652 ifile, line_number, "Property %s:%s has no documentation." % (arg_object, arg_name))
4654 elif m7:
4655 arg_default = m7.group(1)
4656 elif re.search(r'^</ARG>', line):
4657 logging.info("Found end of arg: %s::%s\n%s : %s", arg_object, arg_name, arg_type, arg_flags)
4658 ArgObjects.append(arg_object)
4659 ArgNames.append(arg_name)
4660 ArgTypes.append(arg_type)
4661 ArgRanges.append(arg_range)
4662 ArgFlags.append(arg_flags)
4663 ArgNicks.append(arg_nick)
4664 ArgBlurbs.append(arg_blurb)
4665 ArgDefaults.append(arg_default)
4666 in_arg = False
4668 INPUT.close()
4671 def AddTreeLineArt(tree):
4672 """Generate a line art tree.
4674 Add unicode lineart to a pre-indented string array and returns
4675 it as as multiline string.
4677 Args:
4678 tree (list): of indented strings.
4680 Returns:
4681 str: multiline string with tree line art
4683 # iterate bottom up over the tree
4684 for i in range(len(tree) - 1, -1, -1):
4685 # count leading spaces
4686 m = re.search(r'^([^<A-Za-z]*)', tree[i])
4687 indent = len(m.group(1))
4688 # replace with ╰───, if place of ╰ is not space insert ├
4689 if indent > 4:
4690 if tree[i][indent - 4] == " ":
4691 tree[i] = tree[i][:indent - 4] + "--- " + tree[i][indent:]
4692 else:
4693 tree[i] = tree[i][:indent - 4] + "+-- " + tree[i][indent:]
4695 # go lines up while space and insert |
4696 j = i - 1
4697 while j >= 0 and tree[j][indent - 4] == ' ':
4698 tree[j] = tree[j][:indent - 4] + '|' + tree[j][indent - 3:]
4699 j -= 1
4701 res = "\n".join(tree)
4702 # unicode chars for: ╰──
4703 res = re.sub(r'---', '<phrase role=\"lineart\">&#9584;&#9472;&#9472;</phrase>', res)
4704 # unicde chars for: ├──
4705 res = re.sub(r'\+--', '<phrase role=\"lineart\">&#9500;&#9472;&#9472;</phrase>', res)
4706 # unicode char for: │
4707 res = re.sub(r'\|', '<phrase role=\"lineart\">&#9474;</phrase>', res)
4709 return res
4712 def CheckIsObject(name):
4713 """Check if symbols is an object.
4715 It uses the global Objects array. Note that the Objects array only contains
4716 classes in the current module and their ancestors - not all GObject classes.
4718 Args:
4719 name (str): the object name to check.
4721 Returns:
4722 bool: True if the given name is a GObject or a subclass.
4724 root = ObjectRoots.get(name)
4725 # Let GBoxed pass as an object here to get -struct appended to the id
4726 # and prevent conflicts with sections.
4727 return root and root != 'GEnum' and root != 'GFlags'
4730 def GetSymbolSourceFile(symbol):
4731 """Get the filename where the symbol docs where taken from."""
4732 return SourceSymbolSourceFile.get(symbol, '')
4735 def GetSymbolSourceLine(symbol):
4736 """Get the file line where the symbol docs where taken from."""
4737 return SourceSymbolSourceLine.get(symbol, 0)