mkdb: readd space in error message
[gtk-doc.git] / gtkdoc / mkdb.py
blob0abdb3115e0813c269b13ba1f013cf11dae4e5cb
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 # We should pass the options variable around instead of this global variable horror
239 # but too much of the code expects these to be around. Fix this once the transition is done.
240 MODULE = options.module
241 INLINE_MARKUP_MODE = options.xml_mode or options.sgml_mode
242 DEFAULT_STABILITY = options.default_stability
243 NAME_SPACE = options.name_space
245 main_sgml_file = options.main_sgml_file
246 if not main_sgml_file:
247 # backwards compatibility
248 if os.path.exists(MODULE + "-docs.sgml"):
249 main_sgml_file = MODULE + "-docs.sgml"
250 else:
251 main_sgml_file = MODULE + "-docs.xml"
253 # extract docbook header or define default
254 doctype_header = GetDocbookHeader(main_sgml_file)
256 # This is where we put all the DocBook output.
257 DB_OUTPUT_DIR = DB_OUTPUT_DIR if DB_OUTPUT_DIR else os.path.join(ROOT_DIR, "xml")
258 if not os.path.isdir(DB_OUTPUT_DIR):
259 os.mkdir(DB_OUTPUT_DIR)
261 ReadKnownSymbols(os.path.join(ROOT_DIR, MODULE + "-sections.txt"))
262 ReadSignalsFile(os.path.join(ROOT_DIR, MODULE + ".signals"))
263 ReadArgsFile(os.path.join(ROOT_DIR, MODULE + ".args"))
264 ReadObjectHierarchy(os.path.join(ROOT_DIR, MODULE + ".hierarchy"))
265 ReadInterfaces(os.path.join(ROOT_DIR, MODULE + ".interfaces"))
266 ReadPrerequisites(os.path.join(ROOT_DIR, MODULE + ".prerequisites"))
268 ReadDeclarationsFile(os.path.join(ROOT_DIR, MODULE + "-decl.txt"), 0)
269 if os.path.isfile(os.path.join(ROOT_DIR, MODULE + "-overrides.txt")):
270 ReadDeclarationsFile(os.path.join(ROOT_DIR, MODULE + "-overrides.txt"), 1)
272 # Scan sources
273 if options.source_suffixes:
274 suffix_list = ['.' + ext for ext in options.source_suffixes.split(',')]
275 else:
276 suffix_list = ['.c', '.h']
278 source_dirs = options.source_dir
279 ignore_files = options.ignore_files
280 logging.info(" ignore files: " + ignore_files)
281 for sdir in source_dirs:
282 ReadSourceDocumentation(sdir, suffix_list, source_dirs, ignore_files)
284 changed, book_top, book_bottom = OutputDB(os.path.join(ROOT_DIR, MODULE + "-sections.txt"), options)
285 OutputBook(main_sgml_file, book_top, book_bottom)
287 logging.info("All files created: %d", changed)
289 # If any of the DocBook files have changed, update the timestamp file (so
290 # it can be used for Makefile dependencies).
291 if changed or not os.path.exists(os.path.join(ROOT_DIR, "sgml.stamp")):
293 # try to detect the common prefix
294 # GtkWidget, GTK_WIDGET, gtk_widget -> gtk
295 if NAME_SPACE == '':
296 NAME_SPACE = DetermineNamespace()
298 logging.info('namespace prefix ="%s"', NAME_SPACE)
300 OutputIndex("api-index-full", IndexEntriesFull)
301 OutputIndex("api-index-deprecated", IndexEntriesDeprecated)
302 OutputSinceIndexes()
303 OutputAnnotationGlossary()
305 with open(os.path.join(ROOT_DIR, 'sgml.stamp'), 'w') as h:
306 h.write('timestamp')
309 def OutputObjectList():
310 """This outputs the alphabetical list of objects, in a columned table."""
311 # FIXME: Currently this also outputs ancestor objects which may not actually
312 # be in this module.
313 cols = 3
315 # FIXME: use .xml
316 old_object_index = os.path.join(DB_OUTPUT_DIR, "object_index.sgml")
317 new_object_index = os.path.join(DB_OUTPUT_DIR, "object_index.new")
319 OUTPUT = common.open_text(new_object_index, 'w')
321 OUTPUT.write('''%s
322 <informaltable pgwide="1" frame="none">
323 <tgroup cols="%s">
324 <colspec colwidth="1*"/>
325 <colspec colwidth="1*"/>
326 <colspec colwidth="1*"/>
327 <tbody>
328 ''' % (MakeDocHeader("informaltable"), cols))
330 count = 0
331 object = None
332 for object in sorted(Objects):
333 xref = MakeXRef(object)
334 if count % cols == 0:
335 OUTPUT.write("<row>\n")
336 OUTPUT.write("<entry>%s</entry>\n" % xref)
337 if count % cols == cols - 1:
338 OUTPUT.write("</row>\n")
339 count += 1
341 if count == 0:
342 # emit an empty row, since empty tables are invalid
343 OUTPUT.write("<row><entry> </entry></row>\n")
345 else:
346 if count % cols > 0:
347 OUTPUT.write("</row>\n")
349 OUTPUT.write('''</tbody></tgroup></informaltable>\n''')
350 OUTPUT.close()
352 common.UpdateFileIfChanged(old_object_index, new_object_index, 0)
355 def TrimTextBlock(desc):
356 """Trims extra whitespace.
358 Empty lines inside a block are preserved.
359 Args:
360 desc (str): the text block to trim. May contain newlines.
363 # strip trailing spaces on every line
364 return re.sub(r'\s+$', '\n', desc.lstrip(), flags=re.MULTILINE)
367 def OutputDB(file, options):
368 """Generate docbook files.
370 This collects the output for each section of the docs, and outputs each file
371 when the end of the section is found.
373 Args:
374 file (str): the $MODULE-sections.txt file which contains all of the
375 functions/macros/structs etc. being documented, organised
376 into sections and subsections.
377 options: commandline options
380 logging.info("Reading: %s", file)
381 INPUT = common.open_text(file)
382 filename = ''
383 book_top = ''
384 book_bottom = ''
385 includes = options.default_includes or ''
386 section_includes = ''
387 in_section = 0
388 title = ''
389 section_id = ''
390 subsection = ''
391 num_symbols = 0
392 changed = 0
393 functions_synop = ''
394 other_synop = ''
395 functions_details = ''
396 other_details = ''
397 signals_synop = ''
398 signals_desc = ''
399 args_synop = ''
400 child_args_synop = ''
401 style_args_synop = ''
402 args_desc = ''
403 child_args_desc = ''
404 style_args_desc = ''
405 hierarchy_str = ''
406 hierarchy = []
407 interfaces = ''
408 implementations = ''
409 prerequisites = ''
410 derived = ''
411 file_objects = []
412 file_def_line = {}
413 symbol_def_line = {}
415 MergeSourceDocumentation()
417 line_number = 0
418 for line in INPUT:
419 line_number += 1
421 if line.startswith('#'):
422 continue
424 logging.info("section file data: %d: %s", line_number, line)
426 m1 = re.search(r'^<SUBSECTION\s*(.*)>', line, re.I)
427 m2 = re.search(r'^<TITLE>(.*)<\/TITLE', line)
428 m3 = re.search(r'^<FILE>(.*)<\/FILE>', line)
429 m4 = re.search(r'^<INCLUDE>(.*)<\/INCLUDE>', line)
430 m5 = re.search(r'^(\S+)', line)
432 if line.startswith('<SECTION>'):
433 num_symbols = 0
434 in_section = False
435 file_objects = []
436 symbol_def_line = {}
438 elif m1:
439 other_synop += "\n"
440 functions_synop += "\n"
441 subsection = m1.group(1)
443 elif line.startswith('<SUBSECTION>'):
444 continue
445 elif m2:
446 title = m2.group(1)
447 logging.info("Section: %s", title)
449 # We don't want warnings if object & class structs aren't used.
450 DeclarationOutput[title] = 1
451 DeclarationOutput["%sClass" % title] = 1
452 DeclarationOutput["%sIface" % title] = 1
453 DeclarationOutput["%sInterface" % title] = 1
455 elif m3:
456 filename = m3.group(1)
457 if filename not in file_def_line:
458 file_def_line[filename] = line_number
459 else:
460 common.LogWarning(file, line_number, "Double <FILE>%s</FILE> entry. Previous occurrence on line %s." %
461 (filename, file_def_line[filename]))
462 if title == '':
463 key = filename + ":Title"
464 if key in SourceSymbolDocs:
465 title = SourceSymbolDocs[key].rstrip()
467 elif m4:
468 if in_section:
469 section_includes = m4.group(1)
470 else:
471 if options.default_includes:
472 common.LogWarning(file, line_number, "Default <INCLUDE> being overridden by command line option.")
473 else:
474 includes = m4.group(1)
476 elif re.search(r'^<\/SECTION>', line):
477 logging.info("End of section: %s", title)
478 # TODO: also output if we have sections docs?
479 # long_desc = SymbolDocs.get(filename + ":Long_Description")
480 if num_symbols > 0:
481 # collect documents
482 book_bottom += " <xi:include href=\"xml/%s.xml\"/>\n" % filename
484 key = filename + ":Include"
485 if key in SourceSymbolDocs:
486 if section_includes:
487 common.LogWarning(file, line_number, "Section <INCLUDE> being overridden by inline comments.")
488 section_includes = SourceSymbolDocs[key]
490 if section_includes == '':
491 section_includes = includes
493 signals_synop = re.sub(r'^\n*', '', signals_synop)
494 signals_synop = re.sub(r'\n+$', '\n', signals_synop)
496 if signals_synop != '':
497 signals_synop = '''<refsect1 id="%s.signals" role="signal_proto">
498 <title role="signal_proto.title">Signals</title>
499 <informaltable frame="none">
500 <tgroup cols="3">
501 <colspec colname="signals_return" colwidth="150px"/>
502 <colspec colname="signals_name" colwidth="300px"/>
503 <colspec colname="signals_flags" colwidth="200px"/>
504 <tbody>
506 </tbody>
507 </tgroup>
508 </informaltable>
509 </refsect1>
510 ''' % (section_id, signals_synop)
511 signals_desc = TrimTextBlock(signals_desc)
512 signals_desc = '''<refsect1 id="%s.signal-details" role="signals">
513 <title role="signals.title">Signal Details</title>
515 </refsect1>
516 ''' % (section_id, signals_desc)
518 args_synop = re.sub(r'^\n*', '', args_synop)
519 args_synop = re.sub(r'\n+$', '\n', args_synop)
520 if args_synop != '':
521 args_synop = '''<refsect1 id="%s.properties" role="properties">
522 <title role="properties.title">Properties</title>
523 <informaltable frame="none">
524 <tgroup cols="3">
525 <colspec colname="properties_type" colwidth="150px"/>
526 <colspec colname="properties_name" colwidth="300px"/>
527 <colspec colname="properties_flags" colwidth="200px"/>
528 <tbody>
530 </tbody>
531 </tgroup>
532 </informaltable>
533 </refsect1>
534 ''' % (section_id, args_synop)
535 args_desc = TrimTextBlock(args_desc)
536 args_desc = '''<refsect1 id="%s.property-details" role="property_details">
537 <title role="property_details.title">Property Details</title>
539 </refsect1>
540 ''' % (section_id, args_desc)
542 child_args_synop = re.sub(r'^\n*', '', child_args_synop)
543 child_args_synop = re.sub(r'\n+$', '\n', child_args_synop)
544 if child_args_synop != '':
545 args_synop += '''<refsect1 id="%s.child-properties" role="child_properties">
546 <title role="child_properties.title">Child Properties</title>
547 <informaltable frame="none">
548 <tgroup cols="3">
549 <colspec colname="child_properties_type" colwidth="150px"/>
550 <colspec colname="child_properties_name" colwidth="300px"/>
551 <colspec colname="child_properties_flags" colwidth="200px"/>
552 <tbody>
554 </tbody>
555 </tgroup>
556 </informaltable>
557 </refsect1>
558 ''' % (section_id, child_args_synop)
559 child_args_desc = TrimTextBlock(child_args_desc)
560 args_desc += '''<refsect1 id="%s.child-property-details" role="child_property_details">
561 <title role="child_property_details.title">Child Property Details</title>
563 </refsect1>
564 ''' % (section_id, child_args_desc)
566 style_args_synop = re.sub(r'^\n*', '', style_args_synop)
567 style_args_synop = re.sub(r'\n+$', '\n', style_args_synop)
568 if style_args_synop != '':
569 args_synop += '''<refsect1 id="%s.style-properties" role="style_properties">
570 <title role="style_properties.title">Style Properties</title>
571 <informaltable frame="none">
572 <tgroup cols="3">
573 <colspec colname="style_properties_type" colwidth="150px"/>
574 <colspec colname="style_properties_name" colwidth="300px"/>
575 <colspec colname="style_properties_flags" colwidth="200px"/>
576 <tbody>
578 </tbody>
579 </tgroup>
580 </informaltable>
581 </refsect1>
582 ''' % (section_id, style_args_synop)
583 style_args_desc = TrimTextBlock(style_args_desc)
584 args_desc += '''<refsect1 id="%s.style-property-details" role="style_properties_details">
585 <title role="style_properties_details.title">Style Property Details</title>
587 </refsect1>
588 ''' % (section_id, style_args_desc)
590 hierarchy_str = AddTreeLineArt(hierarchy)
591 if hierarchy_str != '':
592 hierarchy_str = '''<refsect1 id="%s.object-hierarchy" role="object_hierarchy">
593 <title role="object_hierarchy.title">Object Hierarchy</title>
594 <screen>%s
595 </screen>
596 </refsect1>
597 ''' % (section_id, hierarchy_str)
599 interfaces = TrimTextBlock(interfaces)
600 if interfaces != '':
601 interfaces = '''<refsect1 id="%s.implemented-interfaces" role="impl_interfaces">
602 <title role="impl_interfaces.title">Implemented Interfaces</title>
604 </refsect1>
605 ''' % (section_id, interfaces)
607 implementations = TrimTextBlock(implementations)
608 if implementations != '':
609 implementations = '''<refsect1 id="%s.implementations" role="implementations">
610 <title role="implementations.title">Known Implementations</title>
612 </refsect1>
613 ''' % (section_id, implementations)
615 prerequisites = TrimTextBlock(prerequisites)
616 if prerequisites != '':
617 prerequisites = '''<refsect1 id="%s.prerequisites" role="prerequisites">
618 <title role="prerequisites.title">Prerequisites</title>
620 </refsect1>
621 ''' % (section_id, prerequisites)
623 derived = TrimTextBlock(derived)
624 if derived != '':
625 derived = '''<refsect1 id="%s.derived-interfaces" role="derived_interfaces">
626 <title role="derived_interfaces.title">Known Derived Interfaces</title>
628 </refsect1>
629 ''' % (section_id, derived)
631 functions_synop = re.sub(r'^\n*', '', functions_synop)
632 functions_synop = re.sub(r'\n+$', '\n', functions_synop)
633 if functions_synop != '':
634 functions_synop = '''<refsect1 id="%s.functions" role="functions_proto">
635 <title role="functions_proto.title">Functions</title>
636 <informaltable pgwide="1" frame="none">
637 <tgroup cols="2">
638 <colspec colname="functions_return" colwidth="150px"/>
639 <colspec colname="functions_name"/>
640 <tbody>
642 </tbody>
643 </tgroup>
644 </informaltable>
645 </refsect1>
646 ''' % (section_id, functions_synop)
648 other_synop = re.sub(r'^\n*', '', other_synop)
649 other_synop = re.sub(r'\n+$', '\n', other_synop)
650 if other_synop != '':
651 other_synop = '''<refsect1 id="%s.other" role="other_proto">
652 <title role="other_proto.title">Types and Values</title>
653 <informaltable role="enum_members_table" pgwide="1" frame="none">
654 <tgroup cols="2">
655 <colspec colname="name" colwidth="150px"/>
656 <colspec colname="description"/>
657 <tbody>
659 </tbody>
660 </tgroup>
661 </informaltable>
662 </refsect1>
663 ''' % (section_id, other_synop)
665 file_changed = OutputDBFile(filename, title, section_id,
666 section_includes,
667 functions_synop, other_synop,
668 functions_details, other_details,
669 signals_synop, signals_desc,
670 args_synop, args_desc,
671 hierarchy_str, interfaces,
672 implementations,
673 prerequisites, derived,
674 file_objects)
675 if file_changed:
676 changed = True
678 title = ''
679 section_id = ''
680 subsection = ''
681 in_section = 0
682 section_includes = ''
683 functions_synop = ''
684 other_synop = ''
685 functions_details = ''
686 other_details = ''
687 signals_synop = ''
688 signals_desc = ''
689 args_synop = ''
690 child_args_synop = ''
691 style_args_synop = ''
692 args_desc = ''
693 child_args_desc = ''
694 style_args_desc = ''
695 hierarchy_str = ''
696 hierarchy = []
697 interfaces = ''
698 implementations = ''
699 prerequisites = ''
700 derived = ''
702 elif m5:
703 symbol = m5.group(1)
704 logging.info(' Symbol: "%s" in subsection: "%s"', symbol, subsection)
706 # check for duplicate entries
707 if symbol not in symbol_def_line:
708 declaration = Declarations.get(symbol)
709 # FIXME: with this we'll output empty declaration
710 if declaration is not None:
711 if CheckIsObject(symbol):
712 file_objects.append(symbol)
714 # We don't want standard macros/functions of GObjects,
715 # or private declarations.
716 if subsection != "Standard" and subsection != "Private":
717 synop, desc = OutputDeclaration(symbol, declaration)
718 type = DeclarationTypes[symbol]
720 if type == 'FUNCTION' or type == 'USER_FUNCTION':
721 functions_synop += synop
722 functions_details += desc
723 elif type == 'MACRO' and re.search(symbol + r'\(', declaration):
724 functions_synop += synop
725 functions_details += desc
726 else:
727 other_synop += synop
728 other_details += desc
730 sig_synop, sig_desc = GetSignals(symbol)
731 arg_synop, child_arg_synop, style_arg_synop, arg_desc, child_arg_desc, style_arg_desc = GetArgs(
732 symbol)
733 ifaces = GetInterfaces(symbol)
734 impls = GetImplementations(symbol)
735 prereqs = GetPrerequisites(symbol)
736 der = GetDerived(symbol)
737 hierarchy = GetHierarchy(symbol, hierarchy)
739 signals_synop += sig_synop
740 signals_desc += sig_desc
741 args_synop += arg_synop
742 child_args_synop += child_arg_synop
743 style_args_synop += style_arg_synop
744 args_desc += arg_desc
745 child_args_desc += child_arg_desc
746 style_args_desc += style_arg_desc
747 interfaces += ifaces
748 implementations += impls
749 prerequisites += prereqs
750 derived += der
752 # Note that the declaration has been output.
753 DeclarationOutput[symbol] = True
754 elif subsection != "Standard" and subsection != "Private":
755 UndeclaredSymbols[symbol] = True
756 common.LogWarning(file, line_number, "No declaration found for %s." % symbol)
758 num_symbols += 1
759 symbol_def_line[symbol] = line_number
761 if section_id == '':
762 if title == '' and filename == '':
763 common.LogWarning(file, line_number, "Section has no title and no file.")
765 # FIXME: one of those would be enough
766 # filename should be an internal detail for gtk-doc
767 if title == '':
768 title = filename
769 elif filename == '':
770 filename = title
772 filename = filename.replace(' ', '_')
774 section_id = SourceSymbolDocs.get(filename + ":Section_Id")
775 if section_id and section_id.strip() != '':
776 # Remove trailing blanks and use as is
777 section_id = section_id.rstrip()
778 elif CheckIsObject(title):
779 # GObjects use their class name as the ID.
780 section_id = common.CreateValidSGMLID(title)
781 else:
782 section_id = common.CreateValidSGMLID(MODULE + '-' + title)
784 SymbolSection[symbol] = title
785 SymbolSectionId[symbol] = section_id
787 else:
788 common.LogWarning(file, line_number, "Double symbol entry for %s. "
789 "Previous occurrence on line %d." % (symbol, symbol_def_line[symbol]))
790 INPUT.close()
792 OutputMissingDocumentation()
793 OutputUndeclaredSymbols()
794 OutputUnusedSymbols()
796 if options.outputallsymbols:
797 OutputAllSymbols()
799 if options.outputsymbolswithoutsince:
800 OutputSymbolsWithoutSince()
802 for filename in options.expand_content_files.split():
803 file_changed = OutputExtraFile(filename)
804 if file_changed:
805 changed = True
807 return (changed, book_top, book_bottom)
810 def DetermineNamespace():
811 """Find common set of characters."""
812 name_space = ''
813 pos = 0
814 ratio = 0.0
815 while True:
816 prefix = {}
817 letter = ''
818 for symbol in iterkeys(IndexEntriesFull):
819 if name_space == '' or name_space.lower() in symbol.lower():
820 if len(symbol) > pos:
821 letter = symbol[pos:pos + 1]
822 # stop prefix scanning
823 if letter == "_":
824 # stop on "_"
825 break
826 # Should we also stop on a uppercase char, if last was lowercase
827 # GtkWidget, if we have the 'W' and had the 't' before
828 # or should we count upper and lowercase, and stop one 2nd uppercase, if we already had a lowercase
829 # GtkWidget, the 'W' would be the 2nd uppercase and with 't','k' we had lowercase chars before
830 # need to recound each time as this is per symbol
831 ul = letter.upper()
832 if ul in prefix:
833 prefix[ul] += 1
834 else:
835 prefix[ul] = 1
837 if letter != '' and letter != "_":
838 maxletter = ''
839 maxsymbols = 0
840 for letter in iterkeys(prefix):
841 logging.debug("ns prefix: %s: %s", letter, prefix[letter])
842 if prefix[letter] > maxsymbols:
843 maxletter = letter
844 maxsymbols = prefix[letter]
846 ratio = float(len(IndexEntriesFull)) / prefix[maxletter]
847 logging.debug('most symbols start with %s, that is %f', maxletter, (100 * ratio))
848 if ratio > 0.9:
849 # do another round
850 name_space += maxletter
852 pos += 1
854 else:
855 ratio = 0.0
857 if ratio < 0.9:
858 break
859 return name_space
862 def OutputIndex(basename, apiindex):
863 """Writes an index that can be included into the main-document into an <index> tag.
865 Args:
866 basename (str): name of the index file without extension
867 apiindex (dict): the index data
869 old_index = os.path.join(DB_OUTPUT_DIR, basename + '.xml')
870 new_index = os.path.join(DB_OUTPUT_DIR, basename + '.new')
871 lastletter = " "
872 divopen = 0
873 symbol = None
874 short_symbol = None
876 OUTPUT = open(new_index, 'w')
878 OUTPUT.write(MakeDocHeader("indexdiv") + "\n<indexdiv id=\"%s\">\n" % basename)
880 logging.info("generate %s index (%d entries) with namespace %s", basename, len(apiindex), NAME_SPACE)
882 # do a case insensitive sort while chopping off the prefix
883 mapped_keys = [
885 'original': x,
886 'short': re.sub(r'^' + NAME_SPACE + r'\_?(.*)', r'\1', x.upper(), flags=re.I),
887 } for x in iterkeys(apiindex)]
888 sorted_keys = sorted(mapped_keys, key=lambda d: (d['short'], d['original']))
890 for key in sorted_keys:
891 symbol = key['original']
892 short = key['short']
893 if short != '':
894 short_symbol = short
895 else:
896 short_symbol = symbol
898 # generate a short symbol description
899 symbol_desc = ''
900 symbol_section = ''
901 symbol_section_id = ''
902 symbol_type = ''
903 if symbol in DeclarationTypes:
904 symbol_type = DeclarationTypes[symbol].lower()
906 if symbol_type == '':
907 logging.info("trying symbol %s", symbol)
908 m1 = re.search(r'(.*)::(.*)', symbol)
909 m2 = re.search(r'(.*):(.*)', symbol)
910 if m1:
911 oname = m1.group(1)
912 osym = m1.group(2)
913 logging.info(" trying object signal %s:%s in %d signals", oname, osym, len(SignalNames))
914 for name in SignalNames:
915 logging.info(" " + name)
916 if name == osym:
917 symbol_type = "object signal"
918 if oname in SymbolSection:
919 symbol_section = SymbolSection[oname]
920 symbol_section_id = SymbolSectionId[oname]
921 break
922 elif m2:
923 oname = m2.group(1)
924 osym = m2.group(2)
925 logging.info(" trying object property %s::%s in %d properties", oname, osym, len(ArgNames))
926 for name in ArgNames:
927 logging.info(" " + name)
928 if name == osym:
929 symbol_type = "object property"
930 if oname in SymbolSection:
931 symbol_section = SymbolSection[oname]
932 symbol_section_id = SymbolSectionId[oname]
933 break
934 else:
935 if symbol in SymbolSection:
936 symbol_section = SymbolSection[symbol]
937 symbol_section_id = SymbolSectionId[symbol]
939 if symbol_type != '':
940 symbol_desc = ", " + symbol_type
941 if symbol_section != '':
942 symbol_desc += " in <link linkend=\"%s\">%s</link>" % (symbol_section_id, symbol_section)
943 # symbol_desc +=" in " + ExpandAbbreviations(symbol, "#symbol_section")
945 curletter = short_symbol[0].upper()
946 ixid = apiindex[symbol]
948 logging.info(" add symbol %s with %s to index in section '%s' (derived from %s)",
949 symbol, ixid, curletter, short_symbol)
951 if curletter != lastletter:
952 lastletter = curletter
954 if divopen:
955 OUTPUT.write("</indexdiv>\n")
957 OUTPUT.write("<indexdiv><title>%s</title>\n" % curletter)
958 divopen = True
960 OUTPUT.write('<indexentry><primaryie linkends="%s"><link linkend="%s">%s</link>%s</primaryie></indexentry>\n' %
961 (ixid, ixid, symbol, symbol_desc))
963 if divopen:
964 OUTPUT.write("</indexdiv>\n")
966 OUTPUT.write("</indexdiv>\n")
967 OUTPUT.close()
969 common.UpdateFileIfChanged(old_index, new_index, 0)
972 def OutputSinceIndexes():
973 """Generate the 'since' api index files."""
974 for version in set(Since.values()):
975 logging.info("Since : [%s]", version)
976 index = {x: IndexEntriesSince[x] for x in iterkeys(IndexEntriesSince) if Since[x] == version}
977 OutputIndex("api-index-" + version, index)
980 def OutputAnnotationGlossary():
981 """Writes a glossary of the used annotation terms.
983 The glossary file can be included into the main document.
985 # if there are no annotations used return
986 if not AnnotationsUsed:
987 return
989 old_glossary = os.path.join(DB_OUTPUT_DIR, "annotation-glossary.xml")
990 new_glossary = os.path.join(DB_OUTPUT_DIR, "annotation-glossary.new")
991 lastletter = " "
992 divopen = False
994 # add acronyms that are referenced from acronym text
995 rerun = True
996 while rerun:
997 rerun = False
998 for annotation in AnnotationsUsed:
999 if annotation not in AnnotationDefinition:
1000 continue
1001 m = re.search(r'<acronym>([\w ]+)<\/acronym>', AnnotationDefinition[annotation])
1002 if m and m.group(1) not in AnnotationsUsed:
1003 AnnotationsUsed[m.group(1)] = 1
1004 rerun = True
1005 break
1007 OUTPUT = common.open_text(new_glossary, 'w')
1009 OUTPUT.write('''%s
1010 <glossary id="annotation-glossary">
1011 <title>Annotation Glossary</title>
1012 ''' % MakeDocHeader("glossary"))
1014 for annotation in sorted(iterkeys(AnnotationsUsed), key=str.lower):
1015 if annotation in AnnotationDefinition:
1016 definition = AnnotationDefinition[annotation]
1017 curletter = annotation[0].upper()
1019 if curletter != lastletter:
1020 lastletter = curletter
1022 if divopen:
1023 OUTPUT.write("</glossdiv>\n")
1025 OUTPUT.write("<glossdiv><title>%s</title>\n" % curletter)
1026 divopen = True
1028 OUTPUT.write(''' <glossentry>
1029 <glossterm><anchor id="annotation-glossterm-%s"/>%s</glossterm>
1030 <glossdef>
1031 <para>%s</para>
1032 </glossdef>
1033 </glossentry>
1034 ''' % (annotation, annotation, definition))
1036 if divopen:
1037 OUTPUT.write("</glossdiv>\n")
1039 OUTPUT.write("</glossary>\n")
1040 OUTPUT.close()
1042 common.UpdateFileIfChanged(old_glossary, new_glossary, 0)
1045 def ReadKnownSymbols(file):
1046 """Collect the names of non-private symbols from the $MODULE-sections.txt file.
1048 Args:
1049 file: the $MODULE-sections.txt file
1052 subsection = ''
1054 logging.info("Reading: %s", file)
1055 INPUT = common.open_text(file)
1056 for line in INPUT:
1057 if line.startswith('#'):
1058 continue
1060 if line.startswith('<SECTION>'):
1061 subsection = ''
1062 continue
1064 m = re.search(r'^<SUBSECTION\s*(.*)>', line, flags=re.I)
1065 if m:
1066 subsection = m.group(1)
1067 continue
1069 if line.startswith('<SUBSECTION>'):
1070 continue
1072 if re.search(r'^<TITLE>(.*)<\/TITLE>', line):
1073 continue
1075 m = re.search(r'^<FILE>(.*)<\/FILE>', line)
1076 if m:
1077 KnownSymbols[m.group(1) + ":Long_Description"] = 1
1078 KnownSymbols[m.group(1) + ":Short_Description"] = 1
1079 continue
1081 m = re.search(r'^<INCLUDE>(.*)<\/INCLUDE>', line)
1082 if m:
1083 continue
1085 m = re.search(r'^<\/SECTION>', line)
1086 if m:
1087 continue
1089 m = re.search(r'^(\S+)', line)
1090 if m:
1091 symbol = m.group(1)
1092 if subsection != "Standard" and subsection != "Private":
1093 KnownSymbols[symbol] = 1
1094 else:
1095 KnownSymbols[symbol] = 0
1096 INPUT.close()
1099 def OutputDeclaration(symbol, declaration):
1100 """Returns the formatted documentation block for a symbol.
1102 Args:
1103 symbol (str): the name of the function/macro/...
1104 declaration (str): the declaration of the function/macro.
1106 Returns:
1107 str: the formatted documentation
1110 dtype = DeclarationTypes[symbol]
1111 if dtype == 'MACRO':
1112 return OutputMacro(symbol, declaration)
1113 elif dtype == 'TYPEDEF':
1114 return OutputTypedef(symbol, declaration)
1115 elif dtype == 'STRUCT':
1116 return OutputStruct(symbol, declaration)
1117 elif dtype == 'ENUM':
1118 return OutputEnum(symbol, declaration)
1119 elif dtype == 'UNION':
1120 return OutputUnion(symbol, declaration)
1121 elif dtype == 'VARIABLE':
1122 return OutputVariable(symbol, declaration)
1123 elif dtype == 'FUNCTION':
1124 return OutputFunction(symbol, declaration, dtype)
1125 elif dtype == 'USER_FUNCTION':
1126 return OutputFunction(symbol, declaration, dtype)
1127 else:
1128 logging.warning("Unknown symbol type %s for symbol %s", dtype, symbol)
1129 return ('', '')
1132 def OutputSymbolTraits(symbol):
1133 """Returns the Since and StabilityLevel paragraphs for a symbol.
1135 Args:
1136 symbol (str): the name to describe
1138 Returns:
1139 str: paragraph or empty string
1142 desc = ''
1144 if symbol in Since:
1145 link_id = "api-index-" + Since[symbol]
1146 desc += "<para role=\"since\">Since: <link linkend=\"%s\">%s</link></para>" % (link_id, Since[symbol])
1148 if symbol in StabilityLevel:
1149 stability = StabilityLevel[symbol]
1150 if stability in AnnotationDefinition:
1151 AnnotationsUsed[stability] = True
1152 stability = "<acronym>%s</acronym>" % stability
1153 desc += "<para role=\"stability\">Stability Level: %s</para>" % stability
1154 return desc
1157 def uri_escape(text):
1158 if text is None:
1159 return None
1161 # Build a char to hex map
1162 escapes = {chr(i): ("%%%02X" % i) for i in range(256)}
1164 # Default unsafe characters. RFC 2732 ^(uric - reserved)
1165 def do_escape(char):
1166 return escapes[char]
1167 return re.sub(r"([^A-Za-z0-9\-_.!~*'()]", do_escape, text)
1170 def OutputSymbolExtraLinks(symbol):
1171 """Returns extralinks for the symbol (if enabled).
1173 Args:
1174 symbol (str): the name to describe
1176 Returns:
1177 str: paragraph or empty string
1179 desc = ''
1181 if False: # NEW FEATURE: needs configurability
1182 sstr = uri_escape(symbol)
1183 mstr = uri_escape(MODULE)
1184 desc += '''<ulink role="extralinks" url="http://www.google.com/codesearch?q=%s">code search</ulink>
1185 <ulink role="extralinks" url="http://library.gnome.org/edit?module=%s&amp;symbol=%s">edit documentation</ulink>
1186 ''' % (sstr, mstr, sstr)
1188 return desc
1191 def OutputSectionExtraLinks(symbol, docsymbol):
1192 desc = ''
1194 if False: # NEW FEATURE: needs configurability
1195 sstr = uri_escape(symbol)
1196 mstr = uri_escape(MODULE)
1197 dsstr = uri_escape(docsymbol)
1198 desc += '''<ulink role="extralinks" url="http://www.google.com/codesearch?q=%s">code search</ulink>
1199 <ulink role="extralinks" url="http://library.gnome.org/edit?module=%s&amp;symbol=%s">edit documentation</ulink>
1200 ''' % (sstr, mstr, dsstr)
1201 return desc
1204 def OutputMacro(symbol, declaration):
1205 """Returns the synopsis and detailed description of a macro.
1207 Args:
1208 symbol (str): the macro name.
1209 declaration (str): the declaration of the macro.
1211 Returns:
1212 str: the formated docs
1214 sid = common.CreateValidSGMLID(symbol)
1215 condition = MakeConditionDescription(symbol)
1216 synop = "<row><entry role=\"define_keyword\">#define</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link>" % (
1217 sid, symbol)
1219 fields = common.ParseMacroDeclaration(declaration, CreateValidSGML)
1220 title = symbol
1221 if len(fields) > 0:
1222 title += '()'
1224 desc = '<refsect2 id="%s" role="macro"%s>\n<title>%s</title>\n' % (sid, condition, title)
1225 desc += MakeIndexterms(symbol, sid)
1226 desc += "\n"
1227 desc += OutputSymbolExtraLinks(symbol)
1229 if len(fields) > 0:
1230 synop += "<phrase role=\"c_punctuation\">()</phrase>"
1232 synop += "</entry></row>\n"
1234 # Don't output the macro definition if is is a conditional macro or it
1235 # looks like a function, i.e. starts with "g_" or "_?gnome_", or it is
1236 # longer than 2 lines, otherwise we get lots of complicated macros like
1237 # g_assert.
1238 if symbol not in DeclarationConditional and not symbol.startswith('g_') \
1239 and not re.search(r'^_?gnome_', symbol) and declaration.count('\n') < 2:
1240 decl_out = CreateValidSGML(declaration)
1241 desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out
1242 else:
1243 desc += "<programlisting language=\"C\">" + "#define".ljust(RETURN_TYPE_FIELD_WIDTH) + symbol
1244 m = re.search(r'^\s*#\s*define\s+\w+(\([^\)]*\))', declaration)
1245 if m:
1246 args = m.group(1)
1247 pad = ' ' * (RETURN_TYPE_FIELD_WIDTH - len("#define "))
1248 # Align each line so that if should all line up OK.
1249 args = args.replace('\n', '\n' + pad)
1250 desc += CreateValidSGML(args)
1252 desc += "</programlisting>\n"
1254 desc += MakeDeprecationNote(symbol)
1256 parameters = OutputParamDescriptions("MACRO", symbol, fields)
1258 if symbol in SymbolDocs:
1259 symbol_docs = ConvertMarkDown(symbol, SymbolDocs[symbol])
1260 desc += symbol_docs
1262 desc += parameters
1263 desc += OutputSymbolTraits(symbol)
1264 desc += "</refsect2>\n"
1265 return (synop, desc)
1268 def OutputTypedef(symbol, declaration):
1269 """Returns the synopsis and detailed description of a typedef.
1271 Args:
1272 symbol (str): the typedef.
1273 declaration (str): the declaration of the typedef,
1274 e.g. 'typedef unsigned int guint;'
1276 Returns:
1277 str: the formated docs
1279 sid = common.CreateValidSGMLID(symbol)
1280 condition = MakeConditionDescription(symbol)
1281 desc = "<refsect2 id=\"%s\" role=\"typedef\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
1282 synop = "<row><entry role=\"typedef_keyword\">typedef</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1283 sid, symbol)
1285 desc += MakeIndexterms(symbol, sid)
1286 desc += "\n"
1287 desc += OutputSymbolExtraLinks(symbol)
1289 if symbol not in DeclarationConditional:
1290 decl_out = CreateValidSGML(declaration)
1291 desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out
1293 desc += MakeDeprecationNote(symbol)
1295 if symbol in SymbolDocs:
1296 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1298 desc += OutputSymbolTraits(symbol)
1299 desc += "</refsect2>\n"
1300 return (synop, desc)
1303 def OutputStruct(symbol, declaration):
1304 """Returns the synopsis and detailed description of a struct.
1306 We check if it is a object struct, and if so we only output parts of it that
1307 are noted as public fields. We also use a different IDs for object structs,
1308 since the original ID is used for the entire RefEntry.
1310 Args:
1311 symbol (str): the struct.
1312 declaration (str): the declaration of the struct.
1314 Returns:
1315 str: the formated docs
1317 is_gtype = False
1318 default_to_public = True
1319 if CheckIsObject(symbol):
1320 logging.info("Found struct gtype: %s", symbol)
1321 is_gtype = True
1322 default_to_public = ObjectRoots[symbol] == 'GBoxed'
1324 sid = None
1325 condition = None
1326 if is_gtype:
1327 sid = common.CreateValidSGMLID(symbol + "_struct")
1328 condition = MakeConditionDescription(symbol + "_struct")
1329 else:
1330 sid = common.CreateValidSGMLID(symbol)
1331 condition = MakeConditionDescription(symbol)
1333 # Determine if it is a simple struct or it also has a typedef.
1334 has_typedef = False
1335 if symbol in StructHasTypedef or re.search(r'^\s*typedef\s+', declaration):
1336 has_typedef = True
1338 type_output = None
1339 desc = None
1340 if has_typedef:
1341 # For structs with typedefs we just output the struct name.
1342 type_output = ''
1343 desc = "<refsect2 id=\"%s\" role=\"struct\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
1344 else:
1345 type_output = "struct"
1346 desc = "<refsect2 id=\"%s\" role=\"struct\"%s>\n<title>struct %s</title>\n" % (sid, condition, symbol)
1348 synop = "<row><entry role=\"datatype_keyword\">%s</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1349 type_output, sid, symbol)
1351 desc += MakeIndexterms(symbol, sid)
1352 desc += "\n"
1353 desc += OutputSymbolExtraLinks(symbol)
1355 # Form a pretty-printed, private-data-removed form of the declaration
1357 decl_out = ''
1358 if re.search(r'^\s*$', declaration):
1359 logging.info("Found opaque struct: %s", symbol)
1360 decl_out = "typedef struct _%s %s;" % (symbol, symbol)
1361 elif re.search(r'^\s*struct\s+\w+\s*;\s*$', declaration):
1362 logging.info("Found opaque struct: %s", symbol)
1363 decl_out = "struct %s;" % symbol
1364 else:
1365 m = re.search(
1366 r'^\s*(typedef\s+)?struct\s*\w*\s*(?:\/\*.*\*\/)?\s*{(.*)}\s*\w*\s*;\s*$', declaration, flags=re.S)
1367 if m:
1368 struct_contents = m.group(2)
1370 public = default_to_public
1371 new_declaration = ''
1373 for decl_line in struct_contents.splitlines():
1374 logging.info("Struct line: %s", decl_line)
1375 m2 = re.search(r'/\*\s*<\s*public\s*>\s*\*/', decl_line)
1376 m3 = re.search(r'/\*\s*<\s*(private|protected)\s*>\s*\*/', decl_line)
1377 if m2:
1378 public = True
1379 elif m3:
1380 public = False
1381 elif public:
1382 new_declaration += decl_line + "\n"
1384 if new_declaration:
1385 # Strip any blank lines off the ends.
1386 new_declaration = re.sub(r'^\s*\n', '', new_declaration)
1387 new_declaration = re.sub(r'\n\s*$', r'\n', new_declaration)
1389 if has_typedef:
1390 decl_out = "typedef struct {\n%s} %s;\n" % (new_declaration, symbol)
1391 else:
1392 decl_out = "struct %s {\n%s};\n" % (symbol, new_declaration)
1394 else:
1395 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1396 "Couldn't parse struct:\n%s" % declaration)
1398 # If we couldn't parse the struct or it was all private, output an
1399 # empty struct declaration.
1400 if decl_out == '':
1401 if has_typedef:
1402 decl_out = "typedef struct _%s %s;" % (symbol, symbol)
1403 else:
1404 decl_out = "struct %s;" % symbol
1406 decl_out = CreateValidSGML(decl_out)
1407 desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out
1409 desc += MakeDeprecationNote(symbol)
1411 if symbol in SymbolDocs:
1412 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1414 # Create a table of fields and descriptions
1416 # FIXME: Inserting &#160's into the produced type declarations here would
1417 # improve the output in most situations ... except for function
1418 # members of structs!
1419 def pfunc(*args):
1420 return '<structfield id="%s">%s</structfield>' % (common.CreateValidSGMLID(sid + '.' + args[0]), args[0])
1421 fields = common.ParseStructDeclaration(declaration, not default_to_public, 0, MakeXRef, pfunc)
1422 params = SymbolParams.get(symbol)
1424 # If no parameters are filled in, we don't generate the description
1425 # table, for backwards compatibility.
1426 found = False
1427 if params:
1428 found = next((True for p in params.values() if p.strip() != ''), False)
1430 if found:
1431 field_descrs = params
1432 missing_parameters = ''
1433 unused_parameters = ''
1434 sid = common.CreateValidSGMLID(symbol + ".members")
1436 desc += '''<refsect3 id="%s" role="struct_members">\n<title>Members</title>
1437 <informaltable role="struct_members_table" pgwide="1" frame="none">
1438 <tgroup cols="3">
1439 <colspec colname="struct_members_name" colwidth="300px"/>
1440 <colspec colname="struct_members_description"/>
1441 <colspec colname="struct_members_annotations" colwidth="200px"/>
1442 <tbody>
1443 ''' % sid
1445 for field_name, text in iteritems(fields):
1446 param_annotations = ''
1448 desc += "<row role=\"member\"><entry role=\"struct_member_name\"><para>%s</para></entry>\n" % text
1449 if field_name in field_descrs:
1450 (field_descr, param_annotations) = ExpandAnnotation(symbol, field_descrs[field_name])
1451 field_descr = ConvertMarkDown(symbol, field_descr)
1452 # trim
1453 field_descr = re.sub(r'^(\s|\n)+', '', field_descr, flags=re.M | re.S)
1454 field_descr = re.sub(r'(\s|\n)+$', '', field_descr, flags=re.M | re.S)
1455 desc += "<entry role=\"struct_member_description\">%s</entry>\n<entry role=\"struct_member_annotations\">%s</entry>\n" % (
1456 field_descr, param_annotations)
1457 del field_descrs[field_name]
1458 else:
1459 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1460 "Field description for %s::%s is missing in source code comment block." % (symbol, field_name))
1461 if missing_parameters != '':
1462 missing_parameters += ", " + field_name
1463 else:
1464 missing_parameters = field_name
1466 desc += "<entry /><entry />\n"
1468 desc += "</row>\n"
1470 desc += "</tbody></tgroup></informaltable>\n</refsect3>\n"
1471 for field_name in field_descrs:
1472 # Documenting those standard fields is not required anymore, but
1473 # we don't want to warn if they are documented anyway.
1474 m = re.search(r'(g_iface|parent_instance|parent_class)', field_name)
1475 if m:
1476 continue
1478 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1479 "Field description for %s::%s is not used from source code comment block." % (symbol, field_name))
1480 if unused_parameters != '':
1481 unused_parameters += ", " + field_name
1482 else:
1483 unused_parameters = field_name
1485 # remember missing/unused parameters (needed in tmpl-free build)
1486 if missing_parameters != '' and (symbol not in AllIncompleteSymbols):
1487 AllIncompleteSymbols[symbol] = missing_parameters
1489 if unused_parameters != '' and (symbol not in AllUnusedSymbols):
1490 AllUnusedSymbols[symbol] = unused_parameters
1491 else:
1492 if fields:
1493 if symbol not in AllIncompleteSymbols:
1494 AllIncompleteSymbols[symbol] = "<items>"
1495 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1496 "Field descriptions for struct %s are missing in source code comment block." % symbol)
1497 logging.info("Remaining structs fields: " + ':'.join(fields) + "\n")
1499 desc += OutputSymbolTraits(symbol)
1500 desc += "</refsect2>\n"
1501 return (synop, desc)
1504 def OutputUnion(symbol, declaration):
1505 """Returns the synopsis and detailed description of a union.
1507 Args:
1508 symbol (str): the union.
1509 declaration (str): the declaration of the union.
1511 Returns:
1512 str: the formated docs
1514 is_gtype = False
1515 if CheckIsObject(symbol):
1516 logging.info("Found union gtype: %s", symbol)
1517 is_gtype = True
1519 sid = None
1520 condition = None
1521 if is_gtype:
1522 sid = common.CreateValidSGMLID(symbol + "_union")
1523 condition = MakeConditionDescription(symbol + "_union")
1524 else:
1525 sid = common.CreateValidSGMLID(symbol)
1526 condition = MakeConditionDescription(symbol)
1528 # Determine if it is a simple struct or it also has a typedef.
1529 has_typedef = False
1530 if symbol in StructHasTypedef or re.search(r'^\s*typedef\s+', declaration):
1531 has_typedef = True
1533 type_output = None
1534 desc = None
1535 if has_typedef:
1536 # For unions with typedefs we just output the union name.
1537 type_output = ''
1538 desc = "<refsect2 id=\"%s\" role=\"union\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
1539 else:
1540 type_output = "union"
1541 desc = "<refsect2 id=\"%s\" role=\"union\"%s>\n<title>union %s</title>\n" % (sid, condition, symbol)
1543 synop = "<row><entry role=\"datatype_keyword\">%s</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1544 type_output, sid, symbol)
1546 desc += MakeIndexterms(symbol, sid)
1547 desc += "\n"
1548 desc += OutputSymbolExtraLinks(symbol)
1549 desc += MakeDeprecationNote(symbol)
1551 if symbol in SymbolDocs:
1552 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1554 # Create a table of fields and descriptions
1556 # FIXME: Inserting &#160's into the produced type declarations here would
1557 # improve the output in most situations ... except for function
1558 # members of structs!
1559 def pfunc(*args):
1560 return '<structfield id="%s">%s</structfield>' % (common.CreateValidSGMLID(sid + '.' + args[0]), args[0])
1561 fields = common.ParseStructDeclaration(declaration, 0, 0, MakeXRef, pfunc)
1562 params = SymbolParams.get(symbol)
1564 # If no parameters are filled in, we don't generate the description
1565 # table, for backwards compatibility
1566 found = False
1567 if params:
1568 found = next((True for p in params.values() if p.strip() != ''), False)
1570 logging.debug('Union %s has %d entries, found=%d, has_typedef=%d', symbol, len(fields), found, has_typedef)
1572 if found:
1573 field_descrs = params
1574 missing_parameters = ''
1575 unused_parameters = ''
1576 sid = common.CreateValidSGMLID('%s.members' % symbol)
1578 desc += '''<refsect3 id="%s" role="union_members">\n<title>Members</title>
1579 <informaltable role="union_members_table" pgwide="1" frame="none">
1580 <tgroup cols="3">
1581 <colspec colname="union_members_name" colwidth="300px"/>
1582 <colspec colname="union_members_description"/>
1583 <colspec colname="union_members_annotations" colwidth="200px"/>
1584 <tbody>
1585 ''' % sid
1587 for field_name, text in iteritems(fields):
1588 param_annotations = ''
1590 desc += "<row><entry role=\"union_member_name\"><para>%s</para></entry>\n" % text
1591 if field_name in field_descrs:
1592 (field_descr, param_annotations) = ExpandAnnotation(symbol, field_descrs[field_name])
1593 field_descr = ConvertMarkDown(symbol, field_descr)
1595 # trim
1596 field_descr = re.sub(r'^(\s|\n)+', '', field_descr, flags=re.M | re.S)
1597 field_descr = re.sub(r'(\s|\n)+$', '', field_descr, flags=re.M | re.S)
1598 desc += "<entry role=\"union_member_description\">%s</entry>\n<entry role=\"union_member_annotations\">%s</entry>\n" % (
1599 field_descr, param_annotations)
1600 del field_descrs[field_name]
1601 else:
1602 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1603 "Field description for %s::%s is missing in source code comment block." % (symbol, field_name))
1604 if missing_parameters != '':
1605 missing_parameters += ", " + field_name
1606 else:
1607 missing_parameters = field_name
1609 desc += "<entry /><entry />\n"
1611 desc += "</row>\n"
1613 desc += "</tbody></tgroup></informaltable>\n</refsect3>"
1614 for field_name in field_descrs:
1615 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1616 "Field description for %s::%s is not used from source code comment block." % (symbol, field_name))
1617 if unused_parameters != '':
1618 unused_parameters += ", " + field_name
1619 else:
1620 unused_parameters = field_name
1622 # remember missing/unused parameters (needed in tmpl-free build)
1623 if missing_parameters != '' and (symbol not in AllIncompleteSymbols):
1624 AllIncompleteSymbols[symbol] = missing_parameters
1626 if unused_parameters != '' and (symbol not in AllUnusedSymbols):
1627 AllUnusedSymbols[symbol] = unused_parameters
1628 else:
1629 if len(fields) > 0:
1630 if symbol not in AllIncompleteSymbols:
1631 AllIncompleteSymbols[symbol] = "<items>"
1632 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1633 "Field descriptions for union %s are missing in source code comment block." % symbol)
1634 logging.info("Remaining union fields: " + ':'.join(fields) + "\n")
1636 desc += OutputSymbolTraits(symbol)
1637 desc += "</refsect2>\n"
1638 return (synop, desc)
1641 def OutputEnum(symbol, declaration):
1642 """Returns the synopsis and detailed description of a enum.
1644 Args:
1645 symbol (str): the enum.
1646 declaration (str): the declaration of the enum.
1648 Returns:
1649 str: the formated docs
1651 is_gtype = False
1652 if CheckIsObject(symbol):
1653 logging.info("Found enum gtype: %s", symbol)
1654 is_gtype = True
1656 sid = None
1657 condition = None
1658 if is_gtype:
1659 sid = common.CreateValidSGMLID(symbol + "_enum")
1660 condition = MakeConditionDescription(symbol + "_enum")
1661 else:
1662 sid = common.CreateValidSGMLID(symbol)
1663 condition = MakeConditionDescription(symbol)
1665 synop = "<row><entry role=\"datatype_keyword\">enum</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1666 sid, symbol)
1667 desc = "<refsect2 id=\"%s\" role=\"enum\"%s>\n<title>enum %s</title>\n" % (sid, condition, symbol)
1669 desc += MakeIndexterms(symbol, sid)
1670 desc += "\n"
1671 desc += OutputSymbolExtraLinks(symbol)
1672 desc += MakeDeprecationNote(symbol)
1674 if symbol in SymbolDocs:
1675 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1677 # Create a table of fields and descriptions
1679 fields = common.ParseEnumDeclaration(declaration)
1680 params = SymbolParams.get(symbol)
1682 # If nothing at all is documented log a single summary warning at the end.
1683 # Otherwise, warn about each undocumented item.
1685 found = False
1686 if params:
1687 found = next((True for p in params.values() if p.strip() != ''), False)
1688 field_descrs = params
1689 else:
1690 field_descrs = {}
1692 missing_parameters = ''
1693 unused_parameters = ''
1695 sid = common.CreateValidSGMLID("%s.members" % symbol)
1696 desc += '''<refsect3 id="%s" role="enum_members">\n<title>Members</title>
1697 <informaltable role="enum_members_table" pgwide="1" frame="none">
1698 <tgroup cols="3">
1699 <colspec colname="enum_members_name" colwidth="300px"/>
1700 <colspec colname="enum_members_description"/>
1701 <colspec colname="enum_members_annotations" colwidth="200px"/>
1702 <tbody>
1703 ''' % sid
1705 for field_name in fields:
1706 field_descr = field_descrs.get(field_name)
1707 param_annotations = ''
1709 sid = common.CreateValidSGMLID(field_name)
1710 condition = MakeConditionDescription(field_name)
1711 desc += "<row role=\"constant\"><entry role=\"enum_member_name\"><para id=\"%s\">%s</para></entry>\n" % (
1712 sid, field_name)
1713 if field_descr:
1714 field_descr, param_annotations = ExpandAnnotation(symbol, field_descr)
1715 field_descr = ConvertMarkDown(symbol, field_descr)
1716 desc += "<entry role=\"enum_member_description\">%s</entry>\n<entry role=\"enum_member_annotations\">%s</entry>\n" % (
1717 field_descr, param_annotations)
1718 del field_descrs[field_name]
1719 else:
1720 if found:
1721 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1722 "Value description for %s::%s is missing in source code comment block." % (symbol, field_name))
1723 if missing_parameters != '':
1724 missing_parameters += ", " + field_name
1725 else:
1726 missing_parameters = field_name
1727 desc += "<entry /><entry />\n"
1728 desc += "</row>\n"
1730 desc += "</tbody></tgroup></informaltable>\n</refsect3>"
1731 for field_name in field_descrs:
1732 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1733 "Value description for %s::%s is not used from source code comment block." % (symbol, field_name))
1734 if unused_parameters != '':
1735 unused_parameters += ", " + field_name
1736 else:
1737 unused_parameters = field_name
1739 # remember missing/unused parameters (needed in tmpl-free build)
1740 if missing_parameters != '' and (symbol not in AllIncompleteSymbols):
1741 AllIncompleteSymbols[symbol] = missing_parameters
1743 if unused_parameters != '' and (symbol not in AllUnusedSymbols):
1744 AllUnusedSymbols[symbol] = unused_parameters
1746 if not found:
1747 if len(fields) > 0:
1748 if symbol not in AllIncompleteSymbols:
1749 AllIncompleteSymbols[symbol] = "<items>"
1750 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1751 "Value descriptions for %s are missing in source code comment block." % symbol)
1753 desc += OutputSymbolTraits(symbol)
1754 desc += "</refsect2>\n"
1755 return (synop, desc)
1758 def OutputVariable(symbol, declaration):
1759 """Returns the synopsis and detailed description of a variable.
1761 Args:
1762 symbol (str): the extern'ed variable.
1763 declaration (str): the declaration of the variable.
1765 Returns:
1766 str: the formated docs
1768 sid = common.CreateValidSGMLID(symbol)
1769 condition = MakeConditionDescription(symbol)
1771 logging.info("ouputing variable: '%s' '%s'", symbol, declaration)
1773 type_output = None
1774 m1 = re.search(
1775 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)
1776 m2 = re.search(
1777 r'\s*((const\s+|signed\s+|unsigned\s+|long\s+|short\s+)*\w+)(\s+\*+|\*+|\s)(\s*)(const\s+)*([A-Za-z]\w*)\s*=', declaration)
1778 if m1:
1779 mod1 = m1.group(1) or ''
1780 ptr = m1.group(3) or ''
1781 space = m1.group(4) or ''
1782 mod2 = m1.group(5) or ''
1783 type_output = "extern %s%s%s%s" % (mod1, ptr, space, mod2)
1784 elif m2:
1785 mod1 = m2.group(1) or ''
1786 ptr = m2.group(3) or ''
1787 space = m2.group(4) or ''
1788 mod2 = m2.group(5) or ''
1789 type_output = '%s%s%s%s' % (mod1, ptr, space, mod2)
1790 else:
1791 type_output = "extern"
1793 synop = "<row><entry role=\"variable_type\">%s</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1794 type_output, sid, symbol)
1796 desc = "<refsect2 id=\"%s\" role=\"variable\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
1798 desc += MakeIndexterms(symbol, sid)
1799 desc += "\n"
1800 desc += OutputSymbolExtraLinks(symbol)
1802 decl_out = CreateValidSGML(declaration)
1803 desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out
1805 desc += MakeDeprecationNote(symbol)
1807 if symbol in SymbolDocs:
1808 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1810 if symbol in SymbolAnnotations:
1811 param_desc = SymbolAnnotations[symbol]
1812 param_annotations = ''
1813 (param_desc, param_annotations) = ExpandAnnotation(symbol, param_desc)
1814 if param_annotations != '':
1815 desc += "\n<para>%s</para>" % param_annotations
1817 desc += OutputSymbolTraits(symbol)
1818 desc += "</refsect2>\n"
1819 return (synop, desc)
1822 def OutputFunction(symbol, declaration, symbol_type):
1823 """Returns the synopsis and detailed description of a function.
1825 Args:
1826 symbol (str): the function.
1827 declaration (str): the declaration of the function.
1829 Returns:
1830 str: the formated docs
1832 sid = common.CreateValidSGMLID(symbol)
1833 condition = MakeConditionDescription(symbol)
1835 # Take out the return type
1836 # $1 $2 $3
1837 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'
1838 m = re.search(regex, declaration)
1839 declaration = re.sub(regex, '', declaration)
1840 type_modifier = m.group(1) or ''
1841 type = m.group(2)
1842 pointer = m.group(3)
1843 pointer = pointer.rstrip()
1844 xref = MakeXRef(type, tagify(type, "returnvalue"))
1845 start = ''
1846 # if (symbol_type == 'USER_FUNCTION')
1847 # start = "typedef "
1850 # We output const rather than G_CONST_RETURN.
1851 type_modifier = re.sub(r'G_CONST_RETURN', 'const', type_modifier)
1852 pointer = re.sub(r'G_CONST_RETURN', 'const', pointer)
1853 pointer = re.sub(r'^\s+', '&#160;', pointer)
1855 ret_type_output = "%s%s%s%s\n" % (start, type_modifier, xref, pointer)
1857 indent_len = len(symbol) + 2
1858 char1 = char2 = char3 = ''
1859 if symbol_type == 'USER_FUNCTION':
1860 indent_len += 3
1861 char1 = "<phrase role=\"c_punctuation\">(</phrase>"
1862 char2 = "*"
1863 char3 = "<phrase role=\"c_punctuation\">)</phrase>"
1865 symbol_output = "%s<link linkend=\"%s\">%s%s</link>%s" % (char1, sid, char2, symbol, char3)
1866 if indent_len < MAX_SYMBOL_FIELD_WIDTH:
1867 symbol_desc_output = "%s%s%s%s " % (char1, char2, symbol, char3)
1868 else:
1869 indent_len = MAX_SYMBOL_FIELD_WIDTH - 8
1870 symbol_desc_output = ('%s%s%s%s\n' % (char1, char2, symbol, char3)) + (' ' * (indent_len - 1))
1872 synop = "<row><entry role=\"function_type\">%s</entry><entry role=\"function_name\">%s&#160;<phrase role=\"c_punctuation\">()</phrase></entry></row>\n" % (
1873 ret_type_output, symbol_output)
1875 desc = "<refsect2 id=\"%s\" role=\"function\"%s>\n<title>%s&#160;()</title>\n" % (sid, condition, symbol)
1877 desc += MakeIndexterms(symbol, sid)
1878 desc += "\n"
1879 desc += OutputSymbolExtraLinks(symbol)
1881 desc += "<programlisting language=\"C\">%s%s(" % (ret_type_output, symbol_desc_output)
1883 def tagfun(*args):
1884 return tagify(args[0], "parameter")
1886 fields = common.ParseFunctionDeclaration(declaration, MakeXRef, tagfun)
1888 first = True
1889 for field_name in fields.values():
1890 if first:
1891 desc += field_name
1892 first = False
1893 else:
1894 desc += ",\n" + (' ' * indent_len) + field_name
1896 desc += ");</programlisting>\n"
1898 desc += MakeDeprecationNote(symbol)
1900 if symbol in SymbolDocs:
1901 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1903 if symbol in SymbolAnnotations:
1904 param_desc = SymbolAnnotations[symbol]
1905 (param_desc, param_annotations) = ExpandAnnotation(symbol, param_desc)
1906 if param_annotations != '':
1907 desc += "\n<para>%s</para>" % param_annotations
1909 desc += OutputParamDescriptions("FUNCTION", symbol, iterkeys(fields))
1910 desc += OutputSymbolTraits(symbol)
1911 desc += "</refsect2>\n"
1912 return (synop, desc)
1915 def OutputParamDescriptions(symbol_type, symbol, fields):
1916 """Returns the DocBook output describing the parameters of a symbol.
1918 This can be used for functions, macros or signal handlers.
1920 Args:
1921 symbol_type (str): 'FUNCTION', 'MACRO' or 'SIGNAL'. Signal
1922 handlers have an implicit user_data parameter last.
1923 symbol (str): the name of the symbol being described.
1924 fields (list): parsed fields from the declaration, used to determine
1925 undocumented/unused entries
1927 Returns:
1928 str: the formated parameter docs
1930 output = ''
1931 num_params = 0
1932 field_descrs = None
1934 if fields:
1935 field_descrs = [f for f in fields if f not in ['void', 'Returns']]
1936 else:
1937 field_descrs = []
1939 params = SymbolParams.get(symbol)
1940 logging.info("param_desc(%s, %s) = %s", symbol_type, symbol, str(params))
1941 # This might be an empty dict, but for SIGNALS we append the user_data docs.
1942 # TODO(ensonic): maybe create that docstring in GetSignals()
1943 if params is not None:
1944 returns = ''
1945 params_desc = ''
1946 missing_parameters = ''
1947 unused_parameters = ''
1949 for param_name, param_desc in iteritems(params):
1950 (param_desc, param_annotations) = ExpandAnnotation(symbol, param_desc)
1951 param_desc = ConvertMarkDown(symbol, param_desc)
1952 # trim
1953 param_desc = re.sub(r'^(\s|\n)+', '', param_desc, flags=re.M | re.S)
1954 param_desc = re.sub(r'(\s|\n)+$', '', param_desc, flags=re.M | re.S)
1955 if param_name == "Returns":
1956 returns = param_desc
1957 if param_annotations != '':
1958 returns += "\n<para>%s</para>" % param_annotations
1960 elif param_name == "void":
1961 # FIXME: &common.LogWarning()?
1962 logging.info("!!!! void in params for %s?\n", symbol)
1963 else:
1964 if fields:
1965 if param_name not in field_descrs:
1966 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1967 "Parameter description for %s::%s is not used from source code comment block." % (symbol, param_name))
1968 if unused_parameters != '':
1969 unused_parameters += ", " + param_name
1970 else:
1971 unused_parameters = param_name
1972 else:
1973 field_descrs.remove(param_name)
1975 if param_desc != '':
1976 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" % (
1977 param_name, param_desc, param_annotations)
1978 num_params += 1
1980 for param_name in field_descrs:
1981 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
1982 "Parameter description for %s::%s is missing in source code comment block." % (symbol, param_name))
1983 if missing_parameters != '':
1984 missing_parameters += ", " + param_name
1985 else:
1986 missing_parameters = param_name
1988 # Signals have an implicit user_data parameter which we describe.
1989 if symbol_type == "SIGNAL":
1990 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"
1992 # Start a table if we need one.
1993 if params_desc != '':
1994 sid = common.CreateValidSGMLID("%s.parameters" % symbol)
1996 output += '''<refsect3 id="%s" role="parameters">\n<title>Parameters</title>
1997 <informaltable role="parameters_table" pgwide="1" frame="none">
1998 <tgroup cols="3">
1999 <colspec colname="parameters_name" colwidth="150px"/>
2000 <colspec colname="parameters_description"/>
2001 <colspec colname="parameters_annotations" colwidth="200px"/>
2002 <tbody>
2003 ''' % sid
2004 output += params_desc
2005 output += "</tbody></tgroup></informaltable>\n</refsect3>"
2007 # Output the returns info last
2008 if returns != '':
2009 sid = common.CreateValidSGMLID("%s.returns" % symbol)
2011 output += '''<refsect3 id="%s" role=\"returns\">\n<title>Returns</title>
2012 ''' % sid
2013 output += returns
2014 output += "\n</refsect3>"
2016 # remember missing/unused parameters (needed in tmpl-free build)
2017 if missing_parameters != '' and (symbol not in AllIncompleteSymbols):
2018 AllIncompleteSymbols[symbol] = missing_parameters
2020 if unused_parameters != '' and (symbol not in AllUnusedSymbols):
2021 AllUnusedSymbols[symbol] = unused_parameters
2023 if num_params == 0 and fields and field_descrs:
2024 if symbol not in AllIncompleteSymbols:
2025 AllIncompleteSymbols[symbol] = "<parameters>"
2026 return output
2029 def ParseStabilityLevel(stability, file, line, message):
2030 """Parses a stability level and outputs a warning if it isn't valid.
2031 Args:
2032 stability (str): the stability text.
2033 file, line: context for error message
2034 message: description of where the level is from, to use in any error message.
2035 Returns:
2036 str: the parsed stability level string.
2038 stability = stability.strip()
2039 sl = stability.strip().lower()
2040 if sl == 'stable':
2041 stability = "Stable"
2042 elif sl == 'unstable':
2043 stability = "Unstable"
2044 elif sl == 'private':
2045 stability = "Private"
2046 else:
2047 common.LogWarning(file, line,
2048 "%s is %s. It should be one of these: Stable, "
2049 "Unstable, or Private." % (
2050 message, stability))
2051 return str(stability)
2054 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):
2055 """Outputs the final DocBook file for one section.
2057 Args:
2058 file (str): the name of the file.
2059 title (str): the title from the $MODULE-sections.txt file
2060 section_id (str): the id to use for the toplevel tag.
2061 includes (str): comma-separates list of include files added at top of
2062 synopsis, with '<' '>' around them (if not already enclosed in '').
2063 functions_synop (str): the DocBook for the Functions Synopsis part.
2064 other_synop (str): the DocBook for the Types and Values Synopsis part.
2065 functions_details (str): the DocBook for the Functions Details part.
2066 other_details (str): the DocBook for the Types and Values Details part.
2067 signal_synop (str): the DocBook for the Signal Synopsis part
2068 signal_desc (str): the DocBook for the Signal Description part
2069 args_synop (str): the DocBook for the Arg Synopsis part
2070 args_desc (str): the DocBook for the Arg Description part
2071 hierarchy (str): the DocBook for the Object Hierarchy part
2072 interfaces (str): the DocBook for the Interfaces part
2073 implementations (str): the DocBook for the Known Implementations part
2074 prerequisites (str): the DocBook for the Prerequisites part
2075 derived (str): the DocBook for the Derived Interfaces part
2076 file_objects (list): objects in this file
2078 Returns:
2079 bool: True if the docs where updated
2082 logging.info("Output docbook for file %s with title '%s'", file, title)
2084 # The edited title overrides the one from the sections file.
2085 new_title = SymbolDocs.get(file + ":Title")
2086 if new_title and not new_title.strip() == '':
2087 title = new_title
2088 logging.info("Found title: %s", title)
2090 short_desc = SymbolDocs.get(file + ":Short_Description")
2091 if not short_desc or short_desc.strip() == '':
2092 short_desc = ''
2093 else:
2094 # Don't use ConvertMarkDown here for now since we don't want blocks
2095 short_desc = ExpandAbbreviations(title + ":Short_description", short_desc)
2096 logging.info("Found short_desc: %s", short_desc)
2098 long_desc = SymbolDocs.get(file + ":Long_Description")
2099 if not long_desc or long_desc.strip() == '':
2100 long_desc = ''
2101 else:
2102 long_desc = ConvertMarkDown(title + ":Long_description", long_desc)
2103 logging.info("Found long_desc: %s", long_desc)
2105 see_also = SymbolDocs.get(file + ":See_Also")
2106 if not see_also or re.search(r'^\s*(<para>)?\s*(</para>)?\s*$', see_also):
2107 see_also = ''
2108 else:
2109 see_also = ConvertMarkDown(title + ":See_Also", see_also)
2110 logging.info("Found see_also: %s", see_also)
2112 if see_also:
2113 see_also = "<refsect1 id=\"%s.see-also\">\n<title>See Also</title>\n%s\n</refsect1>\n" % (section_id, see_also)
2115 stability = SymbolDocs.get(file + ":Stability_Level")
2116 if not stability or re.search(r'^\s*$', stability):
2117 stability = ''
2118 else:
2119 line_number = GetSymbolSourceLine(file + ":Stability_Level")
2120 stability = ParseStabilityLevel(stability, file, line_number, "Section stability level")
2121 logging.info("Found stability: %s", stability)
2123 if not stability:
2124 stability = DEFAULT_STABILITY or ''
2126 if stability:
2127 if stability in AnnotationDefinition:
2128 AnnotationsUsed[stability] = True
2129 stability = "<acronym>%s</acronym>" % stability
2130 stability = "<refsect1 id=\"%s.stability-level\">\n<title>Stability Level</title>\n%s, unless otherwise indicated\n</refsect1>\n" % (
2131 section_id, stability)
2133 image = SymbolDocs.get(file + ":Image")
2134 if not image or re.search(r'^\s*$', image):
2135 image = ''
2136 else:
2137 image = image.strip()
2139 format = None
2141 il = image.lower()
2142 if re.search(r'jpe?g$', il):
2143 format = "format='JPEG'"
2144 elif il.endswith('png'):
2145 format = "format='PNG'"
2146 elif il.endswith('svg'):
2147 format = "format='SVG'"
2148 else:
2149 format = ''
2151 image = " <inlinegraphic fileref='%s' %s/>\n" % (image, format)
2153 include_output = ''
2154 if includes:
2155 include_output += "<refsect1 id=\"%s.includes\"><title>Includes</title><synopsis>" % section_id
2156 for include in includes.split(','):
2157 if re.search(r'^\".+\"$', include):
2158 include_output += "#include %s\n" % include
2159 else:
2160 include = re.sub(r'^\s+|\s+$', '', include, flags=re.S)
2161 include_output += "#include &lt;%s&gt;\n" % include
2163 include_output += "</synopsis></refsect1>\n"
2165 extralinks = OutputSectionExtraLinks(title, "Section:%s" % file)
2167 old_db_file = os.path.join(DB_OUTPUT_DIR, file + '.xml')
2168 new_db_file = os.path.join(DB_OUTPUT_DIR, file + '.xml.new')
2170 OUTPUT = common.open_text(new_db_file, 'w')
2172 object_anchors = ''
2173 for fobject in file_objects:
2174 if fobject == section_id:
2175 continue
2176 sid = common.CreateValidSGMLID(fobject)
2177 logging.info("Adding anchor for %s\n", fobject)
2178 object_anchors += "<anchor id=\"%s\"/>" % sid
2180 # Make sure we produce valid docbook
2181 if not functions_details:
2182 functions_details = "<para />"
2184 # We used to output this, but is messes up our common.UpdateFileIfChanged code
2185 # since it changes every day (and it is only used in the man pages):
2186 # "<refentry id="$section_id" revision="$mday $month $year">"
2188 OUTPUT.write(REFENTRY.substitute({
2189 'args_desc': args_desc,
2190 'args_synop': args_synop,
2191 'derived': derived,
2192 'extralinks': extralinks,
2193 'functions_details': functions_details,
2194 'functions_synop': functions_synop,
2195 'header': MakeDocHeader('refentry'),
2196 'hierarchy': hierarchy,
2197 'image': image,
2198 'include_output': include_output,
2199 'interfaces': interfaces,
2200 'implementations': implementations,
2201 'long_desc': long_desc,
2202 'object_anchors': object_anchors,
2203 'other_details': other_details,
2204 'other_synop': other_synop,
2205 'prerequisites': prerequisites,
2206 'section_id': section_id,
2207 'see_also': see_also,
2208 'signals_desc': signals_desc,
2209 'signals_synop': signals_synop,
2210 'short_desc': short_desc,
2211 'stability': stability,
2212 'title': title,
2213 'MODULE': MODULE.upper(),
2215 OUTPUT.close()
2217 return common.UpdateFileIfChanged(old_db_file, new_db_file, 0)
2220 def OutputProgramDBFile(program, section_id):
2221 """Outputs the final DocBook file for one program.
2223 Args:
2224 file (str): the name of the file.
2225 section_id (str): the id to use for the toplevel tag.
2227 Returns:
2228 bool: True if the docs where updated
2230 logging.info("Output program docbook for %s", program)
2232 short_desc = SourceSymbolDocs.get(program + ":Short_Description")
2233 if not short_desc or short_desc.strip() == '':
2234 short_desc = ''
2235 else:
2236 # Don't use ConvertMarkDown here for now since we don't want blocks
2237 short_desc = ExpandAbbreviations(program, short_desc)
2238 logging.info("Found short_desc: %s", short_desc)
2240 synopsis = SourceSymbolDocs.get(program + ":Synopsis")
2241 if synopsis and synopsis.strip() != '':
2242 items = synopsis.split(' ')
2243 for i in range(0, len(items)):
2244 parameter = items[i]
2245 choice = "plain"
2246 rep = ''
2248 # first parameter is the command name
2249 if i == 0:
2250 synopsis = "<command>%s</command>\n" % parameter
2251 continue
2253 # square brackets indicate optional parameters, curly brackets
2254 # indicate required parameters ("plain" parameters are also
2255 # mandatory, but do not get extra decoration)
2256 m1 = re.search(r'^\[(.+?)\]$', parameter)
2257 m2 = re.search(r'^\{(.+?)\}$', parameter)
2258 if m1:
2259 choice = "opt"
2260 parameter = m1.group(1)
2261 elif m2:
2262 choice = "req"
2263 parameter = m2.group(1)
2265 # parameters ending in "..." are repeatable
2266 if parameter.endswith('...'):
2267 rep = ' rep=\"repeat\"'
2268 parameter = parameter[:-3]
2270 # italic parameters are replaceable parameters
2271 parameter = re.sub(r'\*(.+?)\*', r'<replaceable>\1</replaceable>', parameter)
2273 synopsis += "<arg choice=\"%s\"%s>" % (choice, rep)
2274 synopsis += parameter
2275 synopsis += "</arg>\n"
2277 logging.info("Found synopsis: %s", synopsis)
2278 else:
2279 synopsis = "<command>%s</command>" % program
2281 long_desc = SourceSymbolDocs.get(program + ":Long_Description")
2282 if not long_desc or long_desc.strip() == '':
2283 long_desc = ''
2284 else:
2285 long_desc = ConvertMarkDown("%s:Long_description" % program, long_desc)
2286 logging.info("Found long_desc: %s", long_desc)
2288 options = ''
2289 o = program + ":Options"
2290 if o in SourceSymbolDocs:
2291 opts = SourceSymbolDocs[o].split('\t')
2293 logging.info('options: %d, %s', len(opts), str(opts))
2295 options = "<refsect1>\n<title>Options</title>\n<variablelist>\n"
2296 for k in range(0, len(opts), 2):
2297 opt_desc = opts[k + 1]
2299 opt_desc = re.sub(r'\*(.+?)\*', r'<replaceable>\1</replaceable>', opt_desc)
2301 options += "<varlistentry>\n<term>"
2302 opt_names = opts[k].split(',')
2303 for i in range(len(opt_names)):
2304 prefix = ', ' if i > 0 else ''
2305 # italic parameters are replaceable parameters
2306 opt_name = re.sub(r'\*(.+?)\*', r'<replaceable>\1</replaceable>', opt_names[i])
2308 options += "%s<option>%s</option>\n" % (prefix, opt_name)
2310 options += "</term>\n"
2311 options += "<listitem><para>%s</para></listitem>\n" % opt_desc
2312 options += "</varlistentry>\n"
2314 options += "</variablelist></refsect1>\n"
2316 exit_status = SourceSymbolDocs.get(program + ":Returns")
2317 if exit_status and exit_status != '':
2318 exit_status = ConvertMarkDown("%s:Returns" % program, exit_status)
2319 exit_status = "<refsect1 id=\"%s.exit-status\">\n<title>Exit Status</title>\n%s\n</refsect1>\n" % (
2320 section_id, exit_status)
2321 else:
2322 exit_status = ''
2324 see_also = SourceSymbolDocs.get(program + ":See_Also")
2325 if not see_also or re.search(r'^\s*(<para>)?\s*(</para>)?\s*$', see_also):
2326 see_also = ''
2327 else:
2328 see_also = ConvertMarkDown("%s:See_Also" % program, see_also)
2329 logging.info("Found see_also: %s", see_also)
2331 if see_also:
2332 see_also = "<refsect1 id=\"%s.see-also\">\n<title>See Also</title>\n%s\n</refsect1>\n" % (section_id, see_also)
2334 old_db_file = os.path.join(DB_OUTPUT_DIR, program + ".xml")
2335 new_db_file = os.path.join(DB_OUTPUT_DIR, program + ".xml.new")
2337 OUTPUT = common.open_text(new_db_file, 'w')
2339 OUTPUT.write('''%s
2340 <refentry id="%s">
2341 <refmeta>
2342 <refentrytitle role="top_of_page" id="%s.top_of_page">%s</refentrytitle>
2343 <manvolnum>1</manvolnum>
2344 <refmiscinfo>User Commands</refmiscinfo>
2345 </refmeta>
2346 <refnamediv>
2347 <refname>%s</refname>
2348 <refpurpose>%s</refpurpose>
2349 </refnamediv>
2350 <refsynopsisdiv>
2351 <cmdsynopsis>%s</cmdsynopsis>
2352 </refsynopsisdiv>
2353 <refsect1 id="%s.description" role="desc">
2354 <title role="desc.title">Description</title>
2356 </refsect1>
2357 %s%s%s
2358 </refentry>
2359 ''' % (MakeDocHeader("refentry"), section_id, section_id, program, program, short_desc, synopsis, section_id, long_desc, options, exit_status, see_also))
2360 OUTPUT.close()
2362 return common.UpdateFileIfChanged(old_db_file, new_db_file, 0)
2365 def OutputExtraFile(file):
2366 """Copies an "extra" DocBook file into the output directory, expanding abbreviations.
2368 Args:
2369 file (str): the source file.
2371 Returns:
2372 bool: True if the docs where updated
2375 basename = os.path.basename(file)
2377 old_db_file = os.path.join(DB_OUTPUT_DIR, basename)
2378 new_db_file = os.path.join(DB_OUTPUT_DIR, basename + ".new")
2380 contents = common.open_text(file).read()
2382 OUTPUT = common.open_text(new_db_file, 'w')
2383 OUTPUT.write(ExpandAbbreviations(basename + " file", contents))
2384 OUTPUT.close()
2386 return common.UpdateFileIfChanged(old_db_file, new_db_file, 0)
2389 def GetDocbookHeader(main_file):
2390 if os.path.exists(main_file):
2391 INPUT = common.open_text(main_file)
2392 header = ''
2393 for line in INPUT:
2394 if re.search(r'^\s*<(book|chapter|article)', line):
2395 # check that the top-level tagSYSTEM or the doctype decl contain the xinclude namespace decl
2396 if not re.search(r'http:\/\/www.w3.org\/200[13]\/XInclude', line) and \
2397 not re.search(r'http:\/\/www.w3.org\/200[13]\/XInclude', header, flags=re.MULTILINE):
2398 header = ''
2399 break
2401 # if there are SYSTEM ENTITIES here, we should prepend "../" to the path
2402 # FIXME: not sure if we can do this now, as people already work-around the problem
2403 # r'#<!ENTITY % ([a-zA-Z-]+) SYSTEM \"([^/][a-zA-Z./]+)\">', r'<!ENTITY % \1 SYSTEM \"../\2\">';
2404 line = re.sub(
2405 r'<!ENTITY % gtkdocentities SYSTEM "([^"]*)">', r'<!ENTITY % gtkdocentities SYSTEM "../\1">', line)
2406 header += line
2407 INPUT.close()
2408 header = header.strip()
2409 else:
2410 header = '''<?xml version="1.0"?>
2411 <!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
2412 "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
2414 <!ENTITY % local.common.attrib "xmlns:xi CDATA #FIXED 'http://www.w3.org/2003/XInclude'">
2415 <!ENTITY % gtkdocentities SYSTEM "../xml/gtkdocentities.ent">
2416 %gtkdocentities;
2417 ]>'''
2418 return header
2421 def OutputBook(main_file, book_top, book_bottom):
2422 """Outputs the entities that need to be included into the main docbook file for the module.
2424 Args:
2425 book_top (str): the declarations of the entities, which are added
2426 at the top of the main docbook file.
2427 book_bottom (str): the entities, which are added in the main docbook
2428 file at the desired position.
2431 old_file = os.path.join(DB_OUTPUT_DIR, MODULE + "-doc.top")
2432 new_file = os.path.join(DB_OUTPUT_DIR, MODULE + "-doc.top.new")
2434 OUTPUT = common.open_text(new_file, 'w')
2435 OUTPUT.write(book_top)
2436 OUTPUT.close()
2438 common.UpdateFileIfChanged(old_file, new_file, 0)
2440 old_file = os.path.join(DB_OUTPUT_DIR, MODULE + "-doc.bottom")
2441 new_file = os.path.join(DB_OUTPUT_DIR, MODULE + "-doc.bottom.new")
2443 OUTPUT = common.open_text(new_file, 'w')
2444 OUTPUT.write(book_bottom)
2445 OUTPUT.close()
2447 common.UpdateFileIfChanged(old_file, new_file, 0)
2449 # If the main docbook file hasn't been created yet, we create it here.
2450 # The user can tweak it later.
2451 if main_file and not os.path.exists(main_file):
2452 OUTPUT = common.open_text(main_file, 'w')
2454 logging.info("no master doc, create default one at: " + main_file)
2456 OUTPUT.write('''%s
2457 <book id="index">
2458 <bookinfo>
2459 <title>&package_name; Reference Manual</title>
2460 <releaseinfo>
2461 for &package_string;.
2462 The latest version of this documentation can be found on-line at
2463 <ulink role="online-location" url="http://[SERVER]/&package_name;/index.html">http://[SERVER]/&package_name;/</ulink>.
2464 </releaseinfo>
2465 </bookinfo>
2467 <chapter>
2468 <title>[Insert title here]</title>
2470 </chapter>
2471 ''' % (MakeDocHeader("book"), book_bottom))
2472 if os.path.exists('xml/tree_index.sgml'):
2473 OUTPUT.write(''' <chapter id="object-tree">
2474 <title>Object Hierarchy</title>
2475 <xi:include href="xml/tree_index.sgml"/>
2476 </chapter>
2477 ''')
2478 else:
2479 OUTPUT.write(''' <!-- enable this when you use gobject types
2480 <chapter id="object-tree">
2481 <title>Object Hierarchy</title>
2482 <xi:include href="xml/tree_index.sgml"/>
2483 </chapter>
2485 ''')
2487 OUTPUT.write(''' <index id="api-index-full">
2488 <title>API Index</title>
2489 <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
2490 </index>
2491 <index id="deprecated-api-index" role="deprecated">
2492 <title>Index of deprecated API</title>
2493 <xi:include href="xml/api-index-deprecated.xml"><xi:fallback /></xi:include>
2494 </index>
2495 ''')
2496 for version in set(Since.values()):
2497 dash_version = version.replace('.', '-')
2498 OUTPUT.write(''' <index id="api-index-%s" role="%s">
2499 <title>Index of new API in %s</title>
2500 <xi:include href="xml/api-index-%s.xml"><xi:fallback /></xi:include>
2501 </index>
2502 ''' % (dash_version, version, version, version))
2504 if AnnotationsUsed:
2505 OUTPUT.write(''' <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
2506 ''')
2507 else:
2508 OUTPUT.write(''' <!-- enable this when you use gobject introspection annotations
2509 <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
2511 ''')
2513 OUTPUT.write('''</book>
2514 ''')
2516 OUTPUT.close()
2519 def CreateValidSGML(text):
2520 """Turn any chars which are used in XML into entities.
2522 e.g. '<' into '&lt;'
2524 Args:
2525 text (str): the text to turn into proper XML.
2527 Returns:
2528 str: escaped input
2531 text = text.replace('&', '&amp;') # Do this first, or the others get messed up.
2532 text = text.replace('<', '&lt;')
2533 text = text.replace('>', '&gt;')
2534 # browsers render single tabs inconsistently
2535 text = re.sub(r'([^\s])\t([^\s])', r'\1&#160;\2', text)
2536 return text
2539 def ConvertSGMLChars(symbol, text):
2540 """Escape XML chars.
2542 This is used for text in source code comment blocks, to turn
2543 chars which are used in XML into entities, e.g. '<' into
2544 &lt;'. Depending on INLINE_MARKUP_MODE, this is done
2545 unconditionally or only if the character doesn't seem to be
2546 part of an XML construct (tag or entity reference).
2547 Args:
2548 text (str): the text to turn into proper XML.
2550 Returns:
2551 str: escaped input
2554 if INLINE_MARKUP_MODE:
2555 # For the XML/SGML mode only convert to entities outside CDATA sections.
2556 return ModifyXMLElements(text, symbol,
2557 "<!\\[CDATA\\[|<programlisting[^>]*>",
2558 ConvertSGMLCharsEndTag,
2559 ConvertSGMLCharsCallback)
2560 # For the simple non-sgml mode, convert to entities everywhere.
2562 text = re.sub(r'&(?![a-zA-Z#]+;)', r'&amp;', text) # Do this first, or the others get messed up.
2563 text = re.sub(r'<', r'&lt;', text)
2564 # Allow '>' at beginning of string for blockquote markdown
2565 text = re.sub(r'''(?<=[^\w\n"'\/-])>''', r'&gt;', text)
2567 return text
2570 def ConvertSGMLCharsEndTag(start_tag):
2571 if start_tag == '<![CDATA[':
2572 return "]]>"
2573 return "</programlisting>"
2576 def ConvertSGMLCharsCallback(text, symbol, tag):
2577 if re.search(r'^<programlisting', tag):
2578 logging.debug('call modifyXML')
2579 # We can handle <programlisting> specially here.
2580 return ModifyXMLElements(text, symbol,
2581 "<!\\[CDATA\\[",
2582 ConvertSGMLCharsEndTag,
2583 ConvertSGMLCharsCallback2)
2584 elif tag == '':
2585 logging.debug('replace entities')
2586 # If we're not in CDATA convert to entities.
2587 text = re.sub(r'&(?![a-zA-Z#]+;)', r'&amp;', text) # Do this first, or the others get messed up.
2588 text = re.sub(r'<(?![a-zA-Z\/!])', r'&lt;', text)
2589 # Allow '>' at beginning of string for blockquote markdown
2590 text = re.sub(r'''(?<=[^\w\n"'\/-])>''', r'&gt;', text)
2592 # Handle "#include <xxxxx>"
2593 text = re.sub(r'#include(\s+)<([^>]+)>', r'#include\1&lt;\2&gt;', text)
2595 return text
2598 def ConvertSGMLCharsCallback2(text, symbol, tag):
2599 # If we're not in CDATA convert to entities.
2600 # We could handle <programlisting> differently, though I'm not sure it helps.
2601 if tag == '':
2602 # replace only if its not a tag
2603 text = re.sub(r'&(?![a-zA-Z#]+;)', r'&amp;', text) # Do this first, or the others get messed up.
2604 text = re.sub(r'<(?![a-zA-Z\/!])', r'&lt;', text)
2605 text = re.sub(r'''(?<![a-zA-Z0-9"'\/-])>''', r'&gt;', text)
2606 # Handle "#include <xxxxx>"
2607 text = re.sub(r'/#include(\s+)<([^>]+)>', r'#include\1&lt;\2&gt;', text)
2609 return text
2612 def ExpandAnnotation(symbol, param_desc):
2613 """This turns annotations into acronym tags.
2614 Args:
2615 symbol (str): the symbol being documented, for error messages.
2616 param_desc (str): the text to expand.
2618 Returns:
2619 str: the remaining param_desc
2620 str: the formatted annotations
2622 param_annotations = ''
2624 # look for annotations at the start of the comment part
2625 # function level annotations don't end with a colon ':'
2626 m = re.search(r'^\s*\((.*?)\)(:|$)', param_desc)
2627 if m:
2628 param_desc = param_desc[m.end():]
2630 annotations = re.split(r'\)\s*\(', m.group(1))
2631 logging.info("annotations for %s: '%s'\n", symbol, m.group(1))
2632 for annotation in annotations:
2633 # need to search for the longest key-match in %AnnotationDefinition
2634 match_length = 0
2635 match_annotation = ''
2637 for annotationdef in AnnotationDefinition:
2638 if annotation.startswith(annotationdef):
2639 if len(annotationdef) > match_length:
2640 match_length = len(annotationdef)
2641 match_annotation = annotationdef
2643 annotation_extra = ''
2644 if match_annotation != '':
2645 m = re.search(match_annotation + r'\s+(.*)', annotation)
2646 if m:
2647 annotation_extra = " " + m.group(1)
2649 AnnotationsUsed[match_annotation] = 1
2650 param_annotations += "[<acronym>%s</acronym>%s]" % (match_annotation, annotation_extra)
2651 else:
2652 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
2653 "unknown annotation \"%s\" in documentation for %s." % (annotation, symbol))
2654 param_annotations += "[%s]" % annotation
2656 param_desc = param_desc.strip()
2657 m = re.search(r'^(.*?)\.*\s*$', param_desc, flags=re.S)
2658 param_desc = m.group(1) + '. '
2660 if param_annotations != '':
2661 param_annotations = "<emphasis role=\"annotation\">%s</emphasis>" % param_annotations
2663 return (param_desc, param_annotations)
2666 def ExpandAbbreviations(symbol, text):
2667 """Expand the shortcut notation for symbol references.
2669 This turns the abbreviations function(), macro(), @param, %constant, and #symbol
2670 into appropriate DocBook markup. CDATA sections and <programlisting> parts
2671 are skipped.
2673 Args:
2674 symbol (str): the symbol being documented, for error messages.
2675 text (str): the text to expand.
2677 Returns:
2678 str: the expanded text
2680 # Note: This is a fallback and normally done in the markdown parser
2682 logging.debug('expand abbreviations for "%s", text: [%s]', symbol, text)
2683 m = re.search(r'\|\[[^\n]*\n(.*)\]\|', text, flags=re.M | re.S)
2684 if m:
2685 logging.debug('replaced entities in code block')
2686 text = text[:m.start(1)] + md_to_db.ReplaceEntities(m.group(1)) + text[m.end(1):]
2688 # Convert "|[" and "]|" into the start and end of program listing examples.
2689 # Support \[<!-- language="C" --> modifiers
2690 text = re.sub(r'\|\[<!-- language="([^"]+)" -->', r'<informalexample><programlisting language="\1"><![CDATA[', text)
2691 text = re.sub(r'\|\[', r'<informalexample><programlisting><![CDATA[', text)
2692 text = re.sub(r'\]\|', r']]></programlisting></informalexample>', text)
2694 # keep CDATA unmodified, preserve ulink tags (ideally we preseve all tags
2695 # as such)
2696 return ModifyXMLElements(text, symbol,
2697 "<!\\[CDATA\\[|<ulink[^>]*>|<programlisting[^>]*>|<!DOCTYPE",
2698 ExpandAbbreviationsEndTag,
2699 ExpandAbbreviationsCallback)
2702 def ExpandAbbreviationsEndTag(start_tag):
2703 # Returns the end tag (as a regexp) corresponding to the given start tag.
2704 if start_tag == r'<!\[CDATA\[':
2705 return "]]>"
2706 if start_tag == "<!DOCTYPE":
2707 return '>'
2708 m = re.search(r'<(\w+)', start_tag)
2709 if m:
2710 return "</%s>" % m.group(1)
2712 logging.warning('no end tag for "%s"', start_tag)
2713 return ''
2716 def ExpandAbbreviationsCallback(text, symbol, tag):
2717 # Called inside or outside each CDATA or <programlisting> section.
2718 if tag.startswith(r'^<programlisting'):
2719 # Handle any embedded CDATA sections.
2720 return ModifyXMLElements(text, symbol,
2721 "<!\\[CDATA\\[",
2722 ExpandAbbreviationsEndTag,
2723 ExpandAbbreviationsCallback2)
2724 elif tag == '':
2725 # NOTE: this is a fallback. It is normally done by the Markdown parser.
2726 # but is also used for OutputExtraFile
2728 # We are outside any CDATA or <programlisting> sections, so we expand
2729 # any gtk-doc abbreviations.
2731 # Convert '@param()'
2732 # FIXME: we could make those also links ($symbol.$2), but that would be less
2733 # useful as the link target is a few lines up or down
2734 text = re.sub(r'(\A|[^\\])\@(\w+((\.|->)\w+)*)\s*\(\)', r'\1<parameter>\2()</parameter>', text)
2736 # Convert 'function()' or 'macro()'.
2737 # if there is abc_*_def() we don't want to make a link to _def()
2738 # FIXME: also handle abc(def(....)) : but that would need to be done recursively :/
2739 def f1(m):
2740 return m.group(1) + MakeXRef(m.group(2), tagify(m.group(2) + "()", "function"))
2741 text = re.sub(r'([^\*.\w])(\w+)\s*\(\)', f1, text)
2742 # handle #Object.func()
2743 text = re.sub(r'(\A|[^\\])#([\w\-:\.]+[\w]+)\s*\(\)', f1, text)
2745 # Convert '@param', but not '\@param'.
2746 text = re.sub(r'(\A|[^\\])\@(\w+((\.|->)\w+)*)', r'\1<parameter>\2</parameter>', text)
2747 text = re.sub(r'/\\\@', r'\@', text)
2749 # Convert '%constant', but not '\%constant'.
2750 # Also allow negative numbers, e.g. %-1.
2751 def f2(m):
2752 return m.group(1) + MakeXRef(m.group(2), tagify(m.group(2), "literal"))
2753 text = re.sub(r'(\A|[^\\])\%(-?\w+)', f2, text)
2754 text = re.sub(r'\\\%', r'\%', text)
2756 # Convert '#symbol', but not '\#symbol'.
2757 def f3(m):
2758 return m.group(1) + MakeHashXRef(m.group(2), "type")
2759 text = re.sub(r'(\A|[^\\])#([\w\-:\.]+[\w]+)', f3, text)
2760 text = re.sub(r'\\#', '#', text)
2762 return text
2765 def ExpandAbbreviationsCallback2(text, symbol, tag):
2766 # This is called inside a <programlisting>
2767 if tag == '':
2768 # We are inside a <programlisting> but outside any CDATA sections,
2769 # so we expand any gtk-doc abbreviations.
2770 # FIXME: why is this different from &ExpandAbbreviationsCallback(),
2771 # why not just call it
2772 text = re.sub(r'#(\w+)', lambda m: '%s;' % MakeHashXRef(m.group(1), ''), text)
2773 elif tag == "<![CDATA[":
2774 # NOTE: this is a fallback. It is normally done by the Markdown parser.
2775 text = ReplaceEntities(text, symbol)
2777 return text
2780 def MakeHashXRef(symbol, tag):
2781 text = symbol
2783 # Check for things like '#include', '#define', and skip them.
2784 if symbol in PreProcessorDirectives:
2785 return "#%s" % symbol
2787 # Get rid of special suffixes ('-struct','-enum').
2788 text = re.sub(r'-struct$', '', text)
2789 text = re.sub(r'-enum$', '', text)
2791 # If the symbol is in the form "Object::signal", then change the symbol to
2792 # "Object-signal" and use "signal" as the text.
2793 if '::' in symbol:
2794 o, s = symbol.split('::', 1)
2795 symbol = '%s-%s' % (o, s)
2796 text = u'“' + s + u'”'
2798 # If the symbol is in the form "Object:property", then change the symbol to
2799 # "Object--property" and use "property" as the text.
2800 if ':' in symbol:
2801 o, p = symbol.split(':', 1)
2802 symbol = '%s--%s' % (o, p)
2803 text = u'“' + p + u'”'
2805 if tag != '':
2806 text = tagify(text, tag)
2808 return MakeXRef(symbol, text)
2811 def ModifyXMLElements(text, symbol, start_tag_regexp, end_tag_func, callback):
2812 """Rewrite XML blocks.
2814 Looks for given XML element tags within the text, and calls
2815 the callback on pieces of text inside & outside those elements.
2816 Used for special handling of text inside things like CDATA
2817 and <programlisting>.
2819 Args:
2820 text (str): the text.
2821 symbol (str): the symbol currently being documented (only used for
2822 error messages).
2823 start_tag_regexp (str): the regular expression to match start tags.
2824 e.g. "<!\\[CDATA\\[|<programlisting[^>]*>" to
2825 match CDATA sections or programlisting elements.
2826 end_tag_func (func): function which is passed the matched start tag
2827 and should return the appropriate end tag string
2828 regexp.
2829 callback - callback called with each part of the text. It is
2830 called with a piece of text, the symbol being
2831 documented, and the matched start tag or '' if the text
2832 is outside the XML elements being matched.
2834 Returns:
2835 str: modified text
2837 before_tag = start_tag = end_tag_regexp = end_tag = None
2838 result = ''
2840 logging.debug('modify xml for symbol: %s, regex: %s, text: [%s]', symbol, start_tag_regexp, text)
2842 m = re.search(start_tag_regexp, text, flags=re.S)
2843 while m:
2844 before_tag = text[:m.start()] # Prematch for last successful match string
2845 start_tag = m.group(0) # Last successful match
2846 text = text[m.end():] # Postmatch for last successful match string
2847 # get the matching end-tag for current tag
2848 end_tag_regexp = end_tag_func(start_tag)
2850 logging.debug('symbol: %s matched start: %s, end_tag: %s, text: [%s]', symbol, start_tag, end_tag_regexp, text)
2852 logging.debug('converting before tag: [%s]', before_tag)
2853 result += callback(before_tag, symbol, '')
2854 result += start_tag
2856 m2 = re.search(end_tag_regexp, text, flags=re.S)
2857 if m2:
2858 before_tag = text[:m2.start()]
2859 end_tag = m2.group(0)
2860 text = text[m2.end():]
2862 logging.debug('symbol: %s matched end %s: text: [%s]', symbol, end_tag, text)
2864 result += callback(before_tag, symbol, start_tag)
2865 result += end_tag
2866 else:
2867 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
2868 "Can't find tag end: %s in docs for: %s." % (end_tag_regexp, symbol))
2869 # Just assume it is all inside the tag.
2870 result += callback(text, symbol, start_tag)
2871 text = ''
2872 m = re.search(start_tag_regexp, text, flags=re.S)
2874 # Handle any remaining text outside the tags.
2875 logging.debug('converting after tag: [%s]', text)
2876 result += callback(text, symbol, '')
2877 logging.debug('results for symbol: %s, text: [%s]', symbol, result)
2879 return result
2882 def tagify(text, elem):
2883 # Adds a tag around some text.
2884 # e.g tagify("Text", "literal") => "<literal>Text</literal>".
2885 return '<' + elem + '>' + text + '</' + elem + '>'
2888 def MakeDocHeader(tag):
2889 """Builds a docbook header for the given tag.
2891 Args:
2892 tag (str): doctype tag
2894 Returns:
2895 str: the docbook header
2897 header = re.sub(r'<!DOCTYPE \w+', r'<!DOCTYPE ' + tag, doctype_header)
2898 # fix the path for book since this is one level up
2899 if tag == 'book':
2900 header = re.sub(
2901 r'<!ENTITY % gtkdocentities SYSTEM "../([a-zA-Z./]+)">', r'<!ENTITY % gtkdocentities SYSTEM "\1">', header)
2902 return header
2905 def MakeXRef(symbol, text=None):
2906 """This returns a cross-reference link to the given symbol.
2908 Though it doesn't try to do this for a few standard C types that it knows
2909 won't be in the documentation.
2911 Args:
2912 symbol (str): the symbol to try to create a XRef to.
2913 text (str): text to put inside the XRef, defaults to symbol
2915 Returns:
2916 str: a docbook link
2918 symbol = symbol.strip()
2919 if not text:
2920 text = symbol
2922 # Get rid of special suffixes ('-struct','-enum').
2923 text = re.sub(r'-struct$', '', text)
2924 text = re.sub(r'-enum$', '', text)
2926 if ' ' in symbol:
2927 return text
2929 logging.info("Getting type link for %s -> %s", symbol, text)
2931 symbol_id = common.CreateValidSGMLID(symbol)
2932 return "<link linkend=\"%s\">%s</link>" % (symbol_id, text)
2935 def MakeIndexterms(symbol, sid):
2936 """This returns a indexterm elements for the given symbol
2938 Args:
2939 symbol (str): the symbol to create indexterms for
2941 Returns:
2942 str: doxbook index terms
2944 terms = ''
2945 sortas = ''
2947 # make the index useful, by ommiting the namespace when sorting
2948 if NAME_SPACE != '':
2949 m = re.search(r'^%s\_?(.*)' % NAME_SPACE, symbol, flags=re.I)
2950 if m:
2951 sortas = ' sortas="%s"' % m.group(1)
2953 if symbol in Deprecated:
2954 terms += "<indexterm zone=\"%s\" role=\"deprecated\"><primary%s>%s</primary></indexterm>" % (
2955 sid, sortas, symbol)
2956 IndexEntriesDeprecated[symbol] = sid
2957 IndexEntriesFull[symbol] = sid
2958 if symbol in Since:
2959 since = Since[symbol].strip()
2960 if since != '':
2961 terms += "<indexterm zone=\"%s\" role=\"%s\"><primary%s>%s</primary></indexterm>" % (
2962 sid, since, sortas, symbol)
2963 IndexEntriesSince[symbol] = sid
2964 IndexEntriesFull[symbol] = sid
2965 if terms == '':
2966 terms += "<indexterm zone=\"%s\"><primary%s>%s</primary></indexterm>" % (sid, sortas, symbol)
2967 IndexEntriesFull[symbol] = sid
2968 return terms
2971 def MakeDeprecationNote(symbol):
2972 """This returns a deprecation warning for the given symbol.
2974 Args:
2975 symbol (str): the symbol to try to create a warning for.
2977 Returns:
2978 str: formatted warning or empty string if symbol is not deprecated
2980 desc = ''
2981 if symbol in Deprecated:
2982 desc += "<warning><para><literal>%s</literal> " % symbol
2983 note = Deprecated[symbol]
2985 m = re.search(r'^\s*([0-9\.]+)\s*:?', note)
2986 if m:
2987 desc += "has been deprecated since version %s and should not be used in newly-written code.</para>" % m.group(
2989 else:
2990 desc += "is deprecated and should not be used in newly-written code.</para>"
2992 note = re.sub(r'^\s*([0-9\.]+)\s*:?\s*', '', note)
2993 note = note.strip()
2995 if note != '':
2996 note = ConvertMarkDown(symbol, note)
2997 desc += " " + note
2999 desc += "</warning>\n"
3001 return desc
3004 def MakeConditionDescription(symbol):
3005 """This returns a sumary of conditions for the given symbol.
3007 Args:
3008 symbol (str): the symbol to create the sumary for.
3010 Returns:
3011 str: formatted text or empty string if no special conditions apply.
3013 desc = ''
3014 if symbol in Deprecated:
3015 if desc != '':
3016 desc += "|"
3017 m = re.search(r'^\s*(.*?)\s*$', Deprecated[symbol])
3018 if m:
3019 desc += "deprecated:%s" % m.group(1)
3020 else:
3021 desc += "deprecated"
3023 if symbol in Since:
3024 if desc != '':
3025 desc += "|"
3026 m = re.search(r'^\s*(.*?)\s*$', Since[symbol])
3027 if m:
3028 desc += "since:%s" % m.group(1)
3029 else:
3030 desc += "since"
3032 if symbol in StabilityLevel:
3033 if desc != '':
3034 desc += "|"
3036 desc += "stability:" + StabilityLevel[symbol]
3038 if desc != '':
3039 cond = re.sub(r'"', r'&quot;', desc)
3040 desc = ' condition=\"%s\"' % cond
3041 logging.info("condition for '%s' = '%s'", symbol, desc)
3043 return desc
3046 def GetHierarchy(gobject, hierarchy):
3047 """Generate the object inheritance graph.
3049 Returns the DocBook output describing the ancestors and
3050 immediate children of a GObject subclass. It uses the
3051 global Objects and ObjectLevels arrays to walk the tree.
3053 Args:
3054 object (str): the GtkObject subclass.
3055 hierarchy (list) - previous hierarchy
3057 Returns:
3058 list: lines of docbook describing the hierarchy
3060 # Find object in the objects array.
3061 found = False
3062 children = []
3063 level = 0
3064 j = 0
3065 for i in range(len(Objects)):
3066 if found:
3067 if ObjectLevels[i] <= level:
3068 break
3070 elif ObjectLevels[i] == level + 1:
3071 children.append(Objects[i])
3073 elif Objects[i] == gobject:
3074 found = True
3075 j = i
3076 level = ObjectLevels[i]
3078 if not found:
3079 return hierarchy
3081 logging.info("=== Hierachy for: %s (%d existing entries) ===", gobject, len(hierarchy))
3083 # Walk up the hierarchy, pushing ancestors onto the ancestors array.
3084 ancestors = [gobject]
3085 logging.info("Level: %s", level)
3086 while level > 1:
3087 j -= 1
3088 if ObjectLevels[j] < level:
3089 ancestors.append(Objects[j])
3090 level = ObjectLevels[j]
3091 logging.info("Level: %s", level)
3093 # Output the ancestors, indented and with links.
3094 logging.info('%d ancestors', len(ancestors))
3095 last_index = 0
3096 level = 1
3097 for i in range(len(ancestors) - 1, -1, -1):
3098 ancestor = ancestors[i]
3099 ancestor_id = common.CreateValidSGMLID(ancestor)
3100 indent = ' ' * (level * 4)
3101 # Don't add a link to the current object, i.e. when i == 0.
3102 if i > 0:
3103 entry_text = indent + "<link linkend=\"%s\">%s</link>" % (ancestor_id, ancestor)
3104 alt_text = indent + ancestor
3105 else:
3106 entry_text = indent + ancestor
3107 alt_text = indent + "<link linkend=\"%s\">%s</link>" % (ancestor_id, ancestor)
3109 logging.info("Checking for '%s' or '%s'", entry_text, alt_text)
3110 # Check if we already have this object
3111 index = -1
3112 for j in range(len(hierarchy)):
3113 if hierarchy[j] == entry_text or (hierarchy[j] == alt_text):
3114 index = j
3115 break
3116 if index == -1:
3117 # We have a new entry, find insert position in alphabetical order
3118 found = False
3119 for j in range(last_index, len(hierarchy)):
3120 if not re.search(r'^' + indent, hierarchy[j]):
3121 last_index = j
3122 found = True
3123 break
3124 elif re.search(r'^%s[^ ]' % indent, hierarchy[j]):
3125 stripped_text = hierarchy[j]
3126 if r'<link linkend' not in entry_text:
3127 stripped_text = re.sub(r'<link linkend="[A-Za-z]*">', '', stripped_text)
3128 stripped_text = re.sub(r'</link>', '', stripped_text)
3130 if entry_text < stripped_text:
3131 last_index = j
3132 found = True
3133 break
3135 # Append to bottom
3136 if not found:
3137 last_index = len(hierarchy)
3139 logging.debug('insert at %d: %s', last_index, entry_text)
3140 hierarchy.insert(last_index, entry_text)
3141 last_index += 1
3142 else:
3143 # Already have this one, make sure we use the not linked version
3144 if r'<link linkend' not in entry_text:
3145 hierarchy[j] = entry_text
3147 # Remember index as base insert point
3148 last_index = index + 1
3150 level += 1
3152 # Output the children, indented and with links.
3153 logging.info('%d children', len(children))
3154 for i in range(len(children)):
3155 sid = common.CreateValidSGMLID(children[i])
3156 indented_text = ' ' * (level * 4) + "<link linkend=\"%s\">%s</link>" % (sid, children[i])
3157 logging.debug('insert at %d: %s', last_index, indented_text)
3158 hierarchy.insert(last_index, indented_text)
3159 last_index += 1
3160 return hierarchy
3163 def GetInterfaces(gobject):
3164 """Generate interface implementation graph.
3166 Returns the DocBook output describing the interfaces
3167 implemented by a class. It uses the global Interfaces hash.
3169 Args:
3170 object (str): the GObject subclass.
3172 Returns:
3173 str: implemented interfaces
3175 text = ''
3176 # Find object in the objects array.
3177 if gobject in Interfaces:
3178 ifaces = Interfaces[gobject].split()
3179 text = '''<para>
3180 %s implements
3181 ''' % gobject
3182 count = len(ifaces)
3183 for i in range(count):
3184 sid = common.CreateValidSGMLID(ifaces[i])
3185 text += " <link linkend=\"%s\">%s</link>" % (sid, ifaces[i])
3186 if i < count - 2:
3187 text += ', '
3188 elif i < count - 1:
3189 text += ' and '
3190 else:
3191 text += '.'
3192 text += '</para>\n'
3193 return text
3196 def GetImplementations(gobject):
3197 """Generate interface usage graph.
3199 Returns the DocBook output describing the implementations
3200 of an interface. It uses the global Interfaces hash.
3202 Args:
3203 object (str): the GObject subclass.
3205 Returns:
3206 str: interface implementations
3208 text = ''
3209 impls = []
3210 for key in Interfaces:
3211 if re.search(r'\b%s\b' % gobject, Interfaces[key]):
3212 impls.append(key)
3214 count = len(impls)
3215 if count > 0:
3216 impls.sort()
3217 text = '''<para>
3218 %s is implemented by
3219 ''' % gobject
3220 for i in range(count):
3221 sid = common.CreateValidSGMLID(impls[i])
3222 text += " <link linkend=\"%s\">%s</link>" % (sid, impls[i])
3223 if i < count - 2:
3224 text += ', '
3225 elif i < count - 1:
3226 text += ' and '
3227 else:
3228 text += '.'
3229 text += '</para>\n'
3230 return text
3233 def GetPrerequisites(iface):
3234 """Generates interface requirements.
3236 Returns the DocBook output describing the prerequisites
3237 of an interface. It uses the global Prerequisites hash.
3238 Args:
3239 iface (str): the interface.
3241 Returns:
3242 str: required interfaces
3245 text = ''
3246 if iface in Prerequisites:
3247 text = '''<para>
3248 %s requires
3249 ''' % iface
3250 prereqs = Prerequisites[iface].split()
3251 count = len(prereqs)
3252 for i in range(count):
3253 sid = common.CreateValidSGMLID(prereqs[i])
3254 text += " <link linkend=\"%s\">%s</link>" % (sid, prereqs[i])
3255 if i < count - 2:
3256 text += ', '
3257 elif i < count - 1:
3258 text += ' and '
3259 else:
3260 text += '.'
3261 text += '</para>\n'
3262 return text
3265 def GetDerived(iface):
3267 Returns the DocBook output describing the derived interfaces
3268 of an interface. It uses the global %Prerequisites hash.
3270 Args:
3271 iface (str): the interface.
3273 Returns:
3274 str: derived interfaces
3276 text = ''
3277 derived = []
3278 for key in Prerequisites:
3279 if re.search(r'\b%s\b' % iface, Prerequisites[key]):
3280 derived.append(key)
3282 count = len(derived)
3283 if count > 0:
3284 derived.sort()
3285 text = '''<para>
3286 %s is required by
3287 ''' % iface
3288 for i in range(count):
3289 sid = common.CreateValidSGMLID(derived[i])
3290 text += " <link linkend=\"%s\">%s</link>" % (sid, derived[i])
3291 if i < count - 2:
3292 text += ', '
3293 elif i < count - 1:
3294 text += ' and '
3295 else:
3296 text += '.'
3297 text += '</para>\n'
3298 return text
3301 def GetSignals(gobject):
3302 """Generate signal docs.
3304 Returns the synopsis and detailed description DocBook output
3305 for the signal handlers of a given GObject subclass.
3307 Args:
3308 object (str): the GObject subclass, e.g. 'GtkButton'.
3310 Returns:
3311 str: signal docs
3313 synop = ''
3314 desc = ''
3316 for i in range(len(SignalObjects)):
3317 if SignalObjects[i] == gobject:
3318 logging.info("Found signal: %s", SignalNames[i])
3319 name = SignalNames[i]
3320 symbol = '%s::%s' % (gobject, name)
3321 sid = common.CreateValidSGMLID('%s-%s' % (gobject, name))
3323 desc += u"<refsect2 id=\"%s\" role=\"signal\"><title>The <literal>“%s”</literal> signal</title>\n" % (
3324 sid, name)
3325 desc += MakeIndexterms(symbol, sid)
3326 desc += "\n"
3327 desc += OutputSymbolExtraLinks(symbol)
3329 desc += "<programlisting language=\"C\">"
3331 m = re.search(r'\s*(const\s+)?(\w+)\s*(\**)', SignalReturns[i])
3332 type_modifier = m.group(1) or ''
3333 gtype = m.group(2)
3334 pointer = m.group(3)
3335 xref = MakeXRef(gtype, tagify(gtype, "returnvalue"))
3337 ret_type_output = '%s%s%s' % (type_modifier, xref, pointer)
3338 callback_name = "user_function"
3339 desc += '%s\n%s (' % (ret_type_output, callback_name)
3341 indentation = ' ' * (len(callback_name) + 2)
3343 sourceparams = SourceSymbolParams.get(symbol)
3344 sourceparam_names = None
3345 if sourceparams:
3346 sourceparam_names = list(sourceparams) # keys as list
3347 params = SignalPrototypes[i].splitlines()
3348 type_len = len("gpointer")
3349 name_len = len("user_data")
3350 # do two passes, the first one is to calculate padding
3351 for l in range(2):
3352 for j in range(len(params)):
3353 param_name = None
3354 # allow alphanumerics, '_', '[' & ']' in param names
3355 m = re.search(r'^\s*(\w+)\s*(\**)\s*([\w\[\]]+)\s*$', params[j])
3356 if m:
3357 gtype = m.group(1)
3358 pointer = m.group(2)
3359 if sourceparam_names:
3360 if j < len(sourceparam_names):
3361 param_name = sourceparam_names[j]
3362 logging.info('from sourceparams: "%s" (%d: %s)', param_name, j, params[j])
3363 # we're mssing the docs for this param, don't warn here though
3364 else:
3365 param_name = m.group(3)
3366 logging.info('from params: "%s" (%d: %s)', param_name, j, params[j])
3368 if not param_name:
3369 param_name = "arg%d" % j
3371 if l == 0:
3372 if len(gtype) + len(pointer) > type_len:
3373 type_len = len(gtype) + len(pointer)
3374 if len(param_name) > name_len:
3375 name_len = len(param_name)
3376 else:
3377 logging.info("signal arg[%d]: '%s'", j, param_name)
3378 xref = MakeXRef(gtype, tagify(gtype, "type"))
3379 pad = ' ' * (type_len - len(gtype) - len(pointer))
3380 desc += '%s%s %s%s,\n' % (xref, pad, pointer, param_name)
3381 desc += indentation
3383 else:
3384 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
3385 "Can't parse arg: %s\nArgs:%s" % (params[j], SignalPrototypes[i]))
3387 xref = MakeXRef("gpointer", tagify("gpointer", "type"))
3388 pad = ' ' * (type_len - len("gpointer"))
3389 desc += '%s%s user_data)' % (xref, pad)
3390 desc += "</programlisting>\n"
3392 flags = SignalFlags[i]
3393 flags_string = ''
3394 if flags:
3395 if 'f' in flags:
3396 flags_string = "<link linkend=\"G-SIGNAL-RUN-FIRST:CAPS\">Run First</link>"
3398 elif 'l' in flags:
3399 flags_string = "<link linkend=\"G-SIGNAL-RUN-LAST:CAPS\">Run Last</link>"
3401 elif 'c' in flags:
3402 flags_string = "<link linkend=\"G-SIGNAL-RUN-CLEANUP:CAPS\">Cleanup</link>"
3403 flags_string = "Cleanup"
3405 if 'r' in flags:
3406 if flags_string:
3407 flags_string += " / "
3408 flags_string = "<link linkend=\"G-SIGNAL-NO-RECURSE:CAPS\">No Recursion</link>"
3410 if 'd' in flags:
3411 if flags_string:
3412 flags_string += " / "
3413 flags_string = "<link linkend=\"G-SIGNAL-DETAILED:CAPS\">Has Details</link>"
3415 if 'a' in flags:
3416 if flags_string:
3417 flags_string += " / "
3418 flags_string = "<link linkend=\"G-SIGNAL-ACTION:CAPS\">Action</link>"
3420 if 'h' in flags:
3421 if flags_string:
3422 flags_string += " / "
3423 flags_string = "<link linkend=\"G-SIGNAL-NO-HOOKS:CAPS\">No Hooks</link>"
3425 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" % (
3426 ret_type_output, sid, name, flags_string)
3428 parameters = OutputParamDescriptions("SIGNAL", symbol, None)
3429 logging.info("formatted signal params: '%s' -> '%s'", symbol, parameters)
3431 AllSymbols[symbol] = 1
3432 if symbol in SymbolDocs:
3433 symbol_docs = ConvertMarkDown(symbol, SymbolDocs[symbol])
3435 desc += symbol_docs
3437 if not IsEmptyDoc(SymbolDocs[symbol]):
3438 AllDocumentedSymbols[symbol] = 1
3440 if symbol in SymbolAnnotations:
3441 param_desc = SymbolAnnotations[symbol]
3442 (param_desc, param_annotations) = ExpandAnnotation(symbol, param_desc)
3443 if param_annotations != '':
3444 desc += "\n<para>%s</para>" % param_annotations
3446 desc += MakeDeprecationNote(symbol)
3448 desc += parameters
3449 if flags_string:
3450 desc += "<para>Flags: %s</para>\n" % flags_string
3452 desc += OutputSymbolTraits(symbol)
3453 desc += "</refsect2>"
3455 return (synop, desc)
3458 def GetArgs(gobject):
3459 """Generate property docs.
3461 Returns the synopsis and detailed description DocBook output
3462 for the Args of a given GtkObject subclass.
3464 Args:
3465 object (str): the GObject subclass, e.g. 'GtkButton'.
3467 Returns:
3468 str: property docs
3470 synop = ''
3471 desc = ''
3472 child_synop = ''
3473 child_desc = ''
3474 style_synop = ''
3475 style_desc = ''
3477 for i in range(len(ArgObjects)):
3478 if ArgObjects[i] == gobject:
3479 logging.info("Found arg: %s", ArgNames[i])
3480 name = ArgNames[i]
3481 flags = ArgFlags[i]
3482 flags_string = ''
3483 kind = ''
3484 id_sep = ''
3486 if 'c' in flags:
3487 kind = "child property"
3488 id_sep = "c-"
3489 elif 's' in flags:
3490 kind = "style property"
3491 id_sep = "s-"
3492 else:
3493 kind = "property"
3495 # Remember only one colon so we don't clash with signals.
3496 symbol = '%s:%s' % (gobject, name)
3497 # use two dashes and ev. an extra separator here for the same reason.
3498 sid = common.CreateValidSGMLID('%s--%s%s' % (gobject, id_sep, name))
3500 atype = ArgTypes[i]
3501 type_output = None
3502 arange = ArgRanges[i]
3503 range_output = CreateValidSGML(arange)
3504 default = ArgDefaults[i]
3505 default_output = CreateValidSGML(default)
3507 if atype == "GtkString":
3508 atype = "char&#160;*"
3510 if atype == "GtkSignal":
3511 atype = "GtkSignalFunc, gpointer"
3512 type_output = MakeXRef("GtkSignalFunc") + ", " + MakeXRef("gpointer")
3513 elif re.search(r'^(\w+)\*$', atype):
3514 m = re.search(r'^(\w+)\*$', atype)
3515 type_output = MakeXRef(m.group(1), tagify(m.group(1), "type")) + "&#160;*"
3516 else:
3517 type_output = MakeXRef(atype, tagify(atype, "type"))
3519 if 'r' in flags:
3520 flags_string = "Read"
3522 if 'w' in flags:
3523 if flags_string:
3524 flags_string += " / "
3525 flags_string += "Write"
3527 if 'x' in flags:
3528 if flags_string:
3529 flags_string += " / "
3530 flags_string += "Construct"
3532 if 'X' in flags:
3533 if flags_string:
3534 flags_string += " / "
3535 flags_string += "Construct Only"
3537 AllSymbols[symbol] = 1
3538 blurb = ''
3539 if symbol in SymbolDocs and not IsEmptyDoc(SymbolDocs[symbol]):
3540 blurb = ConvertMarkDown(symbol, SymbolDocs[symbol])
3541 logging.info(".. [%s][%s]", SymbolDocs[symbol], blurb)
3542 AllDocumentedSymbols[symbol] = 1
3544 else:
3545 if ArgBlurbs[i] != '':
3546 blurb = "<para>" + CreateValidSGML(ArgBlurbs[i]) + "</para>"
3547 AllDocumentedSymbols[symbol] = 1
3548 else:
3549 # FIXME: print a warning?
3550 logging.info(".. no description")
3552 pad1 = ''
3553 if len(name) < 24:
3554 pad1 = " " * (24 - len(name))
3556 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" % (
3557 type_output, sid, name, flags_string)
3558 arg_desc = u"<refsect2 id=\"%s\" role=\"property\"><title>The <literal>“%s”</literal> %s</title>\n" % (
3559 sid, name, kind)
3560 arg_desc += MakeIndexterms(symbol, sid)
3561 arg_desc += "\n"
3562 arg_desc += OutputSymbolExtraLinks(symbol)
3564 arg_desc += u"<programlisting> “%s%s %s</programlisting>\n" % (name, pad1, type_output)
3565 arg_desc += blurb
3566 if symbol in SymbolAnnotations:
3567 param_desc = SymbolAnnotations[symbol]
3568 (param_desc, param_annotations) = ExpandAnnotation(symbol, param_desc)
3569 if param_annotations != '':
3570 arg_desc += "\n<para>%s</para>" % param_annotations
3572 arg_desc += MakeDeprecationNote(symbol)
3574 if flags_string:
3575 arg_desc += "<para>Flags: %s</para>\n" % flags_string
3577 if arange != '':
3578 arg_desc += "<para>Allowed values: %s</para>\n" % range_output
3580 if default != '':
3581 arg_desc += "<para>Default value: %s</para>\n" % default_output
3583 arg_desc += OutputSymbolTraits(symbol)
3584 arg_desc += "</refsect2>\n"
3586 if 'c' in flags:
3587 child_synop += arg_synop
3588 child_desc += arg_desc
3590 elif 's' in flags:
3591 style_synop += arg_synop
3592 style_desc += arg_desc
3594 else:
3595 synop += arg_synop
3596 desc += arg_desc
3598 return (synop, child_synop, style_synop, desc, child_desc, style_desc)
3601 def IgnorePath(path, source_dirs, ignore_files):
3602 for sdir in source_dirs:
3603 # Cut off base directory
3604 m1 = re.search(r'^%s/(.*)$' % re.escape(sdir), path)
3605 if m1:
3606 # Check if the filename is in the ignore list.
3607 m2 = re.search(r'(\s|^)%s(\s|$)' % re.escape(m1.group(1)), ignore_files)
3608 if m2:
3609 logging.info("Skipping path: %s", path)
3610 return True
3611 else:
3612 logging.info("No match for: %s", m1.group(1))
3613 else:
3614 logging.info("No match for: %s", path)
3615 return False
3618 def ReadSourceDocumentation(source_dir, suffix_list, source_dirs, ignore_files):
3619 """Read the documentation embedded in comment blocks in the source code.
3621 It recursively descends the source directory looking for source files and
3622 scans them looking for specially-formatted comment blocks.
3624 Args:
3625 source_dir (str): the directory to scan.
3626 suffix_list (list): extensions to check
3628 if IgnorePath(source_dir, source_dirs, ignore_files):
3629 return
3631 logging.info("Scanning source directory: %s", source_dir)
3633 # This array holds any subdirectories found.
3634 subdirs = []
3636 for ifile in sorted(os.listdir(source_dir)):
3637 logging.debug("... : %s", ifile)
3638 if ifile.startswith('.'):
3639 continue
3640 fname = os.path.join(source_dir, ifile)
3641 if os.path.isdir(fname):
3642 subdirs.append(fname)
3643 else:
3644 for suffix in suffix_list:
3645 if ifile.endswith(suffix):
3646 if not IgnorePath(fname, source_dirs, ignore_files):
3647 ScanSourceFile(fname, ignore_files)
3648 break
3650 # Now recursively scan the subdirectories.
3651 for sdir in subdirs:
3652 ReadSourceDocumentation(sdir, suffix_list, source_dirs, ignore_files)
3655 def ScanSourceFile(ifile, ignore_files):
3656 """Scans one source file looking for specially-formatted comment blocks.
3658 Later MergeSourceDocumentation() is copying over the doc blobs that are not
3659 suppressed/ignored.
3661 Args:
3662 file (str): the file to scan.
3664 m = re.search(r'^.*[\/\\]([^\/\\]*)$', ifile)
3665 if m:
3666 basename = m.group(1)
3667 else:
3668 common.LogWarning(ifile, 1, "Can't find basename for this filename.")
3669 basename = ifile
3671 # Check if the basename is in the list of files to ignore.
3672 if re.search(r'(\s|^)%s(\s|$)' % re.escape(basename), ignore_files):
3673 logging.info("Skipping source file: %s", ifile)
3674 return
3676 logging.info("Scanning source file: %s", ifile)
3678 SRCFILE = common.open_text(ifile)
3679 in_comment_block = False
3680 symbol = None
3681 in_part = ''
3682 description = ''
3683 return_desc = ''
3684 since_desc = stability_desc = deprecated_desc = ''
3685 params = OrderedDict()
3686 param_name = None
3687 line_number = 0
3688 for line in SRCFILE:
3689 line_number += 1
3690 # Look for the start of a comment block.
3691 if not in_comment_block:
3692 if re.search(r'^\s*/\*.*\*/', line):
3693 # one-line comment - not gtkdoc
3694 pass
3695 elif re.search(r'^\s*/\*\*\s', line):
3696 logging.info("Found comment block start")
3698 in_comment_block = True
3700 # Reset all the symbol data.
3701 symbol = ''
3702 in_part = ''
3703 description = ''
3704 return_desc = ''
3705 since_desc = ''
3706 deprecated_desc = ''
3707 stability_desc = ''
3708 params = OrderedDict()
3709 param_name = None
3711 continue
3713 # We're in a comment block. Check if we've found the end of it.
3714 if re.search(r'^\s*\*+/', line):
3715 if not symbol:
3716 # maybe its not even meant to be a gtk-doc comment?
3717 common.LogWarning(ifile, line_number, "Symbol name not found at the start of the comment block.")
3718 else:
3719 # Add the return value description onto the end of the params.
3720 if return_desc:
3721 # TODO(ensonic): check for duplicated Return docs
3722 # common.LogWarning(file, line_number, "Multiple Returns for %s." % symbol)
3723 params['Returns'] = return_desc
3725 # Convert special characters
3726 description = ConvertSGMLChars(symbol, description)
3727 for (param_name, param_desc) in iteritems(params):
3728 params[param_name] = ConvertSGMLChars(symbol, param_desc)
3730 # Handle Section docs
3731 m = re.search(r'SECTION:\s*(.*)', symbol)
3732 m2 = re.search(r'PROGRAM:\s*(.*)', symbol)
3733 if m:
3734 real_symbol = m.group(1)
3735 long_descr = real_symbol + ":Long_Description"
3737 if long_descr not in KnownSymbols or KnownSymbols[long_descr] != 1:
3738 common.LogWarning(
3739 ifile, line_number, "Section %s is not defined in the %s-sections.txt file." % (real_symbol, MODULE))
3741 logging.info("SECTION DOCS found in source for : '%s'", real_symbol)
3742 for param_name, param_desc in iteritems(params):
3743 logging.info(" '" + param_name + "'")
3744 param_name = param_name.lower()
3745 key = None
3746 if param_name == "short_description":
3747 key = real_symbol + ":Short_Description"
3748 elif param_name == "see_also":
3749 key = real_symbol + ":See_Also"
3750 elif param_name == "title":
3751 key = real_symbol + ":Title"
3752 elif param_name == "stability":
3753 key = real_symbol + ":Stability_Level"
3754 elif param_name == "section_id":
3755 key = real_symbol + ":Section_Id"
3756 elif param_name == "include":
3757 key = real_symbol + ":Include"
3758 elif param_name == "image":
3759 key = real_symbol + ":Image"
3761 if key:
3762 SourceSymbolDocs[key] = param_desc
3763 SourceSymbolSourceFile[key] = ifile
3764 SourceSymbolSourceLine[key] = line_number
3766 SourceSymbolDocs[long_descr] = description
3767 SourceSymbolSourceFile[long_descr] = ifile
3768 SourceSymbolSourceLine[long_descr] = line_number
3769 elif m2:
3770 real_symbol = m2.group(1)
3771 key = None
3772 section_id = None
3774 logging.info("PROGRAM DOCS found in source for '%s'", real_symbol)
3775 for param_name, param_desc in iteritems(params):
3776 logging.info("PROGRAM key %s: '%s'", real_symbol, param_name)
3777 param_name = param_name.lower()
3778 key = None
3779 if param_name == "short_description":
3780 key = real_symbol + ":Short_Description"
3781 elif param_name == "see_also":
3782 key = real_symbol + ":See_Also"
3783 elif param_name == "section_id":
3784 key = real_symbol + ":Section_Id"
3785 elif param_name == "synopsis":
3786 key = real_symbol + ":Synopsis"
3787 elif param_name == "returns":
3788 key = real_symbol + ":Returns"
3789 elif re.search(r'^(-.*)', param_name):
3790 logging.info("PROGRAM opts: '%s': '%s'", param_name, param_desc)
3791 key = real_symbol + ":Options"
3792 opts = []
3793 opts_str = SourceSymbolDocs.get(key)
3794 if opts_str:
3795 opts = opts_str.split('\t')
3796 opts.append(param_name)
3797 opts.append(param_desc)
3799 logging.info("Setting options for symbol: %s: '%s'", real_symbol, '\t'.join(opts))
3800 SourceSymbolDocs[key] = '\t'.join(opts)
3801 continue
3803 if key:
3804 logging.info("PROGRAM value %s: '%s'", real_symbol, param_desc.rstrip())
3805 SourceSymbolDocs[key] = param_desc.rstrip()
3806 SourceSymbolSourceFile[key] = ifile
3807 SourceSymbolSourceLine[key] = line_number
3809 long_descr = real_symbol + ":Long_Description"
3810 SourceSymbolDocs[long_descr] = description
3811 SourceSymbolSourceFile[long_descr] = ifile
3812 SourceSymbolSourceLine[long_descr] = line_number
3814 section_id = SourceSymbolDocs.get(real_symbol + ":Section_Id")
3815 if section_id and section_id.strip() != '':
3816 # Remove trailing blanks and use as is
3817 section_id = section_id.rstrip()
3818 else:
3819 section_id = common.CreateValidSGMLID('%s-%s' % (MODULE, real_symbol))
3820 OutputProgramDBFile(real_symbol, section_id)
3822 else:
3823 logging.info("SYMBOL DOCS found in source for : '%s'", symbol)
3824 SourceSymbolDocs[symbol] = description
3825 SourceSymbolParams[symbol] = params
3826 SourceSymbolSourceFile[symbol] = ifile
3827 SourceSymbolSourceLine[symbol] = line_number
3829 if since_desc:
3830 arr = since_desc.splitlines()
3831 since_desc = arr[0].strip()
3832 extra_lines = arr[1:]
3833 logging.info("Since(%s) : [%s]", symbol, since_desc)
3834 Since[symbol] = ConvertSGMLChars(symbol, since_desc)
3835 if len(extra_lines) > 1:
3836 common.LogWarning(ifile, line_number, "multi-line since docs found")
3838 if stability_desc:
3839 stability_desc = ParseStabilityLevel(
3840 stability_desc, ifile, line_number, "Stability level for %s" % symbol)
3841 StabilityLevel[symbol] = ConvertSGMLChars(symbol, stability_desc)
3843 if deprecated_desc:
3844 if symbol not in Deprecated:
3845 # don't warn for signals and properties
3846 # if ($symbol !~ m/::?(.*)/)
3847 if symbol in DeclarationTypes:
3848 common.LogWarning(ifile, line_number,
3849 "%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)
3851 Deprecated[symbol] = ConvertSGMLChars(symbol, deprecated_desc)
3853 in_comment_block = False
3854 continue
3856 # Get rid of ' * ' at start of every line in the comment block.
3857 line = re.sub(r'^\s*\*\s?', '', line)
3858 # But make sure we don't get rid of the newline at the end.
3859 if not line.endswith('\n'):
3860 line = line + "\n"
3862 logging.info("scanning :%s", line.strip())
3864 # If we haven't found the symbol name yet, look for it.
3865 if not symbol:
3866 m1 = re.search(r'^\s*(SECTION:\s*\S+)', line)
3867 m2 = re.search(r'^\s*(PROGRAM:\s*\S+)', line)
3868 m3 = re.search(r'^\s*([\w:-]*\w)\s*:?\s*(\(.+?\)\s*)*$', line)
3869 if m1:
3870 symbol = m1.group(1)
3871 logging.info("SECTION DOCS found in source for : '%s'", symbol)
3872 elif m2:
3873 symbol = m2.group(1)
3874 logging.info("PROGRAM DOCS found in source for : '%s'", symbol)
3875 elif m3:
3876 symbol = m3.group(1)
3877 annotation = m3.group(2)
3878 logging.info("SYMBOL DOCS found in source for : '%s'", symbol)
3879 if annotation:
3880 annotation = annotation.strip()
3881 if annotation != '':
3882 SymbolAnnotations[symbol] = annotation
3883 logging.info("remaining text for %s: '%s'", symbol, annotation)
3885 continue
3887 if in_part == "description":
3888 # Get rid of 'Description:'
3889 line = re.sub(r'^\s*Description:', '', line)
3891 m1 = re.search(r'^\s*(returns|return\s+value):', line, flags=re.I)
3892 m2 = re.search(r'^\s*since:', line, flags=re.I)
3893 m3 = re.search(r'^\s*deprecated:', line, flags=re.I)
3894 m4 = re.search(r'^\s*stability:', line, flags=re.I)
3896 if m1:
3897 # we're in param section and have not seen the blank line
3898 if in_part != '':
3899 return_desc = line[m1.end():]
3900 in_part = "return"
3901 continue
3903 if m2:
3904 # we're in param section and have not seen the blank line
3905 if in_part != "param":
3906 since_desc = line[m2.end():]
3907 in_part = "since"
3908 continue
3910 elif m3:
3911 # we're in param section and have not seen the blank line
3912 if in_part != "param":
3913 deprecated_desc = line[m3.end():]
3914 in_part = "deprecated"
3915 continue
3917 elif m4:
3918 stability_desc = line[m4.end():]
3919 in_part = "stability"
3920 continue
3922 if in_part == "description":
3923 description += line
3924 continue
3925 elif in_part == "return":
3926 return_desc += line
3927 continue
3928 elif in_part == "since":
3929 since_desc += line
3930 continue
3931 elif in_part == "stability":
3932 stability_desc += line
3933 continue
3934 elif in_part == "deprecated":
3935 deprecated_desc += line
3936 continue
3938 # We must be in the parameters. Check for the empty line below them.
3939 if re.search(r'^\s*$', line):
3940 in_part = "description"
3941 continue
3943 # Look for a parameter name.
3944 m = re.search(r'^\s*@(.+?)\s*:\s*', line)
3945 if m:
3946 param_name = m.group(1)
3947 param_desc = line[m.end():]
3949 logging.info("Found parameter: %s", param_name)
3950 # Allow varargs variations
3951 if re.search(r'^\.\.\.$', param_name):
3952 param_name = "..."
3954 logging.info("Found param for symbol %s : '%s'= '%s'", symbol, param_name, line)
3956 params[param_name] = param_desc
3957 in_part = "param"
3958 continue
3959 elif in_part == '':
3960 logging.info("continuation for %s annotation '%s'", symbol, line)
3961 annotation = re.sub(r'^\s+|\s+$', '', line)
3962 if symbol in SymbolAnnotations:
3963 SymbolAnnotations[symbol] += annotation
3964 else:
3965 SymbolAnnotations[symbol] = annotation
3966 continue
3968 # We must be in the middle of a parameter description, so add it on
3969 # to the last element in @params.
3970 if not param_name:
3971 common.LogWarning(file, line_number, "Parsing comment block file : parameter expected, but got '%s'" % line)
3972 else:
3973 params[param_name] += line
3975 SRCFILE.close()
3978 def OutputMissingDocumentation():
3979 """Outputs report of documentation coverage to a file.
3981 Returns:
3982 bool: True if the report was updated
3984 old_undocumented_file = os.path.join(ROOT_DIR, MODULE + "-undocumented.txt")
3985 new_undocumented_file = os.path.join(ROOT_DIR, MODULE + "-undocumented.new")
3987 n_documented = 0
3988 n_incomplete = 0
3989 total = 0
3990 symbol = None
3991 percent = None
3992 buffer = ''
3993 buffer_deprecated = ''
3994 buffer_descriptions = ''
3996 UNDOCUMENTED = common.open_text(new_undocumented_file, 'w')
3998 for symbol in sorted(iterkeys(AllSymbols)):
3999 # FIXME: should we print common.LogWarnings for undocumented stuff?
4000 # DEBUG
4001 # location = "defined at " + GetSymbolSourceFile(symbol) + ":" + GetSymbolSourceLine(symbol) + "\n"
4002 # DEBUG
4003 m = re.search(
4004 r':(Title|Long_Description|Short_Description|See_Also|Stability_Level|Include|Section_Id|Image)', symbol)
4005 m2 = re.search(r':(Long_Description|Short_Description)', symbol)
4006 if not m:
4007 total += 1
4008 if symbol in AllDocumentedSymbols:
4009 n_documented += 1
4010 if symbol in AllIncompleteSymbols:
4011 n_incomplete += 1
4012 buffer += symbol + " (" + AllIncompleteSymbols[symbol] + ")\n"
4014 elif symbol in Deprecated:
4015 if symbol in AllIncompleteSymbols:
4016 n_incomplete += 1
4017 buffer_deprecated += symbol + " (" + AllIncompleteSymbols[symbol] + ")\n"
4018 else:
4019 buffer_deprecated += symbol + "\n"
4021 else:
4022 if symbol in AllIncompleteSymbols:
4023 n_incomplete += 1
4024 buffer += symbol + " (" + AllIncompleteSymbols[symbol] + ")\n"
4025 else:
4026 buffer += symbol + "\n"
4028 elif m2:
4029 total += 1
4030 if (symbol in SymbolDocs and len(SymbolDocs[symbol]) > 0)\
4031 or (symbol in AllDocumentedSymbols and AllDocumentedSymbols[symbol] > 0):
4032 n_documented += 1
4033 else:
4034 buffer_descriptions += symbol + "\n"
4036 if total == 0:
4037 percent = 100
4038 else:
4039 percent = (n_documented / total) * 100.0
4041 UNDOCUMENTED.write("%.0f%% symbol docs coverage.\n" % percent)
4042 UNDOCUMENTED.write("%s symbols documented.\n" % n_documented)
4043 UNDOCUMENTED.write("%s symbols incomplete.\n" % n_incomplete)
4044 UNDOCUMENTED.write("%d not documented.\n" % (total - n_documented))
4046 if buffer_deprecated != '':
4047 buffer += "\n" + buffer_deprecated
4049 if buffer_descriptions != '':
4050 buffer += "\n" + buffer_descriptions
4052 if buffer != '':
4053 UNDOCUMENTED.write("\n\n" + buffer)
4055 UNDOCUMENTED.close()
4057 return common.UpdateFileIfChanged(old_undocumented_file, new_undocumented_file, 0)
4060 def OutputUndeclaredSymbols():
4061 """Reports undeclared symbols.
4063 Outputs symbols that are listed in the section file, but have no declaration
4064 in the sources.
4066 Returns:
4067 bool: True if the report was updated
4069 old_undeclared_file = os.path.join(ROOT_DIR, MODULE + "-undeclared.txt")
4070 new_undeclared_file = os.path.join(ROOT_DIR, MODULE + "-undeclared.new")
4072 UNDECLARED = common.open_text(new_undeclared_file, 'w')
4074 if UndeclaredSymbols:
4075 UNDECLARED.write("\n".join(sorted(iterkeys(UndeclaredSymbols))))
4076 UNDECLARED.write("\n")
4077 print("See %s-undeclared.txt for the list of undeclared symbols." % MODULE)
4079 UNDECLARED.close()
4081 return common.UpdateFileIfChanged(old_undeclared_file, new_undeclared_file, 0)
4084 def OutputUnusedSymbols():
4085 """Reports unused documentation.
4087 Outputs symbols that are documented in comments, but not declared in the
4088 sources.
4090 Returns:
4091 bool: True if the report was updated
4093 num_unused = 0
4094 old_unused_file = os.path.join(ROOT_DIR, MODULE + "-unused.txt")
4095 new_unused_file = os.path.join(ROOT_DIR, MODULE + "-unused.new")
4097 UNUSED = common.open_text(new_unused_file, 'w')
4099 for symbol in sorted(iterkeys(Declarations)):
4100 if symbol not in DeclarationOutput:
4101 UNUSED.write("%s\n" % symbol)
4102 num_unused += 1
4104 for symbol in sorted(iterkeys(AllUnusedSymbols)):
4105 UNUSED.write(symbol + "(" + AllUnusedSymbols[symbol] + ")\n")
4106 num_unused += 1
4108 UNUSED.close()
4109 if num_unused != 0:
4110 common.LogWarning(
4111 old_unused_file, 1, "%d unused declarations. They should be added to %s-sections.txt in the appropriate place." % (num_unused, MODULE))
4113 return common.UpdateFileIfChanged(old_unused_file, new_unused_file, 0)
4116 def OutputAllSymbols():
4117 """Outputs list of all symbols to a file."""
4118 SYMBOLS = common.open_text(os.path.join(ROOT_DIR, MODULE + "-symbols.txt"), 'w')
4120 for symbol in sorted(iterkeys(AllSymbols)):
4121 SYMBOLS.write(symbol + "\n")
4122 SYMBOLS.close()
4125 def OutputSymbolsWithoutSince():
4126 """Outputs list of all symbols without a since tag to a file."""
4127 SYMBOLS = common.open_text(os.path.join(ROOT_DIR, MODULE + "-nosince.txt"), 'w')
4129 for symbol in sorted(iterkeys(SourceSymbolDocs)):
4130 if symbol in Since:
4131 SYMBOLS.write(symbol + "\n")
4132 SYMBOLS.close()
4135 def CheckParamsDocumented(symbol, params):
4136 stype = DeclarationTypes.get(symbol)
4138 item = "Parameter"
4139 if stype:
4140 if stype == 'STRUCT':
4141 item = "Field"
4142 elif stype == 'ENUM':
4143 item = "Value"
4144 elif stype == 'UNION':
4145 item = "Field"
4146 else:
4147 stype = "SIGNAL"
4148 logging.info("Check param docs for %s, params: %s entries, type=%s", symbol, len(params), stype)
4150 if len(params) > 0:
4151 logging.info("params: %s", str(params))
4152 for (param_name, param_desc) in iteritems(params):
4153 # Output a warning if the parameter is empty and remember for stats.
4154 if param_name != "void" and not re.search(r'\S', param_desc):
4155 if symbol in AllIncompleteSymbols:
4156 AllIncompleteSymbols[symbol] += ", " + param_name
4157 else:
4158 AllIncompleteSymbols[symbol] = param_name
4160 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
4161 "%s description for %s::%s is missing in source code comment block." % (item, symbol, param_name))
4163 elif len(params) == 0:
4164 AllIncompleteSymbols[symbol] = "<items>"
4165 common.LogWarning(GetSymbolSourceFile(symbol), GetSymbolSourceLine(symbol),
4166 "%s descriptions for %s are missing in source code comment block." % (item, symbol))
4169 def MergeSourceDocumentation():
4170 """Merges documentation read from a source file.
4172 Parameter descriptions override any in the template files.
4173 Function descriptions are placed before any description from
4174 the template files.
4177 # add whats found in the source
4178 symbols = set(iterkeys(SourceSymbolDocs))
4180 # and add known symbols from -sections.txt
4181 for symbol in iterkeys(KnownSymbols):
4182 if KnownSymbols[symbol] == 1:
4183 symbols.add(symbol)
4185 logging.info("num source entries: %d", len(symbols))
4187 for symbol in symbols:
4188 AllSymbols[symbol] = 1
4190 if symbol in SourceSymbolDocs:
4191 logging.info("merging [%s] from source", symbol)
4193 # remove leading and training whitespaces
4194 src_docs = SourceSymbolDocs[symbol].strip()
4195 if src_docs != '':
4196 AllDocumentedSymbols[symbol] = 1
4198 SymbolDocs[symbol] = src_docs
4200 # merge parameters
4201 if symbol in SourceSymbolParams:
4202 param_docs = SourceSymbolParams[symbol]
4203 SymbolParams[symbol] = param_docs
4204 # if this symbol is documented, check if docs are complete
4205 # remove all xml-tags and whitespaces
4206 check_docs = re.sub(r'\s', '', re.sub(r'<.*?>', '', src_docs))
4207 if check_docs != '' and param_docs:
4208 CheckParamsDocumented(symbol, param_docs)
4209 else:
4210 logging.info("[%s] undocumented", symbol)
4212 logging.info("num doc entries: %d", len(SymbolDocs))
4215 def IsEmptyDoc(doc):
4216 """Check if a doc-string is empty.
4218 It is also regarded as empty if it only consist of whitespace or e.g. FIXME.
4220 Args:
4221 doc (str): the doc-string
4223 Returns:
4224 bool: True if empty
4226 if re.search(r'^\s*$', doc):
4227 return True
4228 if re.search(r'^\s*<para>\s*(FIXME)?\s*<\/para>\s*$', doc):
4229 return True
4230 return False
4233 def ConvertMarkDown(symbol, text):
4234 md_to_db.Init()
4235 return md_to_db.MarkDownParse(text, symbol)
4238 def ReadDeclarationsFile(ifile, override):
4239 """Reads in a file containing the function/macro/enum etc. declarations.
4241 Note that in some cases there are several declarations with
4242 the same name, e.g. for conditional macros. In this case we
4243 set a flag in the DeclarationConditional hash so the
4244 declaration is not shown in the docs.
4246 If a macro and a function have the same name, e.g. for
4247 g_object_ref, the function declaration takes precedence.
4249 Some opaque structs are just declared with 'typedef struct
4250 _name name;' in which case the declaration may be empty.
4251 The structure may have been found later in the header, so
4252 that overrides the empty declaration.
4254 Args:
4255 file (str): the declarations file to read
4256 override (bool): if declarations in this file should override
4257 any current declaration.
4259 if override == 0:
4260 Declarations.clear()
4261 DeclarationTypes.clear()
4262 DeclarationConditional.clear()
4263 DeclarationOutput.clear()
4265 INPUT = common.open_text(ifile)
4266 declaration_type = ''
4267 declaration_name = None
4268 declaration = None
4269 is_deprecated = 0
4270 line_number = 0
4271 for line in INPUT:
4272 line_number += 1
4273 # logging.debug("%s:%d: %s", ifile, line_number, line)
4274 if not declaration_type:
4275 m1 = re.search(r'^<([^>]+)>', line)
4276 if m1:
4277 declaration_type = m1.group(1)
4278 declaration_name = ''
4279 logging.info("Found declaration: %s", declaration_type)
4280 declaration = ''
4281 else:
4282 m2 = re.search(r'^<NAME>(.*)</NAME>', line)
4283 m3 = re.search(r'^<DEPRECATED/>', line)
4284 m4 = re.search(r'^</%s>' % declaration_type, line)
4285 if m2:
4286 declaration_name = m2.group(1)
4287 elif m3:
4288 is_deprecated = True
4289 elif m4:
4290 logging.info("Found end of declaration: %s, %s", declaration_type, declaration_name)
4291 # Check that the declaration has a name
4292 if declaration_name == '':
4293 common.LogWarning(ifile, line_number, declaration_type + " has no name.\n")
4295 # If the declaration is an empty typedef struct _XXX XXX
4296 # set the flag to indicate the struct has a typedef.
4297 if (declaration_type == 'STRUCT' or declaration_type == 'UNION') \
4298 and re.search(r'^\s*$', declaration):
4299 logging.info("Struct has typedef: %s", declaration_name)
4300 StructHasTypedef[declaration_name] = 1
4302 # Check if the symbol is already defined.
4303 if declaration_name in Declarations and override == 0:
4304 # Function declarations take precedence.
4305 if DeclarationTypes[declaration_name] == 'FUNCTION':
4306 # Ignore it.
4307 pass
4308 elif declaration_type == 'FUNCTION':
4309 if is_deprecated:
4310 Deprecated[declaration_name] = ''
4312 Declarations[declaration_name] = declaration
4313 DeclarationTypes[declaration_name] = declaration_type
4314 elif DeclarationTypes[declaration_name] == declaration_type:
4315 # If the existing declaration is empty, or is just a
4316 # forward declaration of a struct, override it.
4317 if declaration_type == 'STRUCT' or declaration_type == 'UNION':
4318 if re.search(r'^\s*((struct|union)\s+\w+\s*;)?\s*$', Declarations[declaration_name]):
4319 if is_deprecated:
4320 Deprecated[declaration_name] = ''
4321 Declarations[declaration_name] = declaration
4322 elif re.search(r'^\s*((struct|union)\s+\w+\s*;)?\s*$', declaration):
4323 # Ignore an empty or forward declaration.
4324 pass
4325 else:
4326 common.LogWarning(
4327 ifile, line_number, "Structure %s has multiple definitions." % declaration_name)
4329 else:
4330 # set flag in %DeclarationConditional hash for
4331 # multiply defined macros/typedefs.
4332 DeclarationConditional[declaration_name] = 1
4334 else:
4335 common.LogWarning(ifile, line_number, declaration_name + " has multiple definitions.")
4337 else:
4338 if is_deprecated:
4339 Deprecated[declaration_name] = ''
4341 Declarations[declaration_name] = declaration
4342 DeclarationTypes[declaration_name] = declaration_type
4343 logging.debug("added declaration: %s, %s, [%s]", declaration_type, declaration_name, declaration)
4345 declaration_type = ''
4346 is_deprecated = False
4347 else:
4348 declaration += line
4349 INPUT.close()
4352 def ReadSignalsFile(ifile):
4353 """Reads information about object signals.
4355 It creates the arrays @SignalNames and @SignalPrototypes containing details
4356 about the signals. The first line of the SignalPrototype is the return type
4357 of the signal handler. The remaining lines are the parameters passed to it.
4358 The last parameter, "gpointer user_data" is always the same so is not included.
4360 Args:
4361 ifile (str): the file containing the signal handler prototype information.
4363 in_signal = 0
4364 signal_object = None
4365 signal_name = None
4366 signal_returns = None
4367 signal_flags = None
4368 signal_prototype = None
4370 # Reset the signal info.
4371 SignalObjects[:] = []
4372 SignalNames[:] = []
4373 SignalReturns[:] = []
4374 SignalFlags[:] = []
4375 SignalPrototypes[:] = []
4377 if not os.path.isfile(ifile):
4378 return
4380 INPUT = common.open_text(ifile)
4381 line_number = 0
4382 for line in INPUT:
4383 line_number += 1
4384 if not in_signal:
4385 if re.search(r'^<SIGNAL>', line):
4386 in_signal = 1
4387 signal_object = ''
4388 signal_name = ''
4389 signal_returns = ''
4390 signal_prototype = ''
4392 else:
4393 m = re.search(r'^<NAME>(.*)<\/NAME>', line)
4394 m2 = re.search(r'^<RETURNS>(.*)<\/RETURNS>', line)
4395 m3 = re.search(r'^<FLAGS>(.*)<\/FLAGS>', line)
4396 if m:
4397 signal_name = m.group(1)
4398 m1_2 = re.search(r'^(.*)::(.*)$', signal_name)
4399 if m1_2:
4400 signal_object = m1_2.group(1)
4401 signal_name = m1_2.group(2).replace('_', '-')
4402 logging.info("Found signal: %s", signal_name)
4403 else:
4404 common.LogWarning(ifile, line_number, "Invalid signal name: %s." % signal_name)
4406 elif m2:
4407 signal_returns = m2.group(1)
4408 elif m3:
4409 signal_flags = m3.group(1)
4410 elif re.search(r'^</SIGNAL>', line):
4411 logging.info("Found end of signal: %s::%s\nReturns: %s\n%s",
4412 signal_object, signal_name, signal_returns, signal_prototype)
4413 SignalObjects.append(signal_object)
4414 SignalNames.append(signal_name)
4415 SignalReturns.append(signal_returns)
4416 SignalFlags.append(signal_flags)
4417 SignalPrototypes.append(signal_prototype)
4418 in_signal = False
4419 else:
4420 signal_prototype += line
4421 INPUT.close()
4424 def ReadObjectHierarchy(ifile):
4425 """Reads the $MODULE-hierarchy.txt file.
4427 This contains all the GObject subclasses described in this module (and their
4428 ancestors).
4429 It places them in the Objects array, and places their level
4430 in the object hierarchy in the ObjectLevels array, at the
4431 same index. GObject, the root object, has a level of 1.
4433 This also generates tree_index.sgml as it goes along.
4435 Args:
4436 ifile (str): the input filename.
4439 Objects[:] = []
4440 ObjectLevels[:] = []
4442 if not os.path.isfile(ifile):
4443 logging.debug('no *-hierarchy.tx')
4444 return
4446 INPUT = common.open_text(ifile)
4448 # Only emit objects if they are supposed to be documented, or if
4449 # they have documented children. To implement this, we maintain a
4450 # stack of pending objects which will be emitted if a documented
4451 # child turns up.
4452 pending_objects = []
4453 pending_levels = []
4454 root = None
4455 tree = []
4456 for line in INPUT:
4457 m1 = re.search(r'\S+', line)
4458 if not m1:
4459 continue
4461 gobject = m1.group(0)
4462 level = len(line[:m1.start()]) // 2 + 1
4464 if level == 1:
4465 root = gobject
4467 while pending_levels and pending_levels[-1] >= level:
4468 pending_objects.pop()
4469 pending_levels.pop()
4471 pending_objects.append(gobject)
4472 pending_levels.append(level)
4474 if gobject in KnownSymbols:
4475 while len(pending_levels) > 0:
4476 gobject = pending_objects.pop(0)
4477 level = pending_levels.pop(0)
4478 xref = MakeXRef(gobject)
4480 tree.append(' ' * (level * 4) + xref)
4481 Objects.append(gobject)
4482 ObjectLevels.append(level)
4483 ObjectRoots[gobject] = root
4484 # else
4485 # common.LogWarning(ifile, line_number, "unknown type %s" % object)
4488 INPUT.close()
4490 # FIXME: use xml
4491 # my $old_tree_index = "$DB_OUTPUT_DIR/tree_index.$xml"
4492 old_tree_index = os.path.join(DB_OUTPUT_DIR, "tree_index.sgml")
4493 new_tree_index = os.path.join(DB_OUTPUT_DIR, "tree_index.new")
4495 logging.debug('got %d entries for hierarchy', len(tree))
4497 OUTPUT = common.open_text(new_tree_index, 'w')
4498 OUTPUT.write(MakeDocHeader("screen") + "\n<screen>\n" + AddTreeLineArt(tree) + "\n</screen>\n")
4499 OUTPUT.close()
4501 common.UpdateFileIfChanged(old_tree_index, new_tree_index, 0)
4503 OutputObjectList()
4506 def ReadInterfaces(ifile):
4507 """Reads the $MODULE.interfaces file.
4509 Args:
4510 ifile (str): the input filename.
4513 Interfaces.clear()
4515 if not os.path.isfile(ifile):
4516 return
4518 INPUT = common.open_text(ifile)
4520 for line in INPUT:
4521 line = line.strip()
4522 ifaces = line.split()
4523 gobject = ifaces.pop(0)
4524 if gobject in KnownSymbols and KnownSymbols[gobject] == 1:
4525 knownIfaces = []
4527 # filter out private interfaces, but leave foreign interfaces
4528 for iface in ifaces:
4529 if iface not in KnownSymbols or KnownSymbols[iface] == 1:
4530 knownIfaces.append(iface)
4532 Interfaces[gobject] = ' '.join(knownIfaces)
4533 logging.info("Interfaces for %s: %s", gobject, Interfaces[gobject])
4534 else:
4535 logging.info("skipping interfaces for unknown symbol: %s", gobject)
4537 INPUT.close()
4540 def ReadPrerequisites(ifile):
4541 """This reads in the $MODULE.prerequisites file.
4543 Args:
4544 ifile (str): the input filename.
4546 Prerequisites.clear()
4548 if not os.path.isfile(ifile):
4549 return
4551 INPUT = common.open_text(ifile)
4553 for line in INPUT:
4554 line = line.strip()
4555 prereqs = line.split()
4556 iface = prereqs.pop(0)
4557 if iface in KnownSymbols and KnownSymbols[iface] == 1:
4558 knownPrereqs = []
4560 # filter out private prerequisites, but leave foreign prerequisites
4561 for prereq in prereqs:
4562 if prereq not in KnownSymbols or KnownSymbols[prereq] == 1:
4563 knownPrereqs.append(prereq)
4565 Prerequisites[iface] = ' '.join(knownPrereqs)
4567 INPUT.close()
4570 def ReadArgsFile(ifile):
4571 """Reads information about object properties
4573 It creates the arrays ArgObjects, ArgNames, ArgTypes, ArgFlags, ArgNicks and
4574 ArgBlurbs containing info on the args.
4576 Args:
4577 ifile (str): the input filename.
4579 in_arg = False
4580 arg_object = None
4581 arg_name = None
4582 arg_type = None
4583 arg_flags = None
4584 arg_nick = None
4585 arg_blurb = None
4586 arg_default = None
4587 arg_range = None
4589 # Reset the args info.
4590 ArgObjects[:] = []
4591 ArgNames[:] = []
4592 ArgTypes[:] = []
4593 ArgFlags[:] = []
4594 ArgNicks[:] = []
4595 ArgBlurbs[:] = []
4596 ArgDefaults[:] = []
4597 ArgRanges[:] = []
4599 if not os.path.isfile(ifile):
4600 return
4602 INPUT = common.open_text(ifile)
4603 line_number = 0
4604 for line in INPUT:
4605 line_number += 1
4606 if not in_arg:
4607 if line.startswith('<ARG>'):
4608 in_arg = True
4609 arg_object = ''
4610 arg_name = ''
4611 arg_type = ''
4612 arg_flags = ''
4613 arg_nick = ''
4614 arg_blurb = ''
4615 arg_default = ''
4616 arg_range = ''
4618 else:
4619 m1 = re.search(r'^<NAME>(.*)</NAME>', line)
4620 m2 = re.search(r'^<TYPE>(.*)</TYPE>', line)
4621 m3 = re.search(r'^<RANGE>(.*)</RANGE>', line)
4622 m4 = re.search(r'^<FLAGS>(.*)</FLAGS>', line)
4623 m5 = re.search(r'^<NICK>(.*)</NICK>', line)
4624 m6 = re.search(r'^<BLURB>(.*)</BLURB>', line)
4625 m7 = re.search(r'^<DEFAULT>(.*)</DEFAULT>', line)
4626 if m1:
4627 arg_name = m1.group(1)
4628 m1_1 = re.search(r'^(.*)::(.*)$', arg_name)
4629 if m1_1:
4630 arg_object = m1_1.group(1)
4631 arg_name = m1_1.group(2).replace('_', '-')
4632 logging.info("Found arg: %s", arg_name)
4633 else:
4634 common.LogWarning(ifile, line_number, "Invalid argument name: " + arg_name)
4636 elif m2:
4637 arg_type = m2.group(1)
4638 elif m3:
4639 arg_range = m3.group(1)
4640 elif m4:
4641 arg_flags = m4.group(1)
4642 elif m5:
4643 arg_nick = m5.group(1)
4644 elif m6:
4645 arg_blurb = m6.group(1)
4646 if arg_blurb == "(null)":
4647 arg_blurb = ''
4648 common.LogWarning(
4649 ifile, line_number, "Property %s:%s has no documentation." % (arg_object, arg_name))
4651 elif m7:
4652 arg_default = m7.group(1)
4653 elif re.search(r'^</ARG>', line):
4654 logging.info("Found end of arg: %s::%s\n%s : %s", arg_object, arg_name, arg_type, arg_flags)
4655 ArgObjects.append(arg_object)
4656 ArgNames.append(arg_name)
4657 ArgTypes.append(arg_type)
4658 ArgRanges.append(arg_range)
4659 ArgFlags.append(arg_flags)
4660 ArgNicks.append(arg_nick)
4661 ArgBlurbs.append(arg_blurb)
4662 ArgDefaults.append(arg_default)
4663 in_arg = False
4665 INPUT.close()
4668 def AddTreeLineArt(tree):
4669 """Generate a line art tree.
4671 Add unicode lineart to a pre-indented string array and returns
4672 it as as multiline string.
4674 Args:
4675 tree (list): of indented strings.
4677 Returns:
4678 str: multiline string with tree line art
4680 # iterate bottom up over the tree
4681 for i in range(len(tree) - 1, -1, -1):
4682 # count leading spaces
4683 m = re.search(r'^([^<A-Za-z]*)', tree[i])
4684 indent = len(m.group(1))
4685 # replace with ╰───, if place of ╰ is not space insert ├
4686 if indent > 4:
4687 if tree[i][indent - 4] == " ":
4688 tree[i] = tree[i][:indent - 4] + "--- " + tree[i][indent:]
4689 else:
4690 tree[i] = tree[i][:indent - 4] + "+-- " + tree[i][indent:]
4692 # go lines up while space and insert |
4693 j = i - 1
4694 while j >= 0 and tree[j][indent - 4] == ' ':
4695 tree[j] = tree[j][:indent - 4] + '|' + tree[j][indent - 3:]
4696 j -= 1
4698 res = "\n".join(tree)
4699 # unicode chars for: ╰──
4700 res = re.sub(r'---', '<phrase role=\"lineart\">&#9584;&#9472;&#9472;</phrase>', res)
4701 # unicde chars for: ├──
4702 res = re.sub(r'\+--', '<phrase role=\"lineart\">&#9500;&#9472;&#9472;</phrase>', res)
4703 # unicode char for: │
4704 res = re.sub(r'\|', '<phrase role=\"lineart\">&#9474;</phrase>', res)
4706 return res
4709 def CheckIsObject(name):
4710 """Check if symbols is an object.
4712 It uses the global Objects array. Note that the Objects array only contains
4713 classes in the current module and their ancestors - not all GObject classes.
4715 Args:
4716 name (str): the object name to check.
4718 Returns:
4719 bool: True if the given name is a GObject or a subclass.
4721 root = ObjectRoots.get(name)
4722 # Let GBoxed pass as an object here to get -struct appended to the id
4723 # and prevent conflicts with sections.
4724 return root and root != 'GEnum' and root != 'GFlags'
4727 def GetSymbolSourceFile(symbol):
4728 """Get the filename where the symbol docs where taken from."""
4729 return SourceSymbolSourceFile.get(symbol, '')
4732 def GetSymbolSourceLine(symbol):
4733 """Get the file line where the symbol docs where taken from."""
4734 return SourceSymbolSourceLine.get(symbol, 0)