Coverage for /builds/ahmed.baizid.0/gtk-doc/gtkdoc/mkdb.py: 8%
2684 statements
« prev ^ index » next coverage.py v6.5.0, created at 2024-11-05 19:25 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2024-11-05 19:25 +0000
1# -*- python; coding: utf-8 -*-
2#
3# gtk-doc - GTK DocBook documentation generator.
4# Copyright (C) 1998 Damon Chaplin
5# 2007-2016 Stefan Sauer
6#
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.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20#
22"""
23Creates the DocBook files from the source comments.
24"""
26import argparse
27from collections import OrderedDict
28import logging
29import os
30import re
31import string
33from . import common, config, md_to_db
35# Options
36MODULE = None
37DB_OUTPUT_DIR = None
38INLINE_MARKUP_MODE = None
39NAME_SPACE = ''
40ROOT_DIR = '.'
42# These global arrays store information on signals. Each signal has an entry
43# in each of these arrays at the same index, like a multi-dimensional array.
44SignalObjects = [] # The GtkObject which emits the signal.
45SignalNames = [] # The signal name.
46SignalReturns = [] # The return type.
47SignalFlags = [] # Flags for the signal
48SignalPrototypes = [] # The rest of the prototype of the signal handler.
50# These global arrays store information on Args. Each Arg has an entry
51# in each of these arrays at the same index, like a multi-dimensional array.
52ArgObjects = [] # The GtkObject which has the Arg.
53ArgNames = [] # The Arg name.
54ArgTypes = [] # The Arg type - gint, GtkArrowType etc.
55ArgFlags = [] # How the Arg can be used - readable/writable etc.
56ArgNicks = [] # The nickname of the Arg.
57ArgBlurbs = [] # Docstring of the Arg.
58ArgDefaults = [] # Default value of the Arg.
59ArgRanges = [] # The range of the Arg type
61ActionObjects = []
62ActionNames = []
63ActionParams = []
64ActionProperties = []
66# These global hashes store declaration info keyed on a symbol name.
67Declarations = {}
68DeclarationTypes = {}
69DeclarationConditional = {}
70DeclarationOutput = {}
71Deprecated = {}
72Since = {}
73StabilityLevel = {}
74StructHasTypedef = {}
76# These global hashes store the existing documentation.
77SymbolDocs = {}
78SymbolParams = {}
79SymbolAnnotations = {}
81# These global hashes store documentation scanned from the source files.
82SourceSymbolDocs = {}
83SourceSymbolParams = {}
84SymbolSourceLocation = {}
86# all documentation goes in here, so we can do coverage analysis
87AllSymbols = {}
88AllIncompleteSymbols = {}
89AllUnusedSymbols = {}
90AllDocumentedSymbols = {}
92# Undeclared yet documented symbols
93UndeclaredSymbols = {}
95# These global arrays store GObject, subclasses and the hierarchy (also of
96# non-object derived types).
97Objects = []
98ObjectLevels = []
99ObjectRoots = {}
101Interfaces = {}
102Prerequisites = {}
104# holds the symbols which are mentioned in <MODULE>-sections.txt and in which
105# section they are defined
106KnownSymbols = {} # values are 1 for public symbols and 0 otherwise
107SymbolSection = {}
108SymbolSectionId = {}
110# collects index entries
111IndexEntriesFull = {}
112IndexEntriesSince = {}
113IndexEntriesDeprecated = {}
115# Standard C preprocessor directives, which we ignore for '#' abbreviations.
116PreProcessorDirectives = {
117 'assert', 'define', 'elif', 'else', 'endif', 'error', 'if', 'ifdef', 'ifndef',
118 'include', 'line', 'pragma', 'unassert', 'undef', 'warning'
119}
121# remember used annotation (to write minimal glossary)
122AnnotationsUsed = {}
124# the regexp that parses the annotation is in ScanSourceFile()
125AnnotationDefinition = {
126 # the GObjectIntrospection annotations are defined at:
127 # https://gi.readthedocs.io/en/latest/annotations/giannotations.html
128 'allow-none': "NULL is OK, both for passing and for returning.",
129 'nullable': "NULL may be passed as the value in, out, in-out; or as a return value.",
130 'not nullable': "NULL must not be passed as the value in, out, in-out; or as a return value.",
131 'optional': "NULL may be passed instead of a pointer to a location.",
132 'not optional': "NULL must not be passed as the pointer to a location.",
133 'array': "Parameter points to an array of items.",
134 'attribute': "Deprecated free-form custom annotation, replaced by (attributes) annotation.",
135 'attributes': "Free-form key-value pairs.",
136 'closure': "This parameter is a 'user_data', for callbacks; many bindings can pass NULL here.",
137 'constructor': "This symbol is a constructor, not a static method.",
138 'destroy': "This parameter is a 'destroy_data', for callbacks.",
139 'default': "Default parameter value (in case a function which shadows this one via <acronym>rename-to</acronym> has fewer parameters).",
140 'element-type': "Generics and defining elements of containers and arrays.",
141 'error-domains': "Typed errors. Similar to throws in Java.",
142 'foreign': "This is a foreign struct.",
143 'get-value-func': "The specified function is used to convert a struct from a GValue, must be a GTypeInstance.",
144 'in': "Parameter for input. Default is <acronym>transfer none</acronym>.",
145 'inout': "Parameter for input and for returning results. Default is <acronym>transfer full</acronym>.",
146 'in-out': "Parameter for input and for returning results. Default is <acronym>transfer full</acronym>.",
147 'method': "This is a method",
148 'not-error': "A GError parameter is not to be handled like a normal GError.",
149 'out': "Parameter for returning results. Default is <acronym>transfer full</acronym>.",
150 'out caller-allocates': "Out parameter, where caller must allocate storage.",
151 'out callee-allocates': "Out parameter, where callee must allocate storage.",
152 'ref-func': "The specified function is used to ref a struct, must be a GTypeInstance.",
153 'rename-to': "Rename the original symbol's name to SYMBOL.",
154 'scope call': "The callback is valid only during the call to the method.",
155 'scope async': "The callback is valid until first called.",
156 'scope notified': "The callback is valid until the GDestroyNotify argument is called.",
157 'set-value-func': "The specified function is used to convert from a struct to a GValue, must be a GTypeInstance.",
158 'skip': "Exposed in C code, not necessarily available in other languages.",
159 'transfer container': "The caller owns the data container, but not the data inside it.",
160 'transfer floating': "Alias for <acronym>transfer none</acronym>, used for objects with floating refs.",
161 'transfer full': "The caller owns the data, and is responsible for free it.",
162 'transfer none': "The data is owned by the callee, which is responsible of freeing it.",
163 'type': "Override the parsed C type with given type.",
164 'unref-func': "The specified function is used to unref a struct, must be a GTypeInstance.",
165 'virtual': "This is the invoker for a virtual method.",
166 'value': "The specified value overrides the evaluated value of the constant.",
167 # Stability Level definition
168 # https://bugzilla.gnome.org/show_bug.cgi?id=170860
169 'Stable': '''The intention of a Stable interface is to enable arbitrary third parties to
170develop applications to these interfaces, release them, and have confidence that
171they will run on all minor releases of the product (after the one in which the
172interface was introduced, and within the same major release). Even at a major
173release, incompatible changes are expected to be rare, and to have strong
174justifications.
175''',
176 'Unstable': '''Unstable interfaces are experimental or transitional. They are typically used to
177give outside developers early access to new or rapidly changing technology, or
178to provide an interim solution to a problem where a more general solution is
179anticipated. No claims are made about either source or binary compatibility from
180one minor release to the next.
182The Unstable interface level is a warning that these interfaces are subject to
183change without warning and should not be used in unbundled products.
185Given such caveats, customer impact need not be a factor when considering
186incompatible changes to an Unstable interface in a major or minor release.
187Nonetheless, when such changes are introduced, the changes should still be
188mentioned in the release notes for the affected release.
189''',
190 'Private': '''An interface that can be used within the GNOME stack itself, but that is not
191documented for end-users. Such functions should only be used in specified and
192documented ways.
193''',
194}
196# Function and other declaration output settings.
197RETURN_TYPE_FIELD_WIDTH = 20
198MAX_SYMBOL_FIELD_WIDTH = 40
200# XML header
201doctype_header = None
203# docbook templates
204DB_REFENTRY = string.Template('''${header}
205<refentry id="${section_id}">
206<refmeta>
207<refentrytitle role="top_of_page" id="${section_id}.top_of_page">${title}</refentrytitle>
208<manvolnum>3</manvolnum>
209<refmiscinfo>${MODULE} Library${image}</refmiscinfo>
210</refmeta>
211<refnamediv>
212<refname>${title}</refname>
213<refpurpose>${short_desc}</refpurpose>
214</refnamediv>
215${stability}
216${functions_synop}${args_synop}${signals_synop}${actions_synop}${object_anchors}${other_synop}${hierarchy}${prerequisites}${derived}${interfaces}${implementations}
217${include_output}
218<refsect1 id="${section_id}.description" role="desc">
219<title role="desc.title">Description</title>
220${extralinks}${long_desc}
221</refsect1>
222<refsect1 id="${section_id}.functions_details" role="details">
223<title role="details.title">Functions</title>
224${functions_details}
225</refsect1>
226${other_desc}${args_desc}${signals_desc}${actions_desc}${see_also}
227</refentry>
228''')
230DB_REFSECT1_SYNOPSIS3 = string.Template('''<refsect1 id="${section_id}.${type}" role="${role}">
231<title role="${role}.title">${title}</title>
232<informaltable frame="none">
233<tgroup cols="3">
234<colspec colname="${role}_type" colwidth="150px"/>
235<colspec colname="${role}_name" colwidth="300px"/>
236<colspec colname="${role}_flags" colwidth="200px"/>
237<tbody>
238${content}
239</tbody>
240</tgroup>
241</informaltable>
242</refsect1>
243''')
245DB_REFSECT1_SYNOPSIS2 = string.Template('''<refsect1 id="${section_id}.${type}" role="${role}">
246<title role="${role}.title">${title}</title>
247<informaltable pgwide="1" frame="none">
248<tgroup cols="2">
249<colspec colname="${role}_type" colwidth="150px"/>
250<colspec colname="${role}_name"/>
251<tbody>
252${content}
253</tbody>
254</tgroup>
255</informaltable>
256</refsect1>
257''')
259DB_REFSECT1_DESC = string.Template('''<refsect1 id="${section_id}.${type}" role="${role}">
260<title role="${role}.title">${title}</title>
261${content}
262</refsect1>
263''')
266def Run(options):
267 global MODULE, INLINE_MARKUP_MODE, NAME_SPACE, DB_OUTPUT_DIR, doctype_header
269 logging.info('options: %s', str(options.__dict__))
271 # We should pass the options variable around instead of this global variable horror
272 # but too much of the code expects these to be around. Fix this once the transition is done.
273 MODULE = options.module
274 INLINE_MARKUP_MODE = options.xml_mode or options.sgml_mode
275 NAME_SPACE = options.name_space
276 DB_OUTPUT_DIR = options.output_dir or os.path.join(ROOT_DIR, "xml")
278 main_sgml_file = options.main_sgml_file
279 if not main_sgml_file:
280 # backwards compatibility
281 if os.path.exists(MODULE + "-docs.sgml"):
282 main_sgml_file = MODULE + "-docs.sgml"
283 else:
284 main_sgml_file = MODULE + "-docs.xml"
286 # -- phase 1: read files produced by previous tools and scane sources
288 # extract docbook header or define default
289 doctype_header = GetDocbookHeader(main_sgml_file)
291 ReadKnownSymbols(os.path.join(ROOT_DIR, MODULE + "-sections.txt"))
292 ReadSignalsFile(os.path.join(ROOT_DIR, MODULE + ".signals"))
293 ReadArgsFile(os.path.join(ROOT_DIR, MODULE + ".args"))
294 ReadActionsFile(os.path.join(ROOT_DIR, MODULE + ".actions"))
295 obj_tree = ReadObjectHierarchy(os.path.join(ROOT_DIR, MODULE + ".hierarchy"))
296 ReadInterfaces(os.path.join(ROOT_DIR, MODULE + ".interfaces"))
297 ReadPrerequisites(os.path.join(ROOT_DIR, MODULE + ".prerequisites"))
299 ReadDeclarationsFile(os.path.join(ROOT_DIR, MODULE + "-decl.txt"), 0)
300 if os.path.isfile(os.path.join(ROOT_DIR, MODULE + "-overrides.txt")):
301 ReadDeclarationsFile(os.path.join(ROOT_DIR, MODULE + "-overrides.txt"), 1)
303 logging.info("Data files read")
305 # -- phase 2: scan sources
307 # TODO: move this to phase 3 once we fixed the call to OutputProgramDBFile()
308 if not os.path.isdir(DB_OUTPUT_DIR):
309 os.mkdir(DB_OUTPUT_DIR)
311 # Scan sources
312 if options.source_suffixes:
313 suffix_list = ['.' + ext for ext in options.source_suffixes.split(',')]
314 else:
315 suffix_list = ['.c', '.h']
317 source_dirs = options.source_dir
318 ignore_files = options.ignore_files
319 logging.info(" ignore files: " + ignore_files)
320 for sdir in source_dirs:
321 ReadSourceDocumentation(sdir, suffix_list, source_dirs, ignore_files)
323 logging.info("Sources scanned")
325 # -- phase 3: write docbook files
327 changed, book_top, book_bottom = OutputDB(os.path.join(ROOT_DIR, MODULE + "-sections.txt"), options)
328 OutputBook(main_sgml_file, book_top, book_bottom, obj_tree)
330 # If any of the DocBook files have changed, update the timestamp file (so
331 # it can be used for Makefile dependencies).
332 if changed or not os.path.exists(os.path.join(ROOT_DIR, "sgml.stamp")):
333 # TODO: MakeIndexterms() uses NAME_SPACE, but also fills IndexEntriesFull
334 # which DetermineNamespace is using
335 # Can we use something else?
336 # no: AllSymbols, KnownSymbols
337 # IndexEntriesFull: consist of all symbols from sections file + signals and properties
338 #
339 # logging.info('# index_entries_full=%d, # declarations=%d',
340 # len(IndexEntriesFull), len(Declarations))
341 # logging.info('known_symbols - index_entries_full: ' + str(Declarations.keys() - IndexEntriesFull.keys()))
343 # try to detect the common prefix
344 # GtkWidget, GTK_WIDGET, gtk_widget -> gtk
345 if NAME_SPACE == '':
346 NAME_SPACE = DetermineNamespace(IndexEntriesFull.keys())
348 logging.info('namespace prefix ="%s"', NAME_SPACE)
350 OutputObjectTree(obj_tree)
351 OutputObjectList(Objects)
353 OutputIndex("api-index-full", IndexEntriesFull)
354 OutputIndex("api-index-deprecated", IndexEntriesDeprecated)
355 OutputSinceIndexes()
356 OutputAnnotationGlossary()
358 with open(os.path.join(ROOT_DIR, 'sgml.stamp'), 'w') as h:
359 h.write('timestamp')
361 logging.info("All files created: %d", changed)
364def OutputObjectList(obj_list):
365 """This outputs the alphabetical list of objects, in a columned table."""
366 # FIXME: Currently this also outputs ancestor objects which may not actually
367 # be in this module.
369 # TODO(ensonic): consider not writing this unconditionally
370 if not obj_list:
371 return
373 cols = 3
375 # FIXME: use .xml
376 old_object_index = os.path.join(DB_OUTPUT_DIR, "object_index.sgml")
377 new_object_index = os.path.join(DB_OUTPUT_DIR, "object_index.new")
379 OUTPUT = open(new_object_index, 'w', encoding='utf-8')
381 OUTPUT.write('''%s
382<informaltable pgwide="1" frame="none">
383<tgroup cols="%s">
384<colspec colwidth="1*"/>
385<colspec colwidth="1*"/>
386<colspec colwidth="1*"/>
387<tbody>
388''' % (MakeDocHeader("informaltable"), cols))
390 count = 0
391 object = None
392 for object in sorted(Objects):
393 xref = MakeXRef(object)
394 if count % cols == 0:
395 OUTPUT.write("<row>\n")
396 OUTPUT.write("<entry>%s</entry>\n" % xref)
397 if count % cols == cols - 1:
398 OUTPUT.write("</row>\n")
399 count += 1
401 if count % cols > 0:
402 OUTPUT.write("</row>\n")
404 OUTPUT.write('''</tbody></tgroup></informaltable>\n''')
405 OUTPUT.close()
407 common.UpdateFileIfChanged(old_object_index, new_object_index, 0)
410def trim_leading_and_trailing_nl(text):
411 """Trims extra newlines.
413 Leaves a single trailing newline.
415 Args:
416 text (str): the text block to trim. May contain newlines.
418 Returns:
419 (str): trimmed text
420 """
421 text = re.sub(r'^\n*', '', text)
422 return re.sub(r'\n+$', '\n', text)
425def trim_white_spaces(text):
426 """Trims extra whitespace.
428 Empty lines inside a block are preserved. All whitespace and the end is
429 replaced with a single newline.
431 Args:
432 text (str): the text block to trim. May contain newlines.
434 Returns:
435 (str): trimmed text
436 """
438 # strip trailing spaces on every line
439 return re.sub(r'\s+$', '\n', text.lstrip(), flags=re.MULTILINE)
442def make_refsect1_synopsis(tmpl, content, title, section_id, section_type, role=None):
443 # TODO(ensonic): canonicalize xml to use the same string for section_type
444 # and role. Needs fixes on gtk-doc.xsl
445 if role is None:
446 role = section_type.replace('-', '_')
448 return tmpl.substitute({
449 'content': content,
450 'role': role,
451 'section_id': section_id,
452 'title': title,
453 'type': section_type,
454 })
457def make_refsect1_synopsis2(content, title, section_id, section_type, role=None):
458 return make_refsect1_synopsis(DB_REFSECT1_SYNOPSIS2, content, title, section_id, section_type, role)
461def make_refsect1_synopsis3(content, title, section_id, section_type, role=None):
462 return make_refsect1_synopsis(DB_REFSECT1_SYNOPSIS3, content, title, section_id, section_type, role)
465def make_refsect1_desc(content, title, section_id, section_type, role=None):
466 content = trim_white_spaces(content)
467 if content == '':
468 return ''
470 # TODO(ensonic): canonicalize xml to use the same string for section_type
471 # and role. Needs fixes on gtk-doc.xsl
472 if role is None:
473 role = section_type.replace('-', '_')
475 return DB_REFSECT1_DESC.substitute({
476 'content': content,
477 'role': role,
478 'section_id': section_id,
479 'title': title,
480 'type': section_type,
481 })
484def OutputDB(file, options):
485 """Generate docbook files.
487 This collects the output for each section of the docs, and outputs each file
488 when the end of the section is found.
490 Args:
491 file (str): the $MODULE-sections.txt file which contains all of the
492 functions/macros/structs etc. being documented, organised
493 into sections and subsections.
494 options: commandline options
495 """
497 logging.info("Reading: %s", file)
498 INPUT = open(file, 'r', encoding='utf-8')
499 filename = ''
500 book_top = ''
501 book_bottom = ''
502 includes = options.default_includes or ''
503 section_includes = ''
504 in_section = 0
505 title = ''
506 section_id = ''
507 subsection = ''
508 num_symbols = 0
509 changed = 0
510 functions_synop = ''
511 other_synop = ''
512 functions_details = ''
513 other_details = ''
514 other_desc = ''
515 signals_synop = ''
516 signals_desc = ''
517 args_synop = ''
518 child_args_synop = ''
519 style_args_synop = ''
520 args_desc = ''
521 child_args_desc = ''
522 style_args_desc = ''
523 actions_synop = ''
524 actions_desc = ''
525 hierarchy_str = ''
526 hierarchy = []
527 interfaces = ''
528 implementations = ''
529 prerequisites = ''
530 derived = ''
531 file_objects = []
532 file_def_line = {}
533 symbol_def_line = {}
535 MergeSourceDocumentation()
537 line_number = 0
538 for line in INPUT:
539 line_number += 1
541 if line.startswith('#'):
542 continue
544 logging.info("section file data: %d: %s", line_number, line)
546 m1 = re.search(r'^<SUBSECTION\s*(.*)>', line, re.I)
547 m2 = re.search(r'^<TITLE>(.*)<\/TITLE', line)
548 m3 = re.search(r'^<FILE>(.*)<\/FILE>', line)
549 m4 = re.search(r'^<INCLUDE>(.*)<\/INCLUDE>', line)
550 m5 = re.search(r'^(\S+)', line)
552 if line.startswith('<SECTION>'):
553 num_symbols = 0
554 in_section = False
555 file_objects = []
556 symbol_def_line = {}
558 elif m1:
559 other_synop += "\n"
560 functions_synop += "\n"
561 subsection = m1.group(1)
563 elif line.startswith('<SUBSECTION>'):
564 continue
565 elif m2:
566 title = m2.group(1)
567 logging.info("Section: %s", title)
569 # We don't want warnings if object & class structs aren't used.
570 DeclarationOutput[title] = 1
571 DeclarationOutput["%sClass" % title] = 1
572 DeclarationOutput["%sIface" % title] = 1
573 DeclarationOutput["%sInterface" % title] = 1
575 elif m3:
576 filename = m3.group(1)
577 if filename not in file_def_line:
578 file_def_line[filename] = line_number
579 else:
580 common.LogWarning(file, line_number, "Double <FILE>%s</FILE> entry. Previous occurrence on line %s." %
581 (filename, file_def_line[filename]))
582 if title == '':
583 key = filename + ":title"
584 if key in SourceSymbolDocs:
585 title = SourceSymbolDocs[key].rstrip()
587 elif m4:
588 if in_section:
589 section_includes = m4.group(1)
590 else:
591 if options.default_includes:
592 common.LogWarning(file, line_number, "Default <INCLUDE> being overridden by command line option.")
593 else:
594 includes = m4.group(1)
596 elif re.search(r'^<\/SECTION>', line):
597 logging.info("End of section: %s", title)
598 # TODO: also output if we have sections docs?
599 # long_desc = SymbolDocs.get(filename + ":long_description")
600 if num_symbols > 0:
601 # collect documents
602 book_bottom += " <xi:include href=\"xml/%s.xml\"/>\n" % filename
604 key = filename + ":include"
605 if key in SourceSymbolDocs:
606 if section_includes:
607 common.LogWarning(file, line_number, "Section <INCLUDE> being overridden by inline comments.")
608 section_includes = SourceSymbolDocs[key]
610 if section_includes == '':
611 section_includes = includes
613 signals_synop = trim_leading_and_trailing_nl(signals_synop)
614 if signals_synop != '':
615 signals_synop = make_refsect1_synopsis3(
616 signals_synop, 'Signals', section_id, 'signals', 'signal_proto')
617 signals_desc = make_refsect1_desc(signals_desc, 'Signal Details',
618 section_id, 'signal-details', 'signals')
620 args_synop = trim_leading_and_trailing_nl(args_synop)
621 if args_synop != '':
622 args_synop = make_refsect1_synopsis3(args_synop, 'Properties', section_id, 'properties')
623 args_desc = make_refsect1_desc(args_desc, 'Property Details', section_id, 'property-details')
625 child_args_synop = trim_leading_and_trailing_nl(child_args_synop)
626 if child_args_synop != '':
627 args_synop += make_refsect1_synopsis3(child_args_synop,
628 'Child Properties', section_id, 'child-properties')
629 args_desc += make_refsect1_desc(child_args_desc, 'Child Property Details',
630 section_id, 'child-property-details')
632 style_args_synop = trim_leading_and_trailing_nl(style_args_synop)
633 if style_args_synop != '':
634 args_synop += make_refsect1_synopsis3(style_args_synop,
635 'Style Properties', section_id, 'style-properties')
636 args_desc += make_refsect1_desc(style_args_desc, 'Style Property Details',
637 section_id, 'style-property-details')
639 actions_synop = re.sub(r'^\n*', '', actions_synop)
640 actions_synop = re.sub(r'\n+$', '\n', actions_synop)
641 if actions_synop != '':
642 actions_synop = '''<refsect1 id="%s.actions" role="actions">
643<title role="actions.title">Actions</title>
644<informaltable frame="none">
645<tgroup cols="3">
646<colspec colname="actions_none" colwidth="150px"/>
647<colspec colname="actions_name" colwidth="300px"/>
648<colspec colname="actions_param" colwidth="200px"/>
649<tbody>
650%s
651</tbody>
652</tgroup>
653</informaltable>
654</refsect1>
655''' % (section_id, actions_synop)
656 actions_desc = trim_leading_and_trailing_nl(actions_desc)
657 actions_desc = '''<refsect1 id="%s.action-details" role="action_details">
658<title role="action_details.title">Action Details</title>
659%s
660</refsect1>
661''' % (section_id, actions_desc)
663 hierarchy_str = AddTreeLineArt(hierarchy)
664 if hierarchy_str != '':
665 hierarchy_str = make_refsect1_desc('<screen>' + hierarchy_str + '\n</screen>',
666 'Object Hierarchy', section_id, 'object-hierarchy')
668 interfaces = make_refsect1_desc(interfaces, 'Implemented Interfaces', section_id,
669 'implemented-interfaces', 'impl_interfaces')
670 implementations = make_refsect1_desc(
671 implementations, 'Known Implementations', section_id, 'implementations')
672 prerequisites = make_refsect1_desc(prerequisites, 'Prerequisites', section_id, 'prerequisites')
673 derived = make_refsect1_desc(derived, 'Known Derived Interfaces', section_id, 'derived-interfaces')
675 functions_synop = trim_leading_and_trailing_nl(functions_synop)
676 if functions_synop != '':
677 functions_synop = make_refsect1_synopsis2(
678 functions_synop, 'Functions', section_id, 'functions', 'functions_proto')
680 other_synop = trim_leading_and_trailing_nl(other_synop)
681 if other_synop != '':
682 other_synop = make_refsect1_synopsis2(
683 other_synop, 'Types and Values', section_id, 'other', 'other_proto')
684 other_desc += make_refsect1_desc(other_details, 'Types and Values',
685 section_id, 'other_details', 'details')
687 file_changed = OutputDBFile(filename, title, section_id,
688 section_includes,
689 functions_synop, other_synop,
690 functions_details, other_desc,
691 signals_synop, signals_desc,
692 args_synop, args_desc,
693 actions_synop, actions_desc,
694 hierarchy_str, interfaces,
695 implementations,
696 prerequisites, derived,
697 file_objects,
698 options.default_stability)
699 if file_changed:
700 changed = True
702 title = ''
703 section_id = ''
704 subsection = ''
705 in_section = 0
706 section_includes = ''
707 functions_synop = ''
708 other_synop = ''
709 functions_details = ''
710 other_details = ''
711 other_desc = ''
712 signals_synop = ''
713 signals_desc = ''
714 args_synop = ''
715 child_args_synop = ''
716 style_args_synop = ''
717 args_desc = ''
718 child_args_desc = ''
719 style_args_desc = ''
720 actions_synop = ''
721 actions_desc = ''
722 hierarchy_str = ''
723 hierarchy = []
724 interfaces = ''
725 implementations = ''
726 prerequisites = ''
727 derived = ''
729 elif m5:
730 symbol = m5.group(1)
731 logging.info(' Symbol: "%s" in subsection: "%s"', symbol, subsection)
733 # check for duplicate entries
734 if symbol not in symbol_def_line:
735 declaration = Declarations.get(symbol)
736 # FIXME: with this we'll output empty declaration
737 if declaration is not None:
738 if CheckIsObject(symbol):
739 file_objects.append(symbol)
741 # We don't want standard macros/functions of GObjects,
742 # or private declarations.
743 if subsection != "Standard" and subsection != "Private":
744 synop, desc = OutputDeclaration(symbol, declaration)
745 type = DeclarationTypes[symbol]
747 if type == 'FUNCTION' or type == 'USER_FUNCTION':
748 functions_synop += synop
749 functions_details += desc
750 elif type == 'MACRO' and re.search(symbol + r'\(', declaration):
751 functions_synop += synop
752 functions_details += desc
753 else:
754 other_synop += synop
755 other_details += desc
757 sig_synop, sig_desc = GetSignals(symbol)
758 arg_synop, child_arg_synop, style_arg_synop, arg_desc, child_arg_desc, style_arg_desc = GetArgs(
759 symbol)
760 action_synop, action_desc = GetActions(symbol)
761 ifaces = GetInterfaces(symbol)
762 impls = GetImplementations(symbol)
763 prereqs = GetPrerequisites(symbol)
764 der = GetDerived(symbol)
765 hierarchy = GetHierarchy(symbol, hierarchy)
767 signals_synop += sig_synop
768 signals_desc += sig_desc
769 args_synop += arg_synop
770 child_args_synop += child_arg_synop
771 style_args_synop += style_arg_synop
772 args_desc += arg_desc
773 child_args_desc += child_arg_desc
774 style_args_desc += style_arg_desc
775 actions_synop += action_synop
776 actions_desc += action_desc
777 interfaces += ifaces
778 implementations += impls
779 prerequisites += prereqs
780 derived += der
782 # Note that the declaration has been output.
783 DeclarationOutput[symbol] = True
784 elif subsection != "Standard" and subsection != "Private":
785 UndeclaredSymbols[symbol] = True
786 common.LogWarning(file, line_number, "No declaration found for %s." % symbol)
788 num_symbols += 1
789 symbol_def_line[symbol] = line_number
791 if section_id == '':
792 if title == '' and filename == '':
793 common.LogWarning(file, line_number, "Section has no title and no file.")
795 # FIXME: one of those would be enough
796 # filename should be an internal detail for gtk-doc
797 if title == '':
798 title = filename
799 elif filename == '':
800 filename = title
802 filename = filename.replace(' ', '_')
804 section_id = SourceSymbolDocs.get(filename + ":section_id")
805 if section_id and section_id.strip() != '':
806 # Remove trailing blanks and use as is
807 section_id = section_id.rstrip()
808 elif CheckIsObject(title):
809 # GObjects use their class name as the ID.
810 section_id = common.CreateValidSGMLID(title)
811 else:
812 section_id = common.CreateValidSGMLID(MODULE + '-' + title)
814 SymbolSection[symbol] = title
815 SymbolSectionId[symbol] = section_id
817 else:
818 common.LogWarning(file, line_number, "Double symbol entry for %s. "
819 "Previous occurrence on line %d." % (symbol, symbol_def_line[symbol]))
820 INPUT.close()
822 OutputMissingDocumentation()
823 OutputUndeclaredSymbols()
824 OutputUnusedSymbols()
826 if options.outputallsymbols:
827 OutputAllSymbols()
829 if options.outputsymbolswithoutsince:
830 OutputSymbolsWithoutSince()
832 for filename in options.expand_content_files.split():
833 file_changed = OutputExtraFile(filename)
834 if file_changed:
835 changed = True
837 return (changed, book_top, book_bottom)
840def DetermineNamespace(symbols):
841 """Find common set of characters.
843 Args:
844 symbols (list): a list of symbols to scan for a common prefix
846 Returns:
847 str: a common namespace prefix (might be empty)
848 """
849 name_space = ''
850 pos = 0
851 ratio = 0.0
852 while True:
853 prefix = {}
854 letter = ''
855 for symbol in symbols:
856 if name_space == '' or name_space.lower() in symbol.lower():
857 if len(symbol) > pos:
858 letter = symbol[pos:pos + 1]
859 # stop prefix scanning
860 if letter == "_":
861 # stop on "_"
862 break
863 # Should we also stop on a uppercase char, if last was lowercase
864 # GtkWidget, if we have the 'W' and had the 't' before
865 # or should we count upper and lowercase, and stop one 2nd uppercase, if we already had a lowercase
866 # GtkWidget, the 'W' would be the 2nd uppercase and with 't','k' we had lowercase chars before
867 # need to recound each time as this is per symbol
868 ul = letter.upper()
869 if ul in prefix:
870 prefix[ul] += 1
871 else:
872 prefix[ul] = 1
874 if letter != '' and letter != "_":
875 maxletter = ''
876 maxsymbols = 0
877 for letter in prefix.keys():
878 logging.debug("ns prefix: %s: %s", letter, prefix[letter])
879 if prefix[letter] > maxsymbols:
880 maxletter = letter
881 maxsymbols = prefix[letter]
883 ratio = float(len(symbols)) / prefix[maxletter]
884 logging.debug('most symbols start with %s, that is %f', maxletter, (100 * ratio))
885 if ratio > 0.9:
886 # do another round
887 name_space += maxletter
889 pos += 1
891 else:
892 ratio = 0.0
894 if ratio < 0.9:
895 break
896 return name_space
899def OutputIndex(basename, apiindex):
900 """Writes an index that can be included into the main-document into an <index> tag.
902 Args:
903 basename (str): name of the index file without extension
904 apiindex (dict): the index data
905 """
906 old_index = os.path.join(DB_OUTPUT_DIR, basename + '.xml')
907 new_index = os.path.join(DB_OUTPUT_DIR, basename + '.new')
908 lastletter = " "
909 divopen = 0
910 symbol = None
911 short_symbol = None
913 OUTPUT = open(new_index, 'w')
915 OUTPUT.write(MakeDocHeader("indexdiv") + "\n<indexdiv id=\"%s\">\n" % basename)
917 logging.info("generate %s index (%d entries) with namespace %s", basename, len(apiindex), NAME_SPACE)
919 # do a case insensitive sort while chopping off the prefix
920 mapped_keys = [
921 {
922 'original': x,
923 'short': re.sub(r'^' + NAME_SPACE + r'\_?(.*)', r'\1', x.upper(), flags=re.I),
924 } for x in apiindex.keys()]
925 sorted_keys = sorted(mapped_keys, key=lambda d: (d['short'], d['original']))
927 for key in sorted_keys:
928 symbol = key['original']
929 short = key['short']
930 if short != '':
931 short_symbol = short
932 else:
933 short_symbol = symbol
935 # generate a short symbol description
936 symbol_desc = ''
937 symbol_section = ''
938 symbol_section_id = ''
939 symbol_type = ''
940 if symbol in DeclarationTypes:
941 symbol_type = DeclarationTypes[symbol].lower()
943 if symbol_type == '':
944 logging.info("trying symbol %s", symbol)
945 m1 = re.search(r'(.*)::(.*)', symbol)
946 m2 = re.search(r'(.*):(.*)', symbol)
947 m3 = re.search(r'(.*)\|(.*)', symbol)
948 if m1:
949 oname = m1.group(1)
950 osym = m1.group(2)
951 logging.info(" trying object signal %s:%s in %d signals", oname, osym, len(SignalNames))
952 for name in SignalNames:
953 logging.info(" " + name)
954 if name == osym:
955 symbol_type = "object signal"
956 if oname in SymbolSection:
957 symbol_section = SymbolSection[oname]
958 symbol_section_id = SymbolSectionId[oname]
959 break
960 elif m2:
961 oname = m2.group(1)
962 osym = m2.group(2)
963 logging.info(" trying object property %s::%s in %d properties", oname, osym, len(ArgNames))
964 for name in ArgNames:
965 logging.info(" " + name)
966 if name == osym:
967 symbol_type = "object property"
968 if oname in SymbolSection:
969 symbol_section = SymbolSection[oname]
970 symbol_section_id = SymbolSectionId[oname]
971 break
972 elif m3:
973 oname = m3.group(1)
974 osym = m3.group(2)
975 logging.info(" trying action %s|%s in %d actions", oname, osym, len(ActionNames))
976 for name in ActionNames:
977 logging.info(" " + name)
978 if name == osym:
979 symbol_type = "action"
980 if oname in SymbolSection:
981 symbol_section = SymbolSection[oname]
982 symbol_section_id = SymbolSectionId[oname]
983 break
984 else:
985 if symbol in SymbolSection:
986 symbol_section = SymbolSection[symbol]
987 symbol_section_id = SymbolSectionId[symbol]
989 if symbol_type != '':
990 symbol_desc = ", " + symbol_type
991 if symbol_section != '':
992 symbol_desc += " in <link linkend=\"%s\">%s</link>" % (symbol_section_id, symbol_section)
993 # symbol_desc +=" in " + ExpandAbbreviations(symbol, "#symbol_section")
995 curletter = short_symbol[0].upper()
996 ixid = apiindex[symbol]
998 logging.info(" add symbol %s with %s to index in section '%s' (derived from %s)",
999 symbol, ixid, curletter, short_symbol)
1001 if curletter != lastletter:
1002 lastletter = curletter
1004 if divopen:
1005 OUTPUT.write("</indexdiv>\n")
1007 OUTPUT.write("<indexdiv><title>%s</title>\n" % curletter)
1008 divopen = True
1010 OUTPUT.write('<indexentry><primaryie linkends="%s"><link linkend="%s">%s</link>%s</primaryie></indexentry>\n' %
1011 (ixid, ixid, symbol, symbol_desc))
1013 if divopen:
1014 OUTPUT.write("</indexdiv>\n")
1016 OUTPUT.write("</indexdiv>\n")
1017 OUTPUT.close()
1019 common.UpdateFileIfChanged(old_index, new_index, 0)
1022def OutputSinceIndexes():
1023 """Generate the 'since' api index files."""
1024 for version in sorted(set(Since.values())):
1025 logging.info("Since : [%s]", version)
1026 index = {x: IndexEntriesSince[x] for x in IndexEntriesSince.keys() if Since[x] == version}
1027 OutputIndex("api-index-" + version, index)
1030def OutputAnnotationGlossary():
1031 """Writes a glossary of the used annotation terms.
1033 The glossary file can be included into the main document.
1034 """
1035 # if there are no annotations used return
1036 if not AnnotationsUsed:
1037 return
1039 old_glossary = os.path.join(DB_OUTPUT_DIR, "annotation-glossary.xml")
1040 new_glossary = os.path.join(DB_OUTPUT_DIR, "annotation-glossary.new")
1041 lastletter = " "
1042 divopen = False
1044 # add acronyms that are referenced from acronym text
1045 rerun = True
1046 while rerun:
1047 rerun = False
1048 for annotation in AnnotationsUsed:
1049 if annotation not in AnnotationDefinition:
1050 continue
1051 m = re.search(r'<acronym>([\w ]+)<\/acronym>', AnnotationDefinition[annotation])
1052 if m and m.group(1) not in AnnotationsUsed:
1053 AnnotationsUsed[m.group(1)] = 1
1054 rerun = True
1055 break
1057 OUTPUT = open(new_glossary, 'w', encoding='utf-8')
1059 OUTPUT.write('''%s
1060<glossary id="annotation-glossary">
1061 <title>Annotation Glossary</title>
1062''' % MakeDocHeader("glossary"))
1064 for annotation in sorted(AnnotationsUsed.keys(), key=str.lower):
1065 if annotation in AnnotationDefinition:
1066 definition = AnnotationDefinition[annotation]
1067 curletter = annotation[0].upper()
1069 if curletter != lastletter:
1070 lastletter = curletter
1072 if divopen:
1073 OUTPUT.write("</glossdiv>\n")
1075 OUTPUT.write("<glossdiv><title>%s</title>\n" % curletter)
1076 divopen = True
1078 OUTPUT.write(''' <glossentry>
1079 <glossterm><anchor id="annotation-glossterm-%s"/>%s</glossterm>
1080 <glossdef>
1081 <para>%s</para>
1082 </glossdef>
1083 </glossentry>
1084''' % (annotation, annotation, definition))
1086 if divopen:
1087 OUTPUT.write("</glossdiv>\n")
1089 OUTPUT.write("</glossary>\n")
1090 OUTPUT.close()
1092 common.UpdateFileIfChanged(old_glossary, new_glossary, 0)
1095def OutputObjectTree(tree):
1096 if not tree:
1097 return
1099 # FIXME: use xml
1100 old_tree_index = os.path.join(DB_OUTPUT_DIR, "tree_index.sgml")
1101 new_tree_index = os.path.join(DB_OUTPUT_DIR, "tree_index.new")
1103 with open(new_tree_index, 'w', encoding='utf-8') as out:
1104 out.write(MakeDocHeader("screen"))
1105 out.write("\n<screen>\n")
1106 out.write(AddTreeLineArt(tree))
1107 out.write("\n</screen>\n")
1109 common.UpdateFileIfChanged(old_tree_index, new_tree_index, 0)
1112def ReadKnownSymbols(file):
1113 """Collect the names of non-private symbols from the $MODULE-sections.txt file.
1115 Args:
1116 file: the $MODULE-sections.txt file
1117 """
1119 subsection = ''
1121 logging.info("Reading: %s", file)
1122 INPUT = open(file, 'r', encoding='utf-8')
1123 for line in INPUT:
1124 if line.startswith('#'):
1125 continue
1127 if line.startswith('<SECTION>'):
1128 subsection = ''
1129 continue
1131 m = re.search(r'^<SUBSECTION\s*(.*)>', line, flags=re.I)
1132 if m:
1133 subsection = m.group(1)
1134 continue
1136 if line.startswith('<SUBSECTION>'):
1137 continue
1139 if re.search(r'^<TITLE>(.*)<\/TITLE>', line):
1140 continue
1142 m = re.search(r'^<FILE>(.*)<\/FILE>', line)
1143 if m:
1144 KnownSymbols[m.group(1) + ":long_description"] = 1
1145 KnownSymbols[m.group(1) + ":short_description"] = 1
1146 continue
1148 m = re.search(r'^<INCLUDE>(.*)<\/INCLUDE>', line)
1149 if m:
1150 continue
1152 m = re.search(r'^<\/SECTION>', line)
1153 if m:
1154 continue
1156 m = re.search(r'^(\S+)', line)
1157 if m:
1158 symbol = m.group(1)
1159 if subsection != "Standard" and subsection != "Private":
1160 KnownSymbols[symbol] = 1
1161 else:
1162 KnownSymbols[symbol] = 0
1163 INPUT.close()
1166def OutputDeclaration(symbol, declaration):
1167 """Returns the formatted documentation block for a symbol.
1169 Args:
1170 symbol (str): the name of the function/macro/...
1171 declaration (str): the declaration of the function/macro.
1173 Returns:
1174 str: the formatted documentation
1175 """
1177 dtype = DeclarationTypes[symbol]
1178 logging.info('Output Symbol: "%s" "%s"', symbol, dtype)
1179 if dtype == 'MACRO':
1180 return OutputMacro(symbol, declaration)
1181 elif dtype == 'TYPEDEF':
1182 return OutputTypedef(symbol, declaration)
1183 elif dtype == 'STRUCT':
1184 return OutputStruct(symbol, declaration)
1185 elif dtype == 'ENUM':
1186 return OutputEnum(symbol, declaration)
1187 elif dtype == 'UNION':
1188 return OutputUnion(symbol, declaration)
1189 elif dtype == 'VARIABLE':
1190 return OutputVariable(symbol, declaration)
1191 elif dtype == 'FUNCTION':
1192 return OutputFunction(symbol, declaration, dtype)
1193 elif dtype == 'USER_FUNCTION':
1194 return OutputFunction(symbol, declaration, dtype)
1195 else:
1196 logging.warning("Unknown symbol type %s for symbol %s", dtype, symbol)
1197 return ('', '')
1200def OutputSymbolTraits(symbol):
1201 """Returns the Since and StabilityLevel paragraphs for a symbol.
1203 Args:
1204 symbol (str): the name to describe
1206 Returns:
1207 str: paragraph or empty string
1208 """
1210 desc = ''
1212 if symbol in Since:
1213 link_id = "api-index-" + Since[symbol]
1214 desc += "<para role=\"since\">Since: <link linkend=\"%s\">%s</link></para>" % (link_id, Since[symbol])
1216 if symbol in StabilityLevel:
1217 stability = StabilityLevel[symbol]
1218 if stability in AnnotationDefinition:
1219 AnnotationsUsed[stability] = True
1220 stability = "<acronym>%s</acronym>" % stability
1221 desc += "<para role=\"stability\">Stability Level: %s</para>" % stability
1222 return desc
1225def uri_escape(text):
1226 if text is None:
1227 return None
1229 # Build a char to hex map
1230 escapes = {chr(i): ("%%%02X" % i) for i in range(256)}
1232 # Default unsafe characters. RFC 2732 ^(uric - reserved)
1233 def do_escape(char):
1234 return escapes[char]
1235 return re.sub(r"([^A-Za-z0-9\-_.!~*'()]", do_escape, text)
1238def extract_struct_body(symbol, decl, has_typedef, public):
1239 """Returns a normalized struct body.
1241 Normalizes whitespace and newlines and supresses non public members.
1243 Returns:
1244 str: the nomalized struct declaration
1245 """
1246 decl_out = ''
1248 m = re.search(
1249 r'^\s*(typedef\s+)?struct\s*\w*\s*(?:\/\*.*\*\/)?\s*{(.*)}\s*\w*\s*;\s*$', decl, flags=re.DOTALL)
1250 if m:
1251 new_boby = ''
1252 for line in m.group(2).splitlines():
1253 logging.info("Struct line: %s", line)
1254 m2 = re.search(r'/\*\s*<\s*public\s*>\s*\*/', line)
1255 m3 = re.search(r'/\*\s*<\s*(private|protected)\s*>\s*\*/', line)
1256 if m2:
1257 public = True
1258 elif m3:
1259 public = False
1260 elif public:
1261 new_boby += line + "\n"
1263 if new_boby:
1264 # Strip any blank lines off the ends.
1265 new_boby = re.sub(r'^\s*\n', '', new_boby)
1266 new_boby = re.sub(r'\n\s*$', r'\n', new_boby)
1268 if has_typedef:
1269 decl_out = "typedef struct {\n%s} %s;\n" % (new_boby, symbol)
1270 else:
1271 decl_out = "struct %s {\n%s};\n" % (symbol, new_boby)
1272 else:
1273 common.LogWarning(*GetSymbolSourceLocation(symbol),
1274 "Couldn't parse struct:\n%s" % decl)
1276 if decl_out == '':
1277 # If we couldn't parse the struct or it was all private, output an
1278 # empty struct declaration.
1279 if has_typedef:
1280 decl_out = "typedef struct _%s %s;" % (symbol, symbol)
1281 else:
1282 decl_out = "struct %s;" % symbol
1284 return decl_out
1287def OutputSymbolExtraLinks(symbol):
1288 """Returns extralinks for the symbol (if enabled).
1290 Args:
1291 symbol (str): the name to describe
1293 Returns:
1294 str: paragraph or empty string
1295 """
1296 desc = ''
1298 if False: # NEW FEATURE: needs configurability
1299 sstr = uri_escape(symbol)
1300 mstr = uri_escape(MODULE)
1301 desc += '''<ulink role="extralinks" url="http://www.google.com/codesearch?q=%s">code search</ulink>
1302<ulink role="extralinks" url="http://library.gnome.org/edit?module=%s&symbol=%s">edit documentation</ulink>
1303''' % (sstr, mstr, sstr)
1305 return desc
1308def OutputSectionExtraLinks(symbol, docsymbol):
1309 desc = ''
1311 if False: # NEW FEATURE: needs configurability
1312 sstr = uri_escape(symbol)
1313 mstr = uri_escape(MODULE)
1314 dsstr = uri_escape(docsymbol)
1315 desc += '''<ulink role="extralinks" url="http://www.google.com/codesearch?q=%s">code search</ulink>
1316<ulink role="extralinks" url="http://library.gnome.org/edit?module=%s&symbol=%s">edit documentation</ulink>
1317''' % (sstr, mstr, dsstr)
1318 return desc
1321def OutputMacro(symbol, declaration):
1322 """Returns the synopsis and detailed description of a macro.
1324 Args:
1325 symbol (str): the macro name.
1326 declaration (str): the declaration of the macro.
1328 Returns:
1329 str: the formatted docs
1330 """
1331 sid = common.CreateValidSGMLID(symbol)
1332 condition = MakeConditionDescription(symbol)
1333 synop = "<row><entry role=\"define_keyword\">#define</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link>" % (
1334 sid, symbol)
1336 fields = common.ParseMacroDeclaration(declaration, CreateValidSGML)
1337 title = symbol
1338 if len(fields) > 0:
1339 title += '()'
1341 desc = '<refsect2 id="%s" role="macro"%s>\n<title>%s</title>\n' % (sid, condition, title)
1342 desc += MakeIndexterms(symbol, sid)
1343 desc += "\n"
1344 desc += OutputSymbolExtraLinks(symbol)
1346 if len(fields) > 0:
1347 synop += "<phrase role=\"c_punctuation\">()</phrase>"
1349 synop += "</entry></row>\n"
1351 # Don't output the macro definition if is is a conditional macro or it
1352 # looks like a function, i.e. starts with "g_" or "_?gnome_", or it is
1353 # longer than 2 lines, otherwise we get lots of complicated macros like
1354 # g_assert.
1355 if symbol not in DeclarationConditional and not symbol.startswith('g_') \
1356 and not re.search(r'^_?gnome_', symbol) and declaration.count('\n') < 2:
1357 decl_out = CreateValidSGML(declaration)
1358 desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out
1359 else:
1360 desc += "<programlisting language=\"C\">" + "#define".ljust(RETURN_TYPE_FIELD_WIDTH) + symbol
1361 m = re.search(r'^\s*#\s*define\s+\w+(\([^\)]*\))', declaration)
1362 if m:
1363 args = m.group(1)
1364 pad = ' ' * (RETURN_TYPE_FIELD_WIDTH - len("#define "))
1365 # Align each line so that if should all line up OK.
1366 args = args.replace('\n', '\n' + pad)
1367 desc += CreateValidSGML(args)
1369 desc += "</programlisting>\n"
1371 desc += MakeDeprecationNote(symbol)
1373 parameters = OutputParamDescriptions("MACRO", symbol, fields)
1375 if symbol in SymbolDocs:
1376 symbol_docs = ConvertMarkDown(symbol, SymbolDocs[symbol])
1377 desc += symbol_docs
1379 desc += parameters
1380 desc += OutputSymbolTraits(symbol)
1381 desc += "</refsect2>\n"
1382 return (synop, desc)
1385def OutputTypedef(symbol, declaration):
1386 """Returns the synopsis and detailed description of a typedef.
1388 Args:
1389 symbol (str): the typedef.
1390 declaration (str): the declaration of the typedef,
1391 e.g. 'typedef unsigned int guint;'
1393 Returns:
1394 str: the formatted docs
1395 """
1396 sid = common.CreateValidSGMLID(symbol)
1397 condition = MakeConditionDescription(symbol)
1398 desc = "<refsect2 id=\"%s\" role=\"typedef\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
1399 synop = "<row><entry role=\"typedef_keyword\">typedef</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1400 sid, symbol)
1402 desc += MakeIndexterms(symbol, sid)
1403 desc += "\n"
1404 desc += OutputSymbolExtraLinks(symbol)
1406 if symbol not in DeclarationConditional:
1407 decl_out = CreateValidSGML(declaration)
1408 desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out
1410 desc += MakeDeprecationNote(symbol)
1412 if symbol in SymbolDocs:
1413 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1415 desc += OutputSymbolTraits(symbol)
1416 desc += "</refsect2>\n"
1417 return (synop, desc)
1420def OutputStruct(symbol, declaration):
1421 """Returns the synopsis and detailed description of a struct.
1423 We check if it is a object struct, and if so we only output parts of it that
1424 are noted as public fields. We also use a different IDs for object structs,
1425 since the original ID is used for the entire RefEntry.
1427 Args:
1428 symbol (str): the struct.
1429 declaration (str): the declaration of the struct.
1431 Returns:
1432 str: the formatted docs
1433 """
1434 is_gtype = False
1435 default_to_public = True
1436 if CheckIsObject(symbol):
1437 logging.info("Found struct gtype: %s", symbol)
1438 is_gtype = True
1439 default_to_public = ObjectRoots[symbol] == 'GBoxed'
1441 sid = None
1442 condition = None
1443 if is_gtype:
1444 sid = common.CreateValidSGMLID(symbol + "_struct")
1445 condition = MakeConditionDescription(symbol + "_struct")
1446 else:
1447 sid = common.CreateValidSGMLID(symbol)
1448 condition = MakeConditionDescription(symbol)
1450 # Determine if it is a simple struct or it also has a typedef.
1451 has_typedef = False
1452 if symbol in StructHasTypedef or re.search(r'^\s*typedef\s+', declaration):
1453 has_typedef = True
1455 type_output = None
1456 desc = None
1457 if has_typedef:
1458 # For structs with typedefs we just output the struct name.
1459 type_output = ''
1460 desc = "<refsect2 id=\"%s\" role=\"struct\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
1461 else:
1462 type_output = "struct"
1463 desc = "<refsect2 id=\"%s\" role=\"struct\"%s>\n<title>struct %s</title>\n" % (sid, condition, symbol)
1465 synop = "<row><entry role=\"datatype_keyword\">%s</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1466 type_output, sid, symbol)
1468 desc += MakeIndexterms(symbol, sid)
1469 desc += "\n"
1470 desc += OutputSymbolExtraLinks(symbol)
1472 # Form a pretty-printed, private-data-removed form of the declaration
1474 decl_out = ''
1475 if re.search(r'^\s*$', declaration):
1476 logging.info("Found opaque struct: %s", symbol)
1477 decl_out = "typedef struct _%s %s;" % (symbol, symbol)
1478 elif re.search(r'^\s*struct\s+\w+\s*;\s*$', declaration):
1479 logging.info("Found opaque struct: %s", symbol)
1480 decl_out = "struct %s;" % symbol
1481 else:
1482 decl_out = extract_struct_body(symbol, declaration, has_typedef, default_to_public)
1484 decl_out = CreateValidSGML(decl_out)
1485 desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out
1487 desc += MakeDeprecationNote(symbol)
1489 if symbol in SymbolDocs:
1490 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1492 # Create a table of fields and descriptions
1494 # FIXME: Inserting  's into the produced type declarations here would
1495 # improve the output in most situations ... except for function
1496 # members of structs!
1497 def pfunc(*args):
1498 return '<structfield id="%s">%s</structfield>' % (common.CreateValidSGMLID(sid + '.' + args[0]), args[0])
1499 fields = common.ParseStructDeclaration(declaration, not default_to_public, 0, MakeXRef, pfunc)
1500 field_descrs, found = GetSymbolParams(symbol)
1502 if found:
1503 missing_parameters = ''
1504 unused_parameters = ''
1505 sid = common.CreateValidSGMLID(symbol + ".members")
1507 desc += '''<refsect3 id="%s" role="struct_members">\n<title>Members</title>
1508<informaltable role="struct_members_table" pgwide="1" frame="none">
1509<tgroup cols="3">
1510<colspec colname="struct_members_name" colwidth="300px"/>
1511<colspec colname="struct_members_description"/>
1512<colspec colname="struct_members_annotations" colwidth="200px"/>
1513<tbody>
1514''' % sid
1516 for field_name, text in fields.items():
1517 param_annotations = ''
1519 desc += "<row role=\"member\"><entry role=\"struct_member_name\"><para>%s</para></entry>\n" % text
1520 if field_name in field_descrs:
1521 field_descr, param_annotations = ExpandAnnotation(symbol, field_descrs[field_name])
1522 field_descr = ConvertMarkDown(symbol, field_descr)
1523 # trim
1524 field_descr = re.sub(r'^(\s|\n)+', '', field_descr, flags=re.M | re.S)
1525 field_descr = re.sub(r'(\s|\n)+$', '', field_descr, flags=re.M | re.S)
1526 desc += "<entry role=\"struct_member_description\">%s</entry>\n<entry role=\"struct_member_annotations\">%s</entry>\n" % (
1527 field_descr, param_annotations)
1528 del field_descrs[field_name]
1529 else:
1530 common.LogWarning(*GetSymbolSourceLocation(symbol),
1531 "Field description for %s::%s is missing in source code comment block." % (symbol, field_name))
1532 if missing_parameters != '':
1533 missing_parameters += ", " + field_name
1534 else:
1535 missing_parameters = field_name
1537 desc += "<entry /><entry />\n"
1539 desc += "</row>\n"
1541 desc += "</tbody></tgroup></informaltable>\n</refsect3>\n"
1542 for field_name in field_descrs:
1543 # Documenting those standard fields is not required anymore, but
1544 # we don't want to warn if they are documented anyway.
1545 m = re.search(r'(g_iface|parent_instance|parent_class)', field_name)
1546 if m:
1547 continue
1549 common.LogWarning(*GetSymbolSourceLocation(symbol),
1550 "Field description for %s::%s is not used from source code comment block." % (symbol, field_name))
1551 if unused_parameters != '':
1552 unused_parameters += ", " + field_name
1553 else:
1554 unused_parameters = field_name
1556 # remember missing/unused parameters (needed in tmpl-free build)
1557 if missing_parameters != '' and (symbol not in AllIncompleteSymbols):
1558 AllIncompleteSymbols[symbol] = missing_parameters
1560 if unused_parameters != '' and (symbol not in AllUnusedSymbols):
1561 AllUnusedSymbols[symbol] = unused_parameters
1562 else:
1563 if fields:
1564 if symbol not in AllIncompleteSymbols:
1565 AllIncompleteSymbols[symbol] = "<items>"
1566 common.LogWarning(*GetSymbolSourceLocation(symbol),
1567 "Field descriptions for struct %s are missing in source code comment block." % symbol)
1568 logging.info("Remaining structs fields: " + ':'.join(fields) + "\n")
1570 desc += OutputSymbolTraits(symbol)
1571 desc += "</refsect2>\n"
1572 return (synop, desc)
1575def OutputUnion(symbol, declaration):
1576 """Returns the synopsis and detailed description of a union.
1578 Args:
1579 symbol (str): the union.
1580 declaration (str): the declaration of the union.
1582 Returns:
1583 str: the formatted docs
1584 """
1585 is_gtype = False
1586 if CheckIsObject(symbol):
1587 logging.info("Found union gtype: %s", symbol)
1588 is_gtype = True
1590 sid = None
1591 condition = None
1592 if is_gtype:
1593 sid = common.CreateValidSGMLID(symbol + "_union")
1594 condition = MakeConditionDescription(symbol + "_union")
1595 else:
1596 sid = common.CreateValidSGMLID(symbol)
1597 condition = MakeConditionDescription(symbol)
1599 # Determine if it is a simple struct or it also has a typedef.
1600 has_typedef = False
1601 if symbol in StructHasTypedef or re.search(r'^\s*typedef\s+', declaration):
1602 has_typedef = True
1604 type_output = None
1605 desc = None
1606 if has_typedef:
1607 # For unions with typedefs we just output the union name.
1608 type_output = ''
1609 desc = "<refsect2 id=\"%s\" role=\"union\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
1610 else:
1611 type_output = "union"
1612 desc = "<refsect2 id=\"%s\" role=\"union\"%s>\n<title>union %s</title>\n" % (sid, condition, symbol)
1614 synop = "<row><entry role=\"datatype_keyword\">%s</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1615 type_output, sid, symbol)
1617 desc += MakeIndexterms(symbol, sid)
1618 desc += "\n"
1619 desc += OutputSymbolExtraLinks(symbol)
1620 desc += MakeDeprecationNote(symbol)
1622 if symbol in SymbolDocs:
1623 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1625 # Create a table of fields and descriptions
1627 # FIXME: Inserting  's into the produced type declarations here would
1628 # improve the output in most situations ... except for function
1629 # members of structs!
1630 def pfunc(*args):
1631 return '<structfield id="%s">%s</structfield>' % (common.CreateValidSGMLID(sid + '.' + args[0]), args[0])
1632 fields = common.ParseStructDeclaration(declaration, 0, 0, MakeXRef, pfunc)
1633 field_descrs, found = GetSymbolParams(symbol)
1635 logging.debug('Union %s has %d entries, found=%d, has_typedef=%d', symbol, len(fields), found, has_typedef)
1637 if found:
1638 missing_parameters = ''
1639 unused_parameters = ''
1640 sid = common.CreateValidSGMLID('%s.members' % symbol)
1642 desc += '''<refsect3 id="%s" role="union_members">\n<title>Members</title>
1643<informaltable role="union_members_table" pgwide="1" frame="none">
1644<tgroup cols="3">
1645<colspec colname="union_members_name" colwidth="300px"/>
1646<colspec colname="union_members_description"/>
1647<colspec colname="union_members_annotations" colwidth="200px"/>
1648<tbody>
1649''' % sid
1651 for field_name, text in fields.items():
1652 param_annotations = ''
1654 desc += "<row><entry role=\"union_member_name\"><para>%s</para></entry>\n" % text
1655 if field_name in field_descrs:
1656 field_descr, param_annotations = ExpandAnnotation(symbol, field_descrs[field_name])
1657 field_descr = ConvertMarkDown(symbol, field_descr)
1659 # trim
1660 field_descr = re.sub(r'^(\s|\n)+', '', field_descr, flags=re.M | re.S)
1661 field_descr = re.sub(r'(\s|\n)+$', '', field_descr, flags=re.M | re.S)
1662 desc += "<entry role=\"union_member_description\">%s</entry>\n<entry role=\"union_member_annotations\">%s</entry>\n" % (
1663 field_descr, param_annotations)
1664 del field_descrs[field_name]
1665 else:
1666 common.LogWarning(*GetSymbolSourceLocation(symbol),
1667 "Field description for %s::%s is missing in source code comment block." % (symbol, field_name))
1668 if missing_parameters != '':
1669 missing_parameters += ", " + field_name
1670 else:
1671 missing_parameters = field_name
1673 desc += "<entry /><entry />\n"
1675 desc += "</row>\n"
1677 desc += "</tbody></tgroup></informaltable>\n</refsect3>"
1678 for field_name in field_descrs:
1679 common.LogWarning(*GetSymbolSourceLocation(symbol),
1680 "Field description for %s::%s is not used from source code comment block." % (symbol, field_name))
1681 if unused_parameters != '':
1682 unused_parameters += ", " + field_name
1683 else:
1684 unused_parameters = field_name
1686 # remember missing/unused parameters (needed in tmpl-free build)
1687 if missing_parameters != '' and (symbol not in AllIncompleteSymbols):
1688 AllIncompleteSymbols[symbol] = missing_parameters
1690 if unused_parameters != '' and (symbol not in AllUnusedSymbols):
1691 AllUnusedSymbols[symbol] = unused_parameters
1692 else:
1693 if len(fields) > 0:
1694 if symbol not in AllIncompleteSymbols:
1695 AllIncompleteSymbols[symbol] = "<items>"
1696 common.LogWarning(*GetSymbolSourceLocation(symbol),
1697 "Field descriptions for union %s are missing in source code comment block." % symbol)
1698 logging.info("Remaining union fields: " + ':'.join(fields) + "\n")
1700 desc += OutputSymbolTraits(symbol)
1701 desc += "</refsect2>\n"
1702 return (synop, desc)
1705def OutputEnum(symbol, declaration):
1706 """Returns the synopsis and detailed description of a enum.
1708 Args:
1709 symbol (str): the enum.
1710 declaration (str): the declaration of the enum.
1712 Returns:
1713 str: the formatted docs
1714 """
1715 is_gtype = False
1716 if CheckIsObject(symbol):
1717 logging.info("Found enum gtype: %s", symbol)
1718 is_gtype = True
1720 sid = None
1721 condition = None
1722 if is_gtype:
1723 sid = common.CreateValidSGMLID(symbol + "_enum")
1724 condition = MakeConditionDescription(symbol + "_enum")
1725 else:
1726 sid = common.CreateValidSGMLID(symbol)
1727 condition = MakeConditionDescription(symbol)
1729 synop = "<row><entry role=\"datatype_keyword\">enum</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1730 sid, symbol)
1731 desc = "<refsect2 id=\"%s\" role=\"enum\"%s>\n<title>enum %s</title>\n" % (sid, condition, symbol)
1733 desc += MakeIndexterms(symbol, sid)
1734 desc += "\n"
1735 desc += OutputSymbolExtraLinks(symbol)
1736 desc += MakeDeprecationNote(symbol)
1738 if symbol in SymbolDocs:
1739 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1741 # Create a table of fields and descriptions
1743 fields = common.ParseEnumDeclaration(declaration)
1744 field_descrs, found = GetSymbolParams(symbol)
1746 missing_parameters = ''
1747 unused_parameters = ''
1749 sid = common.CreateValidSGMLID("%s.members" % symbol)
1750 desc += '''<refsect3 id="%s" role="enum_members">\n<title>Members</title>
1751<informaltable role="enum_members_table" pgwide="1" frame="none">
1752<tgroup cols="3">
1753<colspec colname="enum_members_name" colwidth="300px"/>
1754<colspec colname="enum_members_description"/>
1755<colspec colname="enum_members_annotations" colwidth="200px"/>
1756<tbody>
1757''' % sid
1759 for field_name in fields:
1760 field_descr = field_descrs.get(field_name)
1761 param_annotations = ''
1763 sid = common.CreateValidSGMLID(field_name)
1764 condition = MakeConditionDescription(field_name)
1765 desc += "<row role=\"constant\"><entry role=\"enum_member_name\"><para id=\"%s\">%s</para></entry>\n" % (
1766 sid, field_name)
1767 if field_descr:
1768 field_descr, param_annotations = ExpandAnnotation(symbol, field_descr)
1769 field_descr = ConvertMarkDown(symbol, field_descr)
1770 desc += "<entry role=\"enum_member_description\">%s</entry>\n<entry role=\"enum_member_annotations\">%s</entry>\n" % (
1771 field_descr, param_annotations)
1772 del field_descrs[field_name]
1773 else:
1774 if found:
1775 common.LogWarning(*GetSymbolSourceLocation(symbol),
1776 "Value description for %s::%s is missing in source code comment block." % (symbol, field_name))
1777 if missing_parameters != '':
1778 missing_parameters += ", " + field_name
1779 else:
1780 missing_parameters = field_name
1781 desc += "<entry /><entry />\n"
1782 desc += "</row>\n"
1784 desc += "</tbody></tgroup></informaltable>\n</refsect3>"
1785 for field_name in field_descrs:
1786 common.LogWarning(*GetSymbolSourceLocation(symbol),
1787 "Value description for %s::%s is not used from source code comment block." % (symbol, field_name))
1788 if unused_parameters != '':
1789 unused_parameters += ", " + field_name
1790 else:
1791 unused_parameters = field_name
1793 # remember missing/unused parameters (needed in tmpl-free build)
1794 if missing_parameters != '' and (symbol not in AllIncompleteSymbols):
1795 AllIncompleteSymbols[symbol] = missing_parameters
1797 if unused_parameters != '' and (symbol not in AllUnusedSymbols):
1798 AllUnusedSymbols[symbol] = unused_parameters
1800 if not found:
1801 if len(fields) > 0:
1802 if symbol not in AllIncompleteSymbols:
1803 AllIncompleteSymbols[symbol] = "<items>"
1804 common.LogWarning(*GetSymbolSourceLocation(symbol),
1805 "Value descriptions for %s are missing in source code comment block." % symbol)
1807 desc += OutputSymbolTraits(symbol)
1808 desc += "</refsect2>\n"
1809 return (synop, desc)
1812def OutputVariable(symbol, declaration):
1813 """Returns the synopsis and detailed description of a variable.
1815 Args:
1816 symbol (str): the extern'ed variable.
1817 declaration (str): the declaration of the variable.
1819 Returns:
1820 str: the formatted docs
1821 """
1822 sid = common.CreateValidSGMLID(symbol)
1823 condition = MakeConditionDescription(symbol)
1825 logging.info("ouputing variable: '%s' '%s'", symbol, declaration)
1827 type_output = None
1828 m1 = re.search(
1829 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)
1830 m2 = re.search(
1831 r'\s*((const\s+|signed\s+|unsigned\s+|long\s+|short\s+)*\w+)(\s+\*+|\*+|\s)(\s*)(const\s+)*([A-Za-z]\w*)\s*=', declaration)
1832 if m1:
1833 mod1 = m1.group(1) or ''
1834 ptr = m1.group(3) or ''
1835 space = m1.group(4) or ''
1836 mod2 = m1.group(5) or ''
1837 type_output = "extern %s%s%s%s" % (mod1, ptr, space, mod2)
1838 elif m2:
1839 mod1 = m2.group(1) or ''
1840 ptr = m2.group(3) or ''
1841 space = m2.group(4) or ''
1842 mod2 = m2.group(5) or ''
1843 type_output = '%s%s%s%s' % (mod1, ptr, space, mod2)
1844 else:
1845 type_output = "extern"
1847 synop = "<row><entry role=\"variable_type\">%s</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
1848 type_output, sid, symbol)
1850 desc = "<refsect2 id=\"%s\" role=\"variable\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
1852 desc += MakeIndexterms(symbol, sid)
1853 desc += "\n"
1854 desc += OutputSymbolExtraLinks(symbol)
1856 decl_out = CreateValidSGML(declaration)
1857 desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out
1859 desc += MakeDeprecationNote(symbol)
1861 if symbol in SymbolDocs:
1862 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1864 if symbol in SymbolAnnotations:
1865 param_desc = SymbolAnnotations[symbol]
1866 param_desc, param_annotations = ExpandAnnotation(symbol, param_desc)
1867 if param_annotations != '':
1868 desc += "\n<para>%s</para>" % param_annotations
1870 desc += OutputSymbolTraits(symbol)
1871 desc += "</refsect2>\n"
1872 return (synop, desc)
1875def OutputFunction(symbol, declaration, symbol_type):
1876 """Returns the synopsis and detailed description of a function.
1878 Args:
1879 symbol (str): the function.
1880 declaration (str): the declaration of the function.
1882 Returns:
1883 str: the formatted docs
1884 """
1885 sid = common.CreateValidSGMLID(symbol)
1886 condition = MakeConditionDescription(symbol)
1888 # Take out the return type
1889 # $1 $2 $3
1890 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'
1891 m = re.search(regex, declaration)
1892 declaration = re.sub(regex, '', declaration)
1893 type_modifier = m.group(1) or ''
1894 type = m.group(2)
1895 pointer = m.group(3)
1896 pointer = pointer.rstrip()
1897 xref = MakeXRef(type, tagify(type, "returnvalue"))
1898 start = ''
1899 # if (symbol_type == 'USER_FUNCTION')
1900 # start = "typedef "
1901 #
1903 # We output const rather than G_CONST_RETURN.
1904 type_modifier = re.sub(r'G_CONST_RETURN', 'const', type_modifier)
1905 pointer = re.sub(r'G_CONST_RETURN', 'const', pointer)
1906 pointer = re.sub(r'^\s+', ' ', pointer)
1908 ret_type_output = "%s%s%s%s\n" % (start, type_modifier, xref, pointer)
1910 indent_len = len(symbol) + 2
1911 char1 = char2 = char3 = ''
1912 if symbol_type == 'USER_FUNCTION':
1913 indent_len += 3
1914 char1 = "<phrase role=\"c_punctuation\">(</phrase>"
1915 char2 = "*"
1916 char3 = "<phrase role=\"c_punctuation\">)</phrase>"
1918 symbol_output = "%s<link linkend=\"%s\">%s%s</link>%s" % (char1, sid, char2, symbol, char3)
1919 if indent_len < MAX_SYMBOL_FIELD_WIDTH:
1920 symbol_desc_output = "%s%s%s%s " % (char1, char2, symbol, char3)
1921 else:
1922 indent_len = MAX_SYMBOL_FIELD_WIDTH - 8
1923 symbol_desc_output = ('%s%s%s%s\n' % (char1, char2, symbol, char3)) + (' ' * (indent_len - 1))
1925 synop = "<row><entry role=\"function_type\">%s</entry><entry role=\"function_name\">%s <phrase role=\"c_punctuation\">()</phrase></entry></row>\n" % (
1926 ret_type_output, symbol_output)
1928 desc = "<refsect2 id=\"%s\" role=\"function\"%s>\n<title>%s ()</title>\n" % (sid, condition, symbol)
1930 desc += MakeIndexterms(symbol, sid)
1931 desc += "\n"
1932 desc += OutputSymbolExtraLinks(symbol)
1934 desc += "<programlisting language=\"C\">%s%s(" % (ret_type_output, symbol_desc_output)
1936 def tagfun(*args):
1937 return tagify(args[0], "parameter")
1939 fields = common.ParseFunctionDeclaration(declaration, MakeXRef, tagfun)
1941 first = True
1942 for field_name in fields.values():
1943 if first:
1944 desc += field_name
1945 first = False
1946 else:
1947 desc += ",\n" + (' ' * indent_len) + field_name
1949 desc += ");</programlisting>\n"
1951 desc += MakeDeprecationNote(symbol)
1953 if symbol in SymbolDocs:
1954 desc += ConvertMarkDown(symbol, SymbolDocs[symbol])
1956 if symbol in SymbolAnnotations:
1957 param_desc = SymbolAnnotations[symbol]
1958 param_desc, param_annotations = ExpandAnnotation(symbol, param_desc)
1959 if param_annotations != '':
1960 desc += "\n<para>%s</para>" % param_annotations
1962 desc += OutputParamDescriptions("FUNCTION", symbol, fields.keys())
1963 desc += OutputSymbolTraits(symbol)
1964 desc += "</refsect2>\n"
1965 return (synop, desc)
1968def OutputParamDescriptions(symbol_type, symbol, fields):
1969 """Returns the DocBook output describing the parameters of a symbol.
1971 This can be used for functions, macros or signal handlers.
1973 Args:
1974 symbol_type (str): 'FUNCTION', 'MACRO' or 'SIGNAL'. Signal
1975 handlers have an implicit user_data parameter last.
1976 symbol (str): the name of the symbol being described.
1977 fields (list): parsed fields from the declaration, used to determine
1978 undocumented/unused entries
1980 Returns:
1981 str: the formatted parameter docs
1982 """
1983 output = ''
1984 num_params = 0
1985 field_descrs = None
1987 if fields:
1988 field_descrs = [f for f in fields if f not in ['void', 'Returns']]
1989 else:
1990 field_descrs = []
1992 params = SymbolParams.get(symbol)
1993 logging.info("param_desc(%s, %s) = %s", symbol_type, symbol, str(params))
1994 # This might be an empty dict, but for SIGNALS we append the user_data docs.
1995 # TODO(ensonic): maybe create that docstring in GetSignals()
1996 if params is not None:
1997 returns = ''
1998 params_desc = ''
1999 missing_parameters = ''
2000 unused_parameters = ''
2002 for param_name, param_desc in params.items():
2003 param_desc, param_annotations = ExpandAnnotation(symbol, param_desc)
2004 param_desc = ConvertMarkDown(symbol, param_desc)
2005 # trim
2006 param_desc = re.sub(r'^(\s|\n)+', '', param_desc, flags=re.M | re.S)
2007 param_desc = re.sub(r'(\s|\n)+$', '', param_desc, flags=re.M | re.S)
2008 if param_name == "Returns":
2009 returns = param_desc
2010 if param_annotations != '':
2011 returns += "\n<para>%s</para>" % param_annotations
2013 elif param_name == "void":
2014 # FIXME: &common.LogWarning()?
2015 logging.info("!!!! void in params for %s?\n", symbol)
2016 else:
2017 if fields:
2018 if param_name not in field_descrs:
2019 common.LogWarning(*GetSymbolSourceLocation(symbol),
2020 "Parameter description for %s::%s is not used from source code comment block." % (symbol, param_name))
2021 if unused_parameters != '':
2022 unused_parameters += ", " + param_name
2023 else:
2024 unused_parameters = param_name
2025 else:
2026 field_descrs.remove(param_name)
2028 if param_desc != '':
2029 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" % (
2030 param_name, param_desc, param_annotations)
2031 num_params += 1
2033 for param_name in field_descrs:
2034 common.LogWarning(*GetSymbolSourceLocation(symbol),
2035 "Parameter description for %s::%s is missing in source code comment block." % (symbol, param_name))
2036 if missing_parameters != '':
2037 missing_parameters += ", " + param_name
2038 else:
2039 missing_parameters = param_name
2041 # Signals have an implicit user_data parameter which we describe.
2042 if symbol_type == "SIGNAL":
2043 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"
2045 # Start a table if we need one.
2046 if params_desc != '':
2047 sid = common.CreateValidSGMLID("%s.parameters" % symbol)
2049 output += '''<refsect3 id="%s" role="parameters">\n<title>Parameters</title>
2050<informaltable role="parameters_table" pgwide="1" frame="none">
2051<tgroup cols="3">
2052<colspec colname="parameters_name" colwidth="150px"/>
2053<colspec colname="parameters_description"/>
2054<colspec colname="parameters_annotations" colwidth="200px"/>
2055<tbody>
2056''' % sid
2057 output += params_desc
2058 output += "</tbody></tgroup></informaltable>\n</refsect3>"
2060 # Output the returns info last
2061 if returns != '':
2062 sid = common.CreateValidSGMLID("%s.returns" % symbol)
2064 output += '''<refsect3 id="%s" role=\"returns\">\n<title>Returns</title>
2065''' % sid
2066 output += returns
2067 output += "\n</refsect3>"
2069 # remember missing/unused parameters (needed in tmpl-free build)
2070 if missing_parameters != '' and (symbol not in AllIncompleteSymbols):
2071 AllIncompleteSymbols[symbol] = missing_parameters
2073 if unused_parameters != '' and (symbol not in AllUnusedSymbols):
2074 AllUnusedSymbols[symbol] = unused_parameters
2076 if num_params == 0 and fields and field_descrs:
2077 if symbol not in AllIncompleteSymbols:
2078 AllIncompleteSymbols[symbol] = "<parameters>"
2079 return output
2082def ParseStabilityLevel(stability, file, line, message):
2083 """Parses a stability level and outputs a warning if it isn't valid.
2084 Args:
2085 stability (str): the stability text.
2086 file, line: context for error message
2087 message: description of where the level is from, to use in any error message.
2088 Returns:
2089 str: the parsed stability level string.
2090 """
2091 stability = stability.strip()
2092 sl = stability.strip().lower()
2093 if sl == 'stable':
2094 stability = "Stable"
2095 elif sl == 'unstable':
2096 stability = "Unstable"
2097 elif sl == 'private':
2098 stability = "Private"
2099 else:
2100 common.LogWarning(file, line,
2101 "%s is %s. It should be one of these: Stable, "
2102 "Unstable, or Private." % (
2103 message, stability))
2104 return str(stability)
2107def OutputDBFile(file, title, section_id, includes, functions_synop, other_synop, functions_details, other_desc, signals_synop, signals_desc, args_synop, args_desc, actions_synop, actions_desc, hierarchy, interfaces, implementations, prerequisites, derived, file_objects, default_stability):
2108 """Outputs the final DocBook file for one section.
2110 Args:
2111 file (str): the name of the file.
2112 title (str): the title from the $MODULE-sections.txt file
2113 section_id (str): the id to use for the toplevel tag.
2114 includes (str): comma-separates list of include files added at top of
2115 synopsis, with '<' '>' around them (if not already enclosed in '').
2116 functions_synop (str): the DocBook for the Functions Synopsis part.
2117 other_synop (str): the DocBook for the Types and Values Synopsis part.
2118 functions_details (str): the DocBook for the Functions Details part.
2119 other_desc (str): the DocBook for the Types and Values Details part.
2120 signal_synop (str): the DocBook for the Signal Synopsis part
2121 signal_desc (str): the DocBook for the Signal Description part
2122 args_synop (str): the DocBook for the Arg Synopsis part
2123 args_desc (str): the DocBook for the Arg Description part
2124 actions_synop (str): the DocBook for the Action Synopsis part
2125 actions_desc (str): the DocBook for the Action Description part
2126 hierarchy (str): the DocBook for the Object Hierarchy part
2127 interfaces (str): the DocBook for the Interfaces part
2128 implementations (str): the DocBook for the Known Implementations part
2129 prerequisites (str): the DocBook for the Prerequisites part
2130 derived (str): the DocBook for the Derived Interfaces part
2131 file_objects (list): objects in this file
2132 default_stability (str): fallback if no api-stability is set (optional)
2134 Returns:
2135 bool: True if the docs where updated
2136 """
2138 logging.info("Output docbook for file %s with title '%s'", file, title)
2140 # The edited title overrides the one from the sections file.
2141 new_title = SymbolDocs.get(file + ":title")
2142 if new_title and not new_title.strip() == '':
2143 title = new_title
2144 logging.info("Found title: %s", title)
2146 short_desc = SymbolDocs.get(file + ":short_description")
2147 if not short_desc or short_desc.strip() == '':
2148 short_desc = ''
2149 else:
2150 # Don't use ConvertMarkDown here for now since we don't want blocks
2151 short_desc = ExpandAbbreviations(title + ":short_description", short_desc)
2152 logging.info("Found short_desc: %s", short_desc)
2154 long_desc = SymbolDocs.get(file + ":long_description")
2155 if not long_desc or long_desc.strip() == '':
2156 long_desc = ''
2157 else:
2158 long_desc = ConvertMarkDown(title + ":long_description", long_desc)
2159 logging.info("Found long_desc: %s", long_desc)
2161 see_also = SymbolDocs.get(file + ":see_also")
2162 if not see_also or re.search(r'^\s*(<para>)?\s*(</para>)?\s*$', see_also):
2163 see_also = ''
2164 else:
2165 see_also = ConvertMarkDown(title + ":see_also", see_also)
2166 logging.info("Found see_also: %s", see_also)
2168 if see_also:
2169 see_also = "<refsect1 id=\"%s.see-also\">\n<title>See Also</title>\n%s\n</refsect1>\n" % (section_id, see_also)
2171 stability = SymbolDocs.get(file + ":stability")
2172 if not stability or re.search(r'^\s*$', stability):
2173 stability = ''
2174 else:
2175 line_number = GetSymbolSourceLocation(file + ":stability")[1]
2176 stability = ParseStabilityLevel(stability, file, line_number, "Section stability level")
2177 logging.info("Found stability: %s", stability)
2179 if not stability:
2180 stability = default_stability or ''
2182 if stability:
2183 if stability in AnnotationDefinition:
2184 AnnotationsUsed[stability] = True
2185 stability = "<acronym>%s</acronym>" % stability
2186 stability = "<refsect1 id=\"%s.stability-level\">\n<title>Stability Level</title>\n%s, unless otherwise indicated\n</refsect1>\n" % (
2187 section_id, stability)
2189 image = SymbolDocs.get(file + ":image")
2190 if not image or re.search(r'^\s*$', image):
2191 image = ''
2192 else:
2193 image = image.strip()
2195 format = None
2197 il = image.lower()
2198 if re.search(r'jpe?g$', il):
2199 format = "format='JPEG'"
2200 elif il.endswith('png'):
2201 format = "format='PNG'"
2202 elif il.endswith('svg'):
2203 format = "format='SVG'"
2204 else:
2205 format = ''
2207 image = " <inlinegraphic fileref='%s' %s/>\n" % (image, format)
2209 include_output = ''
2210 if includes:
2211 include_output += "<refsect1 id=\"%s.includes\"><title>Includes</title><synopsis>" % section_id
2212 for include in includes.split(','):
2213 if re.search(r'^\".+\"$', include):
2214 include_output += "#include %s\n" % include
2215 else:
2216 include = re.sub(r'^\s+|\s+$', '', include, flags=re.S)
2217 include_output += "#include <%s>\n" % include
2219 include_output += "</synopsis></refsect1>\n"
2221 extralinks = OutputSectionExtraLinks(title, "Section:%s" % file)
2223 old_db_file = os.path.join(DB_OUTPUT_DIR, file + '.xml')
2224 new_db_file = os.path.join(DB_OUTPUT_DIR, file + '.xml.new')
2226 OUTPUT = open(new_db_file, 'w', encoding='utf-8')
2228 object_anchors = ''
2229 for fobject in file_objects:
2230 if fobject == section_id:
2231 continue
2232 sid = common.CreateValidSGMLID(fobject)
2233 logging.info("Adding anchor for %s\n", fobject)
2234 object_anchors += "<anchor id=\"%s\"/>" % sid
2236 # Make sure we produce valid docbook
2237 if not functions_details:
2238 functions_details = "<para />"
2240 # We used to output this, but is messes up our common.UpdateFileIfChanged code
2241 # since it changes every day (and it is only used in the man pages):
2242 # "<refentry id="$section_id" revision="$mday $month $year">"
2244 OUTPUT.write(DB_REFENTRY.substitute({
2245 'actions_desc': actions_desc,
2246 'actions_synop': actions_synop,
2247 'args_desc': args_desc,
2248 'args_synop': args_synop,
2249 'derived': derived,
2250 'extralinks': extralinks,
2251 'functions_details': functions_details,
2252 'functions_synop': functions_synop,
2253 'header': MakeDocHeader('refentry'),
2254 'hierarchy': hierarchy,
2255 'image': image,
2256 'include_output': include_output,
2257 'interfaces': interfaces,
2258 'implementations': implementations,
2259 'long_desc': long_desc,
2260 'object_anchors': object_anchors,
2261 'other_desc': other_desc,
2262 'other_synop': other_synop,
2263 'prerequisites': prerequisites,
2264 'section_id': section_id,
2265 'see_also': see_also,
2266 'signals_desc': signals_desc,
2267 'signals_synop': signals_synop,
2268 'short_desc': short_desc,
2269 'stability': stability,
2270 'title': title,
2271 'MODULE': MODULE.upper(),
2272 }))
2273 OUTPUT.close()
2275 return common.UpdateFileIfChanged(old_db_file, new_db_file, 0)
2278def OutputProgramDBFile(program, section_id):
2279 """Outputs the final DocBook file for one program.
2281 Args:
2282 file (str): the name of the file.
2283 section_id (str): the id to use for the toplevel tag.
2285 Returns:
2286 bool: True if the docs where updated
2287 """
2288 logging.info("Output program docbook for %s", program)
2290 short_desc = SourceSymbolDocs.get(program + ":short_description")
2291 if not short_desc or short_desc.strip() == '':
2292 short_desc = ''
2293 else:
2294 # Don't use ConvertMarkDown here for now since we don't want blocks
2295 short_desc = ExpandAbbreviations(program, short_desc)
2296 logging.info("Found short_desc: %s", short_desc)
2298 synopsis = SourceSymbolDocs.get(program + ":synopsis")
2299 if synopsis and synopsis.strip() != '':
2300 items = synopsis.split(' ')
2301 for i in range(0, len(items)):
2302 parameter = items[i]
2303 choice = "plain"
2304 rep = ''
2306 # first parameter is the command name
2307 if i == 0:
2308 synopsis = "<command>%s</command>\n" % parameter
2309 continue
2311 # square brackets indicate optional parameters, curly brackets
2312 # indicate required parameters ("plain" parameters are also
2313 # mandatory, but do not get extra decoration)
2314 m1 = re.search(r'^\[(.+?)\]$', parameter)
2315 m2 = re.search(r'^\{(.+?)\}$', parameter)
2316 if m1:
2317 choice = "opt"
2318 parameter = m1.group(1)
2319 elif m2:
2320 choice = "req"
2321 parameter = m2.group(1)
2323 # parameters ending in "..." are repeatable
2324 if parameter.endswith('...'):
2325 rep = ' rep=\"repeat\"'
2326 parameter = parameter[:-3]
2328 # italic parameters are replaceable parameters
2329 parameter = re.sub(r'\*(.+?)\*', r'<replaceable>\1</replaceable>', parameter)
2331 synopsis += "<arg choice=\"%s\"%s>" % (choice, rep)
2332 synopsis += parameter
2333 synopsis += "</arg>\n"
2335 logging.info("Found synopsis: %s", synopsis)
2336 else:
2337 synopsis = "<command>%s</command>" % program
2339 long_desc = SourceSymbolDocs.get(program + ":long_description")
2340 if not long_desc or long_desc.strip() == '':
2341 long_desc = ''
2342 else:
2343 long_desc = ConvertMarkDown("%s:long_description" % program, long_desc)
2344 logging.info("Found long_desc: %s", long_desc)
2346 options = ''
2347 o = program + ":options"
2348 if o in SourceSymbolDocs:
2349 opts = SourceSymbolDocs[o].split('\t')
2351 logging.info('options: %d, %s', len(opts), str(opts))
2353 options = "<refsect1>\n<title>Options</title>\n<variablelist>\n"
2354 for k in range(0, len(opts), 2):
2355 opt_desc = opts[k + 1]
2357 opt_desc = re.sub(r'\*(.+?)\*', r'<replaceable>\1</replaceable>', opt_desc)
2359 options += "<varlistentry>\n<term>"
2360 opt_names = opts[k].split(',')
2361 for i in range(len(opt_names)):
2362 prefix = ', ' if i > 0 else ''
2363 # italic parameters are replaceable parameters
2364 opt_name = re.sub(r'\*(.+?)\*', r'<replaceable>\1</replaceable>', opt_names[i])
2366 options += "%s<option>%s</option>\n" % (prefix, opt_name)
2368 options += "</term>\n"
2369 options += "<listitem><para>%s</para></listitem>\n" % opt_desc
2370 options += "</varlistentry>\n"
2372 options += "</variablelist></refsect1>\n"
2374 exit_status = SourceSymbolDocs.get(program + ":returns")
2375 if exit_status and exit_status != '':
2376 exit_status = ConvertMarkDown("%s:returns" % program, exit_status)
2377 exit_status = "<refsect1 id=\"%s.exit-status\">\n<title>Exit Status</title>\n%s\n</refsect1>\n" % (
2378 section_id, exit_status)
2379 else:
2380 exit_status = ''
2382 see_also = SourceSymbolDocs.get(program + ":see_also")
2383 if not see_also or re.search(r'^\s*(<para>)?\s*(</para>)?\s*$', see_also):
2384 see_also = ''
2385 else:
2386 see_also = ConvertMarkDown("%s:see_also" % program, see_also)
2387 logging.info("Found see_also: %s", see_also)
2389 if see_also:
2390 see_also = "<refsect1 id=\"%s.see-also\">\n<title>See Also</title>\n%s\n</refsect1>\n" % (section_id, see_also)
2392 old_db_file = os.path.join(DB_OUTPUT_DIR, program + ".xml")
2393 new_db_file = os.path.join(DB_OUTPUT_DIR, program + ".xml.new")
2395 OUTPUT = open(new_db_file, 'w', encoding='utf-8')
2397 OUTPUT.write('''%s
2398<refentry id="%s">
2399<refmeta>
2400<refentrytitle role="top_of_page" id="%s.top_of_page">%s</refentrytitle>
2401<manvolnum>1</manvolnum>
2402<refmiscinfo>User Commands</refmiscinfo>
2403</refmeta>
2404<refnamediv>
2405<refname>%s</refname>
2406<refpurpose>%s</refpurpose>
2407</refnamediv>
2408<refsynopsisdiv>
2409<cmdsynopsis>%s</cmdsynopsis>
2410</refsynopsisdiv>
2411<refsect1 id="%s.description" role="desc">
2412<title role="desc.title">Description</title>
2413%s
2414</refsect1>
2415%s%s%s
2416</refentry>
2417''' % (MakeDocHeader("refentry"), section_id, section_id, program, program, short_desc, synopsis, section_id, long_desc, options, exit_status, see_also))
2418 OUTPUT.close()
2420 return common.UpdateFileIfChanged(old_db_file, new_db_file, 0)
2423def OutputExtraFile(file):
2424 """Copies an "extra" DocBook file into the output directory, expanding abbreviations.
2426 Args:
2427 file (str): the source file.
2429 Returns:
2430 bool: True if the docs where updated
2431 """
2433 basename = os.path.basename(file)
2435 old_db_file = os.path.join(DB_OUTPUT_DIR, basename)
2436 new_db_file = os.path.join(DB_OUTPUT_DIR, basename + ".new")
2438 contents = open(file, 'r', encoding='utf-8').read()
2440 with open(new_db_file, 'w', encoding='utf-8') as out:
2441 out.write(ExpandAbbreviations(basename + " file", contents))
2443 return common.UpdateFileIfChanged(old_db_file, new_db_file, 0)
2446def GetDocbookHeader(main_file):
2447 if os.path.exists(main_file):
2448 INPUT = open(main_file, 'r', encoding='utf-8')
2449 header = ''
2450 for line in INPUT:
2451 if re.search(r'^\s*<(book|chapter|article)', line):
2452 # check that the top-level tagSYSTEM or the doctype decl contain the xinclude namespace decl
2453 if not re.search(r'http:\/\/www.w3.org\/200[13]\/XInclude', line) and \
2454 not re.search(r'http:\/\/www.w3.org\/200[13]\/XInclude', header, flags=re.MULTILINE):
2455 header = ''
2456 break
2458 # if there are SYSTEM ENTITIES here, we should prepend "../" to the path
2459 # FIXME: not sure if we can do this now, as people already work-around the problem
2460 # r'#<!ENTITY % ([a-zA-Z-]+) SYSTEM \"([^/][a-zA-Z./]+)\">', r'<!ENTITY % \1 SYSTEM \"../\2\">';
2461 line = re.sub(
2462 r'<!ENTITY % gtkdocentities SYSTEM "([^"]*)">', r'<!ENTITY % gtkdocentities SYSTEM "../\1">', line)
2463 header += line
2464 INPUT.close()
2465 header = header.strip()
2466 else:
2467 header = '''<?xml version="1.0"?>
2468<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
2469 "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd"
2470[
2471 <!ENTITY % local.common.attrib "xmlns:xi CDATA #FIXED 'http://www.w3.org/2003/XInclude'">
2472 <!ENTITY % gtkdocentities SYSTEM "../xml/gtkdocentities.ent">
2473 %gtkdocentities;
2474]>'''
2475 return header
2478def OutputBook(main_file, book_top, book_bottom, obj_tree):
2479 """Outputs the entities that need to be included into the main docbook file for the module.
2481 Args:
2482 book_top (str): the declarations of the entities, which are added
2483 at the top of the main docbook file.
2484 book_bottom (str): the entities, which are added in the main docbook
2485 file at the desired position.
2486 obj_tree (list): object tree list
2487 """
2489 old_file = os.path.join(DB_OUTPUT_DIR, MODULE + "-doc.top")
2490 new_file = os.path.join(DB_OUTPUT_DIR, MODULE + "-doc.top.new")
2492 with open(new_file, 'w', encoding='utf-8') as out:
2493 out.write(book_top)
2495 common.UpdateFileIfChanged(old_file, new_file, 0)
2497 old_file = os.path.join(DB_OUTPUT_DIR, MODULE + "-doc.bottom")
2498 new_file = os.path.join(DB_OUTPUT_DIR, MODULE + "-doc.bottom.new")
2500 with open(new_file, 'w', encoding='utf-8') as out:
2501 out.write(book_bottom)
2503 common.UpdateFileIfChanged(old_file, new_file, 0)
2505 # If the main docbook file hasn't been created yet, we create it here.
2506 # The user can tweak it later.
2507 if main_file and not os.path.exists(main_file):
2508 OUTPUT = open(main_file, 'w', encoding='utf-8')
2510 logging.info("no master doc, create default one at: " + main_file)
2512 OUTPUT.write('''%s
2513<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude">
2514 <bookinfo>
2515 <title>&package_name; Reference Manual</title>
2516 <releaseinfo>
2517 for &package_string;.
2518 The latest version of this documentation can be found on-line at
2519 <ulink role="online-location" url="http://[SERVER]/&package_name;/index.html">http://[SERVER]/&package_name;/</ulink>.
2520 </releaseinfo>
2521 </bookinfo>
2523 <chapter>
2524 <title>[Insert title here]</title>
2525%s </chapter>
2526''' % (MakeDocHeader("book"), book_bottom))
2527 if obj_tree:
2528 OUTPUT.write(''' <chapter id="object-tree">
2529 <title>Object Hierarchy</title>
2530 <xi:include href="xml/tree_index.sgml"/>
2531 </chapter>
2532''')
2533 else:
2534 OUTPUT.write(''' <!-- enable this when you use gobject types
2535 <chapter id="object-tree">
2536 <title>Object Hierarchy</title>
2537 <xi:include href="xml/tree_index.sgml"/>
2538 </chapter>
2539 -->
2540''')
2542 OUTPUT.write(''' <index id="api-index-full">
2543 <title>API Index</title>
2544 <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
2545 </index>
2546 <index id="deprecated-api-index" role="deprecated">
2547 <title>Index of deprecated API</title>
2548 <xi:include href="xml/api-index-deprecated.xml"><xi:fallback /></xi:include>
2549 </index>
2550''')
2551 for version in sorted(set(Since.values())):
2552 dash_version = version.replace('.', '-')
2553 OUTPUT.write(''' <index id="api-index-%s" role="%s">
2554 <title>Index of new API in %s</title>
2555 <xi:include href="xml/api-index-%s.xml"><xi:fallback /></xi:include>
2556 </index>
2557''' % (dash_version, version, version, version))
2559 if AnnotationsUsed:
2560 OUTPUT.write(''' <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
2561''')
2562 else:
2563 OUTPUT.write(''' <!-- enable this when you use gobject introspection annotations
2564 <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
2565 -->
2566''')
2568 OUTPUT.write('''</book>
2569''')
2571 OUTPUT.close()
2574def CreateValidSGML(text):
2575 """Turn any chars which are used in XML into entities.
2577 e.g. '<' into '<'
2579 Args:
2580 text (str): the text to turn into proper XML.
2582 Returns:
2583 str: escaped input
2584 """
2586 text = text.replace('&', '&') # Do this first, or the others get messed up.
2587 text = text.replace('<', '<')
2588 text = text.replace('>', '>')
2589 # browsers render single tabs inconsistently
2590 text = re.sub(r'([^\s])\t([^\s])', r'\1 \2', text)
2591 return text
2594def ConvertXMLChars(symbol, text):
2595 """Escape XML chars.
2597 This is used for text in source code comment blocks, to turn
2598 chars which are used in XML into entities, e.g. '<' into
2599 <'. Depending on INLINE_MARKUP_MODE, this is done
2600 unconditionally or only if the character doesn't seem to be
2601 part of an XML construct (tag or entity reference).
2602 Args:
2603 text (str): the text to turn into proper XML.
2605 Returns:
2606 str: escaped input
2607 """
2609 if INLINE_MARKUP_MODE:
2610 # For the XML mode only convert to entities outside CDATA sections.
2611 return ModifyXMLElements(text, symbol,
2612 "<!\\[CDATA\\[|<programlisting[^>]*>",
2613 ConvertXMLCharsEndTag,
2614 ConvertXMLCharsCallback)
2615 # For the simple non-sgml mode, convert to entities everywhere.
2617 text = re.sub(r'&(?![a-zA-Z#]+;)', r'&', text) # Do this first, or the others get messed up.
2618 # Allow '<' in verbatim markdown
2619 # TODO: we don't want to convert any of them between ``
2620 text = re.sub(r'(?<!`)<', r'<', text)
2621 # Allow '>' at beginning of string for blockquote markdown
2622 text = re.sub(r'''(?<=[^\w\n"'\/-])>''', r'>', text)
2624 return text
2627def ConvertXMLCharsEndTag(start_tag):
2628 if start_tag == '<![CDATA[':
2629 return "]]>"
2630 return "</programlisting>"
2633def ConvertXMLCharsCallback(text, symbol, tag):
2634 if re.search(r'^<programlisting', tag):
2635 logging.debug('call modifyXML')
2636 # We can handle <programlisting> specially here.
2637 return ModifyXMLElements(text, symbol,
2638 "<!\\[CDATA\\[",
2639 ConvertXMLCharsEndTag,
2640 ConvertXMLCharsCallback2)
2641 elif tag == '':
2642 logging.debug('replace entities')
2643 # If we're not in CDATA convert to entities.
2644 text = re.sub(r'&(?![a-zA-Z#]+;)', r'&', text) # Do this first, or the others get messed up.
2645 text = re.sub(r'<(?![a-zA-Z\/!])', r'<', text)
2646 # Allow '>' at beginning of string for blockquote markdown
2647 text = re.sub(r'''(?<=[^\w\n"'\/-])>''', r'>', text)
2649 # Handle "#include <xxxxx>"
2650 text = re.sub(r'#include(\s+)<([^>]+)>', r'#include\1<\2>', text)
2652 return text
2655def ConvertXMLCharsCallback2(text, symbol, tag):
2656 # If we're not in CDATA convert to entities.
2657 # We could handle <programlisting> differently, though I'm not sure it helps.
2658 if tag == '':
2659 # replace only if its not a tag
2660 text = re.sub(r'&(?![a-zA-Z#]+;)', r'&', text) # Do this first, or the others get messed up.
2661 text = re.sub(r'<(?![a-zA-Z\/!])', r'<', text)
2662 text = re.sub(r'''(?<![a-zA-Z0-9"'\/-])>''', r'>', text)
2663 # Handle "#include <xxxxx>"
2664 text = re.sub(r'/#include(\s+)<([^>]+)>', r'#include\1<\2>', text)
2666 return text
2669def ExpandAnnotation(symbol, param_desc):
2670 """This turns annotations into acronym tags.
2671 Args:
2672 symbol (str): the symbol being documented, for error messages.
2673 param_desc (str): the text to expand.
2675 Returns:
2676 str: the remaining param_desc
2677 str: the formatted annotations
2678 """
2679 param_annotations = ''
2681 # look for annotations at the start of the comment part
2682 # function level annotations don't end with a colon ':'
2683 m = re.search(r'^\s*\((.*?)\)(:|$)', param_desc)
2684 if m:
2685 param_desc = param_desc[m.end():]
2687 annotations = re.split(r'\)\s*\(', m.group(1))
2688 logging.info("annotations for %s: '%s'\n", symbol, m.group(1))
2689 for annotation in annotations:
2690 # need to search for the longest key-match in %AnnotationDefinition
2691 match_length = 0
2692 match_annotation = ''
2694 for annotationdef in AnnotationDefinition:
2695 if annotation.startswith(annotationdef):
2696 if len(annotationdef) > match_length:
2697 match_length = len(annotationdef)
2698 match_annotation = annotationdef
2700 annotation_extra = ''
2701 if match_annotation != '':
2702 m = re.search(match_annotation + r'\s+(.*)', annotation)
2703 if m:
2704 annotation_extra = " " + m.group(1)
2706 AnnotationsUsed[match_annotation] = 1
2707 param_annotations += "[<acronym>%s</acronym>%s]" % (match_annotation, annotation_extra)
2708 else:
2709 common.LogWarning(*GetSymbolSourceLocation(symbol),
2710 "unknown annotation \"%s\" in documentation for %s." % (annotation, symbol))
2711 param_annotations += "[%s]" % annotation
2713 param_desc = param_desc.strip()
2714 m = re.search(r'^(.*?)\.*\s*$', param_desc, flags=re.S)
2715 param_desc = m.group(1) + '. '
2717 if param_annotations != '':
2718 param_annotations = "<emphasis role=\"annotation\">%s</emphasis>" % param_annotations
2720 return (param_desc, param_annotations)
2723def ExpandAbbreviations(symbol, text):
2724 """Expand the shortcut notation for symbol references.
2726 This turns the abbreviations function(), macro(), @param, %constant, and #symbol
2727 into appropriate DocBook markup. CDATA sections and <programlisting> parts
2728 are skipped.
2730 Args:
2731 symbol (str): the symbol being documented, for error messages.
2732 text (str): the text to expand.
2734 Returns:
2735 str: the expanded text
2736 """
2737 # Note: This is a fallback and normally done in the markdown parser
2739 logging.debug('expand abbreviations for "%s", text: [%s]', symbol, text)
2740 m = re.search(r'\|\[[^\n]*\n(.*)\]\|', text, flags=re.M | re.S)
2741 if m:
2742 logging.debug('replaced entities in code block')
2743 text = text[:m.start(1)] + md_to_db.ReplaceEntities(m.group(1)) + text[m.end(1):]
2745 # Convert "|[" and "]|" into the start and end of program listing examples.
2746 # Support \[<!-- language="C" --> modifiers
2747 text = re.sub(r'\|\[<!-- language="([^"]+)" -->',
2748 r'<informalexample><programlisting role="example" language="\1"><![CDATA[', text)
2749 text = re.sub(r'\|\[', r'<informalexample><programlisting role="example"><![CDATA[', text)
2750 text = re.sub(r'\]\|', r']]></programlisting></informalexample>', text)
2752 # keep CDATA unmodified, preserve ulink tags (ideally we preseve all tags
2753 # as such)
2754 return ModifyXMLElements(text, symbol,
2755 "<!\\[CDATA\\[|<ulink[^>]*>|<programlisting[^>]*>|<!DOCTYPE",
2756 ExpandAbbreviationsEndTag,
2757 ExpandAbbreviationsCallback)
2760def ExpandAbbreviationsEndTag(start_tag):
2761 # Returns the end tag (as a regexp) corresponding to the given start tag.
2762 if start_tag == r'<!\[CDATA\[':
2763 return "]]>"
2764 if start_tag == "<!DOCTYPE":
2765 return '>'
2766 m = re.search(r'<(\w+)', start_tag)
2767 if m:
2768 return "</%s>" % m.group(1)
2770 logging.warning('no end tag for "%s"', start_tag)
2771 return ''
2774def ExpandAbbreviationsCallback(text, symbol, tag):
2775 # Called inside or outside each CDATA or <programlisting> section.
2776 if tag.startswith(r'^<programlisting'):
2777 # Handle any embedded CDATA sections.
2778 return ModifyXMLElements(text, symbol,
2779 "<!\\[CDATA\\[",
2780 ExpandAbbreviationsEndTag,
2781 ExpandAbbreviationsCallback2)
2782 elif tag == '':
2783 # NOTE: this is a fallback. It is normally done by the Markdown parser.
2784 # but is also used for OutputExtraFile
2786 # We are outside any CDATA or <programlisting> sections, so we expand
2787 # any gtk-doc abbreviations.
2789 # Convert '@param()'
2790 # FIXME: we could make those also links ($symbol.$2), but that would be less
2791 # useful as the link target is a few lines up or down
2792 text = re.sub(r'(\A|[^\\])\@(\w+((\.|->)\w+)*)\s*\(\)', r'\1<parameter>\2()</parameter>', text)
2794 # Convert 'function()' or 'macro()'.
2795 # if there is abc_*_def() we don't want to make a link to _def()
2796 # FIXME: also handle abc(def(....)) : but that would need to be done recursively :/
2797 def f1(m):
2798 return m.group(1) + MakeXRef(m.group(2), tagify(m.group(2) + "()", "function"))
2799 text = re.sub(r'([^\*.\w])(\w+)\s*\(\)', f1, text)
2800 # handle #Object.func()
2801 text = re.sub(r'(\A|[^\\])#([\w\-:\.]+[\w]+)\s*\(\)', f1, text)
2803 # Convert '@param', but not '\@param'.
2804 text = re.sub(r'(\A|[^\\])\@(\w+((\.|->)\w+)*)', r'\1<parameter>\2</parameter>', text)
2805 text = re.sub(r'/\\\@', r'\@', text)
2807 # Convert '%constant', but not '\%constant'.
2808 # Also allow negative numbers, e.g. %-1.
2809 def f2(m):
2810 return m.group(1) + MakeXRef(m.group(2), tagify(m.group(2), "literal"))
2811 text = re.sub(r'(\A|[^\\])\%(-?\w+)', f2, text)
2812 text = re.sub(r'\\\%', r'\%', text)
2814 # Convert '#symbol', but not '\#symbol'.
2815 def f3(m):
2816 return m.group(1) + MakeHashXRef(m.group(2), "type")
2817 text = re.sub(r'(\A|[^\\])#([\w\-:\.]+[\w]+)', f3, text)
2818 text = re.sub(r'\\#', '#', text)
2820 return text
2823def ExpandAbbreviationsCallback2(text, symbol, tag):
2824 # This is called inside a <programlisting>
2825 if tag == '':
2826 # We are inside a <programlisting> but outside any CDATA sections,
2827 # so we expand any gtk-doc abbreviations.
2828 # FIXME: why is this different from &ExpandAbbreviationsCallback(),
2829 # why not just call it
2830 text = re.sub(r'#(\w+)', lambda m: '%s;' % MakeHashXRef(m.group(1), ''), text)
2831 elif tag == "<![CDATA[":
2832 # NOTE: this is a fallback. It is normally done by the Markdown parser.
2833 text = md_to_db.ReplaceEntities(text, symbol)
2835 return text
2838def MakeHashXRef(symbol, tag):
2839 text = symbol
2841 # Check for things like '#include', '#define', and skip them.
2842 if symbol in PreProcessorDirectives:
2843 return "#%s" % symbol
2845 # Get rid of special suffixes ('-struct','-enum').
2846 text = re.sub(r'-struct$', '', text)
2847 text = re.sub(r'-enum$', '', text)
2849 # If the symbol is in the form "Object::signal", then change the symbol to
2850 # "Object-signal" and use "signal" as the text.
2851 if '::' in symbol:
2852 o, s = symbol.split('::', 1)
2853 symbol = '%s-%s' % (o, s)
2854 text = u'“' + s + u'”'
2856 # If the symbol is in the form "Object:property", then change the symbol to
2857 # "Object--property" and use "property" as the text.
2858 if ':' in symbol:
2859 o, p = symbol.split(':', 1)
2860 symbol = '%s--%s' % (o, p)
2861 text = u'“' + p + u'”'
2863 if tag != '':
2864 text = tagify(text, tag)
2866 return MakeXRef(symbol, text)
2869def ModifyXMLElements(text, symbol, start_tag_regexp, end_tag_func, callback):
2870 """Rewrite XML blocks.
2872 Looks for given XML element tags within the text, and calls
2873 the callback on pieces of text inside & outside those elements.
2874 Used for special handling of text inside things like CDATA
2875 and <programlisting>.
2877 Args:
2878 text (str): the text.
2879 symbol (str): the symbol currently being documented (only used for
2880 error messages).
2881 start_tag_regexp (str): the regular expression to match start tags.
2882 e.g. "<!\\[CDATA\\[|<programlisting[^>]*>" to
2883 match CDATA sections or programlisting elements.
2884 end_tag_func (func): function which is passed the matched start tag
2885 and should return the appropriate end tag string
2886 regexp.
2887 callback - callback called with each part of the text. It is
2888 called with a piece of text, the symbol being
2889 documented, and the matched start tag or '' if the text
2890 is outside the XML elements being matched.
2892 Returns:
2893 str: modified text
2894 """
2895 before_tag = start_tag = end_tag_regexp = end_tag = None
2896 result = ''
2898 logging.debug('modify xml for symbol: %s, regex: %s, text: [%s]', symbol, start_tag_regexp, text)
2900 m = re.search(start_tag_regexp, text, flags=re.S)
2901 while m:
2902 before_tag = text[:m.start()] # Prematch for last successful match string
2903 start_tag = m.group(0) # Last successful match
2904 text = text[m.end():] # Postmatch for last successful match string
2905 # get the matching end-tag for current tag
2906 end_tag_regexp = end_tag_func(start_tag)
2908 logging.debug('symbol: %s matched start: %s, end_tag: %s, text: [%s]', symbol, start_tag, end_tag_regexp, text)
2910 logging.debug('converting before tag: [%s]', before_tag)
2911 result += callback(before_tag, symbol, '')
2912 result += start_tag
2914 m2 = re.search(end_tag_regexp, text, flags=re.S)
2915 if m2:
2916 before_tag = text[:m2.start()]
2917 end_tag = m2.group(0)
2918 text = text[m2.end():]
2920 logging.debug('symbol: %s matched end %s: text: [%s]', symbol, end_tag, text)
2922 result += callback(before_tag, symbol, start_tag)
2923 result += end_tag
2924 else:
2925 common.LogWarning(*GetSymbolSourceLocation(symbol),
2926 "Can't find tag end: %s in docs for: %s." % (end_tag_regexp, symbol))
2927 # Just assume it is all inside the tag.
2928 result += callback(text, symbol, start_tag)
2929 text = ''
2930 m = re.search(start_tag_regexp, text, flags=re.S)
2932 # Handle any remaining text outside the tags.
2933 logging.debug('converting after tag: [%s]', text)
2934 result += callback(text, symbol, '')
2935 logging.debug('results for symbol: %s, text: [%s]', symbol, result)
2937 return result
2940def tagify(text, elem):
2941 # Adds a tag around some text.
2942 # e.g tagify("Text", "literal") => "<literal>Text</literal>".
2943 return '<' + elem + '>' + text + '</' + elem + '>'
2946def MakeDocHeader(tag):
2947 """Builds a docbook header for the given tag.
2949 Args:
2950 tag (str): doctype tag
2952 Returns:
2953 str: the docbook header
2954 """
2955 header = re.sub(r'<!DOCTYPE \w+', r'<!DOCTYPE ' + tag, doctype_header)
2956 # fix the path for book since this is one level up
2957 if tag == 'book':
2958 header = re.sub(
2959 r'<!ENTITY % gtkdocentities SYSTEM "../([a-zA-Z./]+)">', r'<!ENTITY % gtkdocentities SYSTEM "\1">', header)
2960 return header
2963def MakeXRef(symbol, text=None):
2964 """This returns a cross-reference link to the given symbol.
2966 Though it doesn't try to do this for a few standard C types that it knows
2967 won't be in the documentation.
2969 Args:
2970 symbol (str): the symbol to try to create a XRef to.
2971 text (str): text to put inside the XRef, defaults to symbol
2973 Returns:
2974 str: a docbook link
2975 """
2976 symbol = symbol.strip()
2977 if not text:
2978 text = symbol
2980 # Get rid of special suffixes ('-struct','-enum').
2981 text = re.sub(r'-struct$', '', text)
2982 text = re.sub(r'-enum$', '', text)
2984 if ' ' in symbol:
2985 return text
2987 logging.info("Getting type link for %s -> %s", symbol, text)
2989 symbol_id = common.CreateValidSGMLID(symbol)
2990 return "<link linkend=\"%s\">%s</link>" % (symbol_id, text)
2993def MakeIndexterms(symbol, sid):
2994 """This returns a indexterm elements for the given symbol
2996 Args:
2997 symbol (str): the symbol to create indexterms for
2999 Returns:
3000 str: doxbook index terms
3001 """
3002 terms = ''
3003 sortas = ''
3005 # make the index useful, by omitting the namespace when sorting
3006 if NAME_SPACE != '':
3007 m = re.search(r'^%s\_?(.*)' % NAME_SPACE, symbol, flags=re.I)
3008 if m:
3009 sortas = ' sortas="%s"' % m.group(1)
3011 if symbol in Deprecated:
3012 terms += "<indexterm zone=\"%s\" role=\"deprecated\"><primary%s>%s</primary></indexterm>" % (
3013 sid, sortas, symbol)
3014 IndexEntriesDeprecated[symbol] = sid
3015 IndexEntriesFull[symbol] = sid
3016 if symbol in Since:
3017 since = Since[symbol].strip()
3018 if since != '':
3019 terms += "<indexterm zone=\"%s\" role=\"%s\"><primary%s>%s</primary></indexterm>" % (
3020 sid, since, sortas, symbol)
3021 IndexEntriesSince[symbol] = sid
3022 IndexEntriesFull[symbol] = sid
3023 if terms == '':
3024 terms += "<indexterm zone=\"%s\"><primary%s>%s</primary></indexterm>" % (sid, sortas, symbol)
3025 IndexEntriesFull[symbol] = sid
3026 return terms
3029def MakeDeprecationNote(symbol):
3030 """This returns a deprecation warning for the given symbol.
3032 Args:
3033 symbol (str): the symbol to try to create a warning for.
3035 Returns:
3036 str: formatted warning or empty string if symbol is not deprecated
3037 """
3038 desc = ''
3039 if symbol in Deprecated:
3040 desc += "<warning><para><literal>%s</literal> " % symbol
3041 note = Deprecated[symbol]
3043 m = re.search(r'^\s*([0-9\.]+)\s*:?', note)
3044 if m:
3045 desc += "has been deprecated since version %s and should not be used in newly-written code.</para>" % m.group(
3046 1)
3047 else:
3048 desc += "is deprecated and should not be used in newly-written code.</para>"
3050 note = re.sub(r'^\s*([0-9\.]+)\s*:?\s*', '', note)
3051 note = note.strip()
3053 if note != '':
3054 note = ConvertMarkDown(symbol, note)
3055 desc += " " + note
3057 desc += "</warning>\n"
3059 return desc
3062def MakeConditionDescription(symbol):
3063 """This returns a summary of conditions for the given symbol.
3065 Args:
3066 symbol (str): the symbol to create the summary for.
3068 Returns:
3069 str: formatted text or empty string if no special conditions apply.
3070 """
3071 desc = ''
3072 if symbol in Deprecated:
3073 if desc != '':
3074 desc += "|"
3075 m = re.search(r'^\s*(.*?)\s*$', Deprecated[symbol])
3076 if m:
3077 desc += "deprecated:%s" % m.group(1)
3078 else:
3079 desc += "deprecated"
3081 if symbol in Since:
3082 if desc != '':
3083 desc += "|"
3084 m = re.search(r'^\s*(.*?)\s*$', Since[symbol])
3085 if m:
3086 desc += "since:%s" % m.group(1)
3087 else:
3088 desc += "since"
3090 if symbol in StabilityLevel:
3091 if desc != '':
3092 desc += "|"
3094 desc += "stability:" + StabilityLevel[symbol]
3096 if desc != '':
3097 cond = re.sub(r'"', r'"', desc)
3098 desc = ' condition=\"%s\"' % cond
3099 logging.info("condition for '%s' = '%s'", symbol, desc)
3101 return desc
3104def GetHierarchy(gobject, hierarchy):
3105 """Generate the object inheritance graph.
3107 Returns the DocBook output describing the ancestors and
3108 immediate children of a GObject subclass. It uses the
3109 global Objects and ObjectLevels arrays to walk the tree.
3111 Args:
3112 object (str): the GtkObject subclass.
3113 hierarchy (list) - previous hierarchy
3115 Returns:
3116 list: lines of docbook describing the hierarchy
3117 """
3118 # Find object in the objects array.
3119 found = False
3120 children = []
3121 level = 0
3122 j = 0
3123 for i in range(len(Objects)):
3124 if found:
3125 if ObjectLevels[i] <= level:
3126 break
3128 elif ObjectLevels[i] == level + 1:
3129 children.append(Objects[i])
3131 elif Objects[i] == gobject:
3132 found = True
3133 j = i
3134 level = ObjectLevels[i]
3136 if not found:
3137 return hierarchy
3139 logging.info("=== Hierarchy for: %s (%d existing entries) ===", gobject, len(hierarchy))
3141 # Walk up the hierarchy, pushing ancestors onto the ancestors array.
3142 ancestors = [gobject]
3143 logging.info("Level: %s", level)
3144 while level > 1:
3145 j -= 1
3146 if ObjectLevels[j] < level:
3147 ancestors.append(Objects[j])
3148 level = ObjectLevels[j]
3149 logging.info("Level: %s", level)
3151 # Output the ancestors, indented and with links.
3152 logging.info('%d ancestors', len(ancestors))
3153 last_index = 0
3154 level = 1
3155 for i in range(len(ancestors) - 1, -1, -1):
3156 ancestor = ancestors[i]
3157 ancestor_id = common.CreateValidSGMLID(ancestor)
3158 indent = ' ' * (level * 4)
3159 # Don't add a link to the current object, i.e. when i == 0.
3160 if i > 0:
3161 entry_text = indent + "<link linkend=\"%s\">%s</link>" % (ancestor_id, ancestor)
3162 alt_text = indent + ancestor
3163 else:
3164 entry_text = indent + ancestor
3165 alt_text = indent + "<link linkend=\"%s\">%s</link>" % (ancestor_id, ancestor)
3167 logging.info("Checking for '%s' or '%s'", entry_text, alt_text)
3168 # Check if we already have this object
3169 index = -1
3170 for j in range(len(hierarchy)):
3171 if hierarchy[j] == entry_text or (hierarchy[j] == alt_text):
3172 index = j
3173 break
3174 if index == -1:
3175 # We have a new entry, find insert position in alphabetical order
3176 found = False
3177 for j in range(last_index, len(hierarchy)):
3178 if not re.search(r'^' + indent, hierarchy[j]):
3179 last_index = j
3180 found = True
3181 break
3182 elif re.search(r'^%s[^ ]' % indent, hierarchy[j]):
3183 stripped_text = hierarchy[j]
3184 if r'<link linkend' not in entry_text:
3185 stripped_text = re.sub(r'<link linkend="[A-Za-z]*">', '', stripped_text)
3186 stripped_text = re.sub(r'</link>', '', stripped_text)
3188 if entry_text < stripped_text:
3189 last_index = j
3190 found = True
3191 break
3193 # Append to bottom
3194 if not found:
3195 last_index = len(hierarchy)
3197 logging.debug('insert at %d: %s', last_index, entry_text)
3198 hierarchy.insert(last_index, entry_text)
3199 last_index += 1
3200 else:
3201 # Already have this one, make sure we use the not linked version
3202 if r'<link linkend' not in entry_text:
3203 hierarchy[j] = entry_text
3205 # Remember index as base insert point
3206 last_index = index + 1
3208 level += 1
3210 # Output the children, indented and with links.
3211 logging.info('%d children', len(children))
3212 for i in range(len(children)):
3213 sid = common.CreateValidSGMLID(children[i])
3214 indented_text = ' ' * (level * 4) + "<link linkend=\"%s\">%s</link>" % (sid, children[i])
3215 logging.debug('insert at %d: %s', last_index, indented_text)
3216 hierarchy.insert(last_index, indented_text)
3217 last_index += 1
3218 return hierarchy
3221def GetInterfaces(gobject):
3222 """Generate interface implementation graph.
3224 Returns the DocBook output describing the interfaces
3225 implemented by a class. It uses the global Interfaces hash.
3227 Args:
3228 object (str): the GObject subclass.
3230 Returns:
3231 str: implemented interfaces
3232 """
3233 text = ''
3234 # Find object in the objects array.
3235 if gobject in Interfaces:
3236 ifaces = Interfaces[gobject].split()
3237 text = '''<para>
3238%s implements
3239''' % gobject
3240 count = len(ifaces)
3241 for i in range(count):
3242 sid = common.CreateValidSGMLID(ifaces[i])
3243 text += " <link linkend=\"%s\">%s</link>" % (sid, ifaces[i])
3244 if i < count - 2:
3245 text += ', '
3246 elif i < count - 1:
3247 text += ' and '
3248 else:
3249 text += '.'
3250 text += '</para>\n'
3251 return text
3254def GetImplementations(gobject):
3255 """Generate interface usage graph.
3257 Returns the DocBook output describing the implementations
3258 of an interface. It uses the global Interfaces hash.
3260 Args:
3261 object (str): the GObject subclass.
3263 Returns:
3264 str: interface implementations
3265 """
3266 text = ''
3267 impls = []
3268 for key in Interfaces:
3269 if re.search(r'\b%s\b' % gobject, Interfaces[key]):
3270 impls.append(key)
3272 count = len(impls)
3273 if count > 0:
3274 impls.sort()
3275 text = '''<para>
3276%s is implemented by
3277''' % gobject
3278 for i in range(count):
3279 sid = common.CreateValidSGMLID(impls[i])
3280 text += " <link linkend=\"%s\">%s</link>" % (sid, impls[i])
3281 if i < count - 2:
3282 text += ', '
3283 elif i < count - 1:
3284 text += ' and '
3285 else:
3286 text += '.'
3287 text += '</para>\n'
3288 return text
3291def GetPrerequisites(iface):
3292 """Generates interface requirements.
3294 Returns the DocBook output describing the prerequisites
3295 of an interface. It uses the global Prerequisites hash.
3296 Args:
3297 iface (str): the interface.
3299 Returns:
3300 str: required interfaces
3301 """
3303 text = ''
3304 if iface in Prerequisites:
3305 text = '''<para>
3306%s requires
3307''' % iface
3308 prereqs = Prerequisites[iface].split()
3309 count = len(prereqs)
3310 for i in range(count):
3311 sid = common.CreateValidSGMLID(prereqs[i])
3312 text += " <link linkend=\"%s\">%s</link>" % (sid, prereqs[i])
3313 if i < count - 2:
3314 text += ', '
3315 elif i < count - 1:
3316 text += ' and '
3317 else:
3318 text += '.'
3319 text += '</para>\n'
3320 return text
3323def GetDerived(iface):
3324 """
3325 Returns the DocBook output describing the derived interfaces
3326 of an interface. It uses the global %Prerequisites hash.
3328 Args:
3329 iface (str): the interface.
3331 Returns:
3332 str: derived interfaces
3333 """
3334 text = ''
3335 derived = []
3336 for key in Prerequisites:
3337 if re.search(r'\b%s\b' % iface, Prerequisites[key]):
3338 derived.append(key)
3340 count = len(derived)
3341 if count > 0:
3342 derived.sort()
3343 text = '''<para>
3344%s is required by
3345''' % iface
3346 for i in range(count):
3347 sid = common.CreateValidSGMLID(derived[i])
3348 text += " <link linkend=\"%s\">%s</link>" % (sid, derived[i])
3349 if i < count - 2:
3350 text += ', '
3351 elif i < count - 1:
3352 text += ' and '
3353 else:
3354 text += '.'
3355 text += '</para>\n'
3356 return text
3359def GetSignals(gobject):
3360 """Generate signal docs.
3362 Returns the synopsis and detailed description DocBook output
3363 for the signal handlers of a given GObject subclass.
3365 Args:
3366 object (str): the GObject subclass, e.g. 'GtkButton'.
3368 Returns:
3369 str: signal docs
3370 """
3371 synop = ''
3372 desc = ''
3374 for i in range(len(SignalObjects)):
3375 if SignalObjects[i] == gobject:
3376 logging.info("Found signal: %s", SignalNames[i])
3377 name = SignalNames[i]
3378 symbol = '%s::%s' % (gobject, name)
3379 sid = common.CreateValidSGMLID('%s-%s' % (gobject, name))
3381 desc += u"<refsect2 id=\"%s\" role=\"signal\"><title>The <literal>“%s”</literal> signal</title>\n" % (
3382 sid, name)
3383 desc += MakeIndexterms(symbol, sid)
3384 desc += "\n"
3385 desc += OutputSymbolExtraLinks(symbol)
3387 desc += "<programlisting language=\"C\">"
3389 m = re.search(r'\s*(const\s+)?(\w+)\s*(\**)', SignalReturns[i])
3390 type_modifier = m.group(1) or ''
3391 gtype = m.group(2)
3392 pointer = m.group(3)
3393 xref = MakeXRef(gtype, tagify(gtype, "returnvalue"))
3395 ret_type_output = '%s%s%s' % (type_modifier, xref, pointer)
3396 callback_name = "user_function"
3397 desc += '%s\n%s (' % (ret_type_output, callback_name)
3399 indentation = ' ' * (len(callback_name) + 2)
3401 sourceparams = SourceSymbolParams.get(symbol)
3402 sourceparam_names = None
3403 if sourceparams:
3404 sourceparam_names = list(sourceparams) # keys as list
3405 params = SignalPrototypes[i].splitlines()
3406 type_len = len("gpointer")
3407 name_len = len("user_data")
3408 # do two passes, the first one is to calculate padding
3409 for l in range(2):
3410 for j in range(len(params)):
3411 param_name = None
3412 # allow alphanumerics, '_', '[' & ']' in param names
3413 m = re.search(r'^\s*(\w+)\s*(\**)\s*([\w\[\]]+)\s*$', params[j])
3414 if m:
3415 gtype = m.group(1)
3416 pointer = m.group(2)
3417 if sourceparam_names:
3418 if j < len(sourceparam_names):
3419 param_name = sourceparam_names[j]
3420 logging.info('from sourceparams: "%s" (%d: %s)', param_name, j, params[j])
3421 # we're missing the docs for this param, don't warn here though
3422 else:
3423 param_name = m.group(3)
3424 logging.info('from params: "%s" (%d: %s)', param_name, j, params[j])
3426 if not param_name:
3427 param_name = "arg%d" % j
3429 if l == 0:
3430 if len(gtype) + len(pointer) > type_len:
3431 type_len = len(gtype) + len(pointer)
3432 if len(param_name) > name_len:
3433 name_len = len(param_name)
3434 else:
3435 logging.info("signal arg[%d]: '%s'", j, param_name)
3436 xref = MakeXRef(gtype, tagify(gtype, "type"))
3437 pad = ' ' * (type_len - len(gtype) - len(pointer))
3438 desc += '%s%s %s%s,\n' % (xref, pad, pointer, param_name)
3439 desc += indentation
3441 else:
3442 common.LogWarning(*GetSymbolSourceLocation(symbol),
3443 "Can't parse arg: %s\nArgs:%s" % (params[j], SignalPrototypes[i]))
3445 xref = MakeXRef("gpointer", tagify("gpointer", "type"))
3446 pad = ' ' * (type_len - len("gpointer"))
3447 desc += '%s%s user_data)' % (xref, pad)
3448 desc += "</programlisting>\n"
3450 flags = SignalFlags[i]
3451 flags_string = ''
3452 if flags:
3453 if 'f' in flags:
3454 flags_string = "<link linkend=\"G-SIGNAL-RUN-FIRST:CAPS\">Run First</link>"
3456 elif 'l' in flags:
3457 flags_string = "<link linkend=\"G-SIGNAL-RUN-LAST:CAPS\">Run Last</link>"
3459 elif 'c' in flags:
3460 flags_string = "<link linkend=\"G-SIGNAL-RUN-CLEANUP:CAPS\">Cleanup</link>"
3461 flags_string = "Cleanup"
3463 if 'r' in flags:
3464 if flags_string:
3465 flags_string += " / "
3466 flags_string = "<link linkend=\"G-SIGNAL-NO-RECURSE:CAPS\">No Recursion</link>"
3468 if 'd' in flags:
3469 if flags_string:
3470 flags_string += " / "
3471 flags_string = "<link linkend=\"G-SIGNAL-DETAILED:CAPS\">Has Details</link>"
3473 if 'a' in flags:
3474 if flags_string:
3475 flags_string += " / "
3476 flags_string = "<link linkend=\"G-SIGNAL-ACTION:CAPS\">Action</link>"
3478 if 'h' in flags:
3479 if flags_string:
3480 flags_string += " / "
3481 flags_string = "<link linkend=\"G-SIGNAL-NO-HOOKS:CAPS\">No Hooks</link>"
3483 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" % (
3484 ret_type_output, sid, name, flags_string)
3486 parameters = OutputParamDescriptions("SIGNAL", symbol, None)
3487 logging.info("formatted signal params: '%s' -> '%s'", symbol, parameters)
3489 AllSymbols[symbol] = 1
3490 if symbol in SymbolDocs:
3491 symbol_docs = ConvertMarkDown(symbol, SymbolDocs[symbol])
3493 desc += symbol_docs
3495 if not IsEmptyDoc(SymbolDocs[symbol]):
3496 AllDocumentedSymbols[symbol] = 1
3498 if symbol in SymbolAnnotations:
3499 param_desc = SymbolAnnotations[symbol]
3500 param_desc, param_annotations = ExpandAnnotation(symbol, param_desc)
3501 if param_annotations != '':
3502 desc += "\n<para>%s</para>" % param_annotations
3504 desc += MakeDeprecationNote(symbol)
3506 desc += parameters
3507 if flags_string:
3508 desc += "<para>Flags: %s</para>\n" % flags_string
3510 desc += OutputSymbolTraits(symbol)
3511 desc += "</refsect2>"
3513 return (synop, desc)
3516def GetArgs(gobject):
3517 """Generate property docs.
3519 Returns the synopsis and detailed description DocBook output
3520 for the Args of a given GtkObject subclass.
3522 Args:
3523 object (str): the GObject subclass, e.g. 'GtkButton'.
3525 Returns:
3526 str: property docs
3527 """
3528 synop = ''
3529 desc = ''
3530 child_synop = ''
3531 child_desc = ''
3532 style_synop = ''
3533 style_desc = ''
3535 for i in range(len(ArgObjects)):
3536 if ArgObjects[i] == gobject:
3537 logging.info("Found arg: %s", ArgNames[i])
3538 name = ArgNames[i]
3539 flags = ArgFlags[i]
3540 flags_string = ''
3541 kind = ''
3542 id_sep = ''
3544 if 'c' in flags:
3545 kind = "child property"
3546 id_sep = "c-"
3547 elif 's' in flags:
3548 kind = "style property"
3549 id_sep = "s-"
3550 else:
3551 kind = "property"
3553 # Remember only one colon so we don't clash with signals.
3554 symbol = '%s:%s' % (gobject, name)
3555 # use two dashes and ev. an extra separator here for the same reason.
3556 sid = common.CreateValidSGMLID('%s--%s%s' % (gobject, id_sep, name))
3558 atype = ArgTypes[i]
3559 type_output = None
3560 arange = ArgRanges[i]
3561 range_output = CreateValidSGML(arange)
3562 default = ArgDefaults[i]
3563 default_output = CreateValidSGML(default)
3565 if atype == "GtkString":
3566 atype = "char *"
3568 if atype == "GtkSignal":
3569 atype = "GtkSignalFunc, gpointer"
3570 type_output = MakeXRef("GtkSignalFunc") + ", " + MakeXRef("gpointer")
3571 elif re.search(r'^(\w+)\*$', atype):
3572 m = re.search(r'^(\w+)\*$', atype)
3573 type_output = MakeXRef(m.group(1), tagify(m.group(1), "type")) + " *"
3574 else:
3575 type_output = MakeXRef(atype, tagify(atype, "type"))
3577 if 'r' in flags:
3578 flags_string = "Read"
3580 if 'w' in flags:
3581 if flags_string:
3582 flags_string += " / "
3583 flags_string += "Write"
3585 if 'x' in flags:
3586 if flags_string:
3587 flags_string += " / "
3588 flags_string += "Construct"
3590 if 'X' in flags:
3591 if flags_string:
3592 flags_string += " / "
3593 flags_string += "Construct Only"
3595 AllSymbols[symbol] = 1
3596 blurb = ''
3597 if symbol in SymbolDocs and not IsEmptyDoc(SymbolDocs[symbol]):
3598 blurb = ConvertMarkDown(symbol, SymbolDocs[symbol])
3599 logging.info(".. [%s][%s]", SymbolDocs[symbol], blurb)
3600 AllDocumentedSymbols[symbol] = 1
3602 else:
3603 if ArgBlurbs[i] != '':
3604 blurb = "<para>" + CreateValidSGML(ArgBlurbs[i]) + "</para>"
3605 AllDocumentedSymbols[symbol] = 1
3606 else:
3607 # FIXME: print a warning?
3608 logging.info(".. no description")
3610 pad1 = ''
3611 if len(name) < 24:
3612 pad1 = " " * (24 - len(name))
3614 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" % (
3615 type_output, sid, name, flags_string)
3616 arg_desc = u"<refsect2 id=\"%s\" role=\"property\"><title>The <literal>“%s”</literal> %s</title>\n" % (
3617 sid, name, kind)
3618 arg_desc += MakeIndexterms(symbol, sid)
3619 arg_desc += "\n"
3620 arg_desc += OutputSymbolExtraLinks(symbol)
3622 arg_desc += u"<programlisting> “%s”%s %s</programlisting>\n" % (name, pad1, type_output)
3623 arg_desc += blurb
3624 if symbol in SymbolAnnotations:
3625 param_desc = SymbolAnnotations[symbol]
3626 param_desc, param_annotations = ExpandAnnotation(symbol, param_desc)
3627 if param_annotations != '':
3628 arg_desc += "\n<para>%s</para>" % param_annotations
3630 arg_desc += MakeDeprecationNote(symbol)
3632 arg_desc += "<para>Owner: %s</para>\n" % gobject
3634 if flags_string:
3635 arg_desc += "<para>Flags: %s</para>\n" % flags_string
3637 if arange != '':
3638 arg_desc += "<para>Allowed values: %s</para>\n" % range_output
3640 if default != '':
3641 arg_desc += "<para>Default value: %s</para>\n" % default_output
3643 arg_desc += OutputSymbolTraits(symbol)
3644 arg_desc += "</refsect2>\n"
3646 if 'c' in flags:
3647 child_synop += arg_synop
3648 child_desc += arg_desc
3650 elif 's' in flags:
3651 style_synop += arg_synop
3652 style_desc += arg_desc
3654 else:
3655 synop += arg_synop
3656 desc += arg_desc
3658 return (synop, child_synop, style_synop, desc, child_desc, style_desc)
3661def GetActions(gobject):
3662 """Generate action docs.
3664 Returns the synopsis and detailed description DocBook output
3665 for the actions of a given GtkWidget subclass.
3667 Args:
3668 object (str): the GObject subclass, e.g. 'GtkButton'.
3670 Returns:
3671 str: action docs
3672 """
3673 synop = ''
3674 desc = ''
3676 for i in range(len(ActionObjects)):
3677 if ActionObjects[i] == gobject:
3678 logging.info("Found action: %s", ActionNames[i])
3679 name = ActionNames[i]
3680 params = ActionParams[i]
3681 prop = ActionProperties[i]
3683 # Remember: pipe, so we don't clash with signals.
3684 symbol = '%s|%s' % (gobject, name)
3685 sid = common.CreateValidSGMLID(symbol)
3687 AllSymbols[symbol] = 1
3688 blurb = ''
3689 if symbol in SymbolDocs and not IsEmptyDoc(SymbolDocs[symbol]):
3690 blurb = ConvertMarkDown(symbol, SymbolDocs[symbol])
3691 logging.info(".. [%s][%s]", SymbolDocs[symbol], blurb)
3692 AllDocumentedSymbols[symbol] = 1
3694 else:
3695 # FIXME: print a warning?
3696 logging.info(".. no description")
3698 pad1 = ''
3699 if len(name) < 24:
3700 pad1 = " " * (24 - len(name))
3702 action_synop = "<row><entry></entry><entry role=\"action_name\"><link linkend=\"%s\">%s</link></entry><entry role=\"parameter_type\">%s</entry></row>\n" % (
3703 sid, name, params)
3704 action_desc = u"<refsect2 id=\"%s\" role=\"action\"><title>The <literal>“%s”</literal> action</title>\n" % (
3705 sid, name)
3706 action_desc += MakeIndexterms(symbol, sid)
3707 action_desc += "\n"
3708 action_desc += OutputSymbolExtraLinks(symbol)
3709 if blurb != '':
3710 action_desc += blurb
3711 elif prop != '':
3712 action_desc += "<para>The %s action sets the %s property.</para>\n" % (name, MakeHashXRef (gobject + ':' + prop, "type"))
3713 action_desc += MakeDeprecationNote(symbol)
3715 if params != '':
3716 action_desc += "<para>Parameter type: %s</para>\n" % params
3718 action_desc += OutputParamDescriptions("ACTION", symbol, None)
3719 action_desc += OutputSymbolTraits(symbol)
3720 action_desc += "</refsect2>\n"
3722 synop += action_synop
3723 desc += action_desc
3725 return (synop, desc)
3727def IgnorePath(path, source_dirs, ignore_files):
3728 for sdir in source_dirs:
3729 # Cut off base directory
3730 m1 = re.search(r'^%s/(.*)$' % re.escape(sdir), path)
3731 if m1:
3732 # Check if the filename is in the ignore list.
3733 m2 = re.search(r'(\s|^)%s(\s|$)' % re.escape(m1.group(1)), ignore_files)
3734 if m2:
3735 logging.info("Skipping path: %s", path)
3736 return True
3737 else:
3738 logging.info("No match for: %s", m1.group(1))
3739 else:
3740 logging.info("No match for: %s", path)
3741 return False
3744def ReadSourceDocumentation(source_dir, suffix_list, source_dirs, ignore_files):
3745 """Read the documentation embedded in comment blocks in the source code.
3747 It recursively descends the source directory looking for source files and
3748 scans them looking for specially-formatted comment blocks.
3750 Args:
3751 source_dir (str): the directory to scan.
3752 suffix_list (list): extensions to check
3753 """
3754 if IgnorePath(source_dir, source_dirs, ignore_files):
3755 return
3757 logging.info("Scanning source directory: %s", source_dir)
3759 # This array holds any subdirectories found.
3760 subdirs = []
3762 for ifile in sorted(os.listdir(source_dir)):
3763 logging.debug("... : %s", ifile)
3764 if ifile.startswith('.'):
3765 continue
3766 fname = os.path.join(source_dir, ifile)
3767 if os.path.isdir(fname):
3768 subdirs.append(fname)
3769 else:
3770 for suffix in suffix_list:
3771 if ifile.endswith(suffix):
3772 if not IgnorePath(fname, source_dirs, ignore_files):
3773 ScanSourceFile(fname, ignore_files)
3774 break
3776 # Now recursively scan the subdirectories.
3777 for sdir in subdirs:
3778 ReadSourceDocumentation(sdir, suffix_list, source_dirs, ignore_files)
3781def ScanSourceFile(ifile, ignore_files):
3782 """Scans one source file looking for specially-formatted comment blocks.
3784 Later MergeSourceDocumentation() is copying over the doc blobs that are not
3785 suppressed/ignored.
3787 Args:
3788 ifile (str): the file to scan.
3789 """
3790 m = re.search(r'^.*[\/\\]([^\/\\]*)$', ifile)
3791 if m:
3792 basename = m.group(1)
3793 else:
3794 common.LogWarning(ifile, 1, "Can't find basename for this filename.")
3795 basename = ifile
3797 # Check if the basename is in the list of files to ignore.
3798 if re.search(r'(\s|^)%s(\s|$)' % re.escape(basename), ignore_files):
3799 logging.info("Skipping source file: %s", ifile)
3800 return
3802 logging.info("Scanning %s", ifile)
3804 with open(ifile, 'r', encoding='utf-8') as src:
3805 input_lines = src.readlines()
3807 for c in ScanSourceContent(input_lines, ifile):
3808 ParseCommentBlock(c[0], c[1], ifile)
3810 logging.info("Scanning %s done", ifile)
3813def ScanSourceContent(input_lines, ifile=''):
3814 """Scans the source code lines for specially-formatted comment blocks.
3816 Updates global state in SourceSymbolDocs, SymbolSourceLocation,
3817 Since, StabilityLevel, Deprecated, SymbolAnnotations.
3819 Might read from global state in DeclarationTypes
3821 Args:
3822 input_lines (list): list of source code lines
3823 ifile (str): file name of the source file (for reporting)
3825 Returns:
3826 list: tuples with comment block and its starting line
3827 """
3828 comments = []
3829 in_comment_block = False
3830 line_number = 0
3831 comment = []
3832 starting_line = 0
3833 for line in input_lines:
3834 line_number += 1
3836 if not in_comment_block:
3837 # Look for the start of a comment block.
3838 if re.search(r'^\s*/\*.*\*/', line):
3839 # one-line comment - not gtkdoc
3840 pass
3841 elif re.search(r'^\s*/\*\*\s', line):
3842 logging.info("Found comment block start")
3844 in_comment_block = True
3845 comment = []
3846 starting_line = line_number + 1
3847 else:
3848 # Look for end of comment
3849 if re.search(r'^\s*\*+/', line):
3850 comments.append((comment, starting_line))
3851 in_comment_block = False
3852 continue
3854 # Get rid of ' * ' at start of every line in the comment block.
3855 line = re.sub(r'^\s*\*\s?', '', line)
3856 # But make sure we don't get rid of the newline at the end.
3857 if not line.endswith('\n'):
3858 line += "\n"
3860 logging.info("scanning :%s", line.strip())
3861 comment.append(line)
3863 return comments
3866def SegmentCommentBlock(lines, line_number=0, ifile=''):
3867 """Cut a single comment block into segments.
3869 Args:
3870 lines (list): the comment block
3871 line_number (int): the first line of the block (for reporting)
3872 ifile (str): file name of the source file (for reporting)
3874 Returns:
3875 (str, dict): symbol name and dict of comment segments
3876 """
3877 symbol = None
3878 in_part = ''
3879 segments = {'body': ''}
3880 params = OrderedDict()
3881 param_name = None
3882 param_indent = None
3883 line_number -= 1
3884 for line in lines:
3885 line_number += 1
3886 logging.info("scanning[%s] :%s", in_part, line.strip())
3888 # If we haven't found the symbol name yet, look for it.
3889 # We need to allow for the following cases:
3890 # function:
3891 # Class::signal:
3892 # Class:property:
3893 # Class|action:
3894 # Signal and property names can contain dashes, action names
3895 # can contain period.
3896 # In all cases, there might be annotations in parentheses
3897 # following the symbol name.
3898 if not symbol:
3899 m1 = re.search(r'^\s*((SECTION|PROGRAM):\s*\S+)', line)
3900 m2 = re.search(r'^\s*([\w:.|-]*\w)\s*:?\s*(\(.+?\)\s*)*$', line)
3901 if m1:
3902 symbol = m1.group(1)
3903 logging.info("docs found in source for : '%s'", symbol)
3904 elif m2:
3905 symbol = m2.group(1)
3906 logging.info("docs found in source for : '%s'", symbol)
3907 if m2.group(2):
3908 annotation = m2.group(2).strip()
3909 if annotation != '':
3910 SymbolAnnotations[symbol] = annotation
3911 logging.info("remaining text for %s: '%s'", symbol, annotation)
3913 continue
3915 if in_part == "body":
3916 # Get rid of 'Description:'
3917 line = re.sub(r'^\s*Description:', '', line)
3919 m1 = re.search(r'^\s*(returns|return\s+value):', line, flags=re.I)
3920 m2 = re.search(r'^\s*since:', line, flags=re.I)
3921 m3 = re.search(r'^\s*deprecated:', line, flags=re.I)
3922 m4 = re.search(r'^\s*stability:', line, flags=re.I)
3924 if m1:
3925 if in_part != '':
3926 in_part = "return"
3927 segments[in_part] = line[m1.end():]
3928 continue
3929 if m2:
3930 if in_part != "param":
3931 in_part = "since"
3932 segments[in_part] = line[m2.end():]
3933 continue
3934 elif m3:
3935 if in_part != "param":
3936 in_part = "deprecated"
3937 segments[in_part] = line[m3.end():]
3938 continue
3939 elif m4:
3940 in_part = "stability"
3941 segments[in_part] = line[m4.end():]
3942 continue
3944 if in_part in ["body", "return", "since", "stability", "deprecated"]:
3945 segments[in_part] += line
3946 continue
3948 # We must be in the parameters. Check for the empty line below them.
3949 if re.search(r'^\s*$', line):
3950 # TODO: only switch if next line starts without indent?
3951 in_part = "body"
3952 continue
3954 # Look for a parameter name.
3955 m = re.search(r'^\s*@(.+?)\s*:\s*', line)
3956 if m:
3957 param_name = m.group(1)
3958 param_desc = line[m.end():]
3959 param_indent = None
3961 # Allow varargs variations
3962 if re.search(r'^\.\.\.$', param_name):
3963 param_name = "..."
3965 logging.info("Found param for symbol %s : '%s'= '%s'", symbol, param_name, line)
3966 params[param_name] = param_desc
3967 in_part = "param"
3968 continue
3969 elif in_part == '':
3970 logging.info("continuation for %s annotation '%s'", symbol, line)
3971 annotation = re.sub(r'^\s+|\s+$', '', line)
3972 if symbol in SymbolAnnotations:
3973 SymbolAnnotations[symbol] += annotation
3974 else:
3975 SymbolAnnotations[symbol] = annotation
3976 continue
3978 # We must be in the middle of a parameter description, so add it on
3979 # to the last element in @params.
3980 if not param_name:
3981 common.LogWarning(ifile, line_number,
3982 "Parsing comment block file : parameter expected, but got '%s'" % line)
3983 else:
3984 if not param_indent:
3985 # determine indentation of first continuation line
3986 spc = len(line) - len(line.lstrip(' '))
3987 if spc > 0:
3988 param_indent = spc
3989 logging.debug("Found param-indentation of %d", param_indent)
3990 if param_indent:
3991 # cut common indentation (after double checking that it is all spaces)
3992 if line[:param_indent].strip() == '':
3993 line = line[param_indent:]
3994 else:
3995 logging.warning("Not cutting param-indentation for %s: '%s'",
3996 param_name, line[:param_indent])
3998 params[param_name] += line
4000 return (symbol, segments, params)
4003def ParseCommentBlockSegments(symbol, segments, params, line_number=0, ifile=''):
4004 """Parse the comemnts block segments.
4006 Args:
4007 symbol (str): the symbol name
4008 segments(dict): the comment block segments (except params)
4009 parans (dict): the comment block params
4010 line_number (int): the first line of the block (for reporting)
4011 ifile (str): file name of the source file (for reporting)
4013 """
4014 # Add the return value description to the end of the params.
4015 if "return" in segments:
4016 # TODO(ensonic): check for duplicated Return docs
4017 # common.LogWarning(file, line_number, "Multiple Returns for %s." % symbol)
4018 params['Returns'] = segments["return"]
4020 # Convert special characters
4021 segments["body"] = ConvertXMLChars(symbol, segments["body"])
4022 for (param_name, param_desc) in params.items():
4023 params[param_name] = ConvertXMLChars(symbol, param_desc)
4025 # Handle Section docs
4026 m = re.search(r'SECTION:\s*(.*)', symbol)
4027 m2 = re.search(r'PROGRAM:\s*(.*)', symbol)
4028 if m:
4029 real_symbol = m.group(1)
4031 logging.info("SECTION DOCS found in source for : '%s'", real_symbol)
4032 for param_name, param_desc in params.items():
4033 logging.info(" '" + param_name + "'")
4034 param_name = param_name.lower()
4035 if param_name in ['image', 'include', 'section_id', 'see_also', 'short_description', 'stability', 'title']:
4036 key = real_symbol + ':' + param_name
4037 SourceSymbolDocs[key] = param_desc
4038 SymbolSourceLocation[key] = (ifile, line_number)
4040 key = real_symbol + ":long_description"
4041 if key not in KnownSymbols or KnownSymbols[key] != 1:
4042 common.LogWarning(
4043 ifile, line_number, "Section %s is not defined in the %s-sections.txt file." % (real_symbol, MODULE))
4044 SourceSymbolDocs[key] = segments["body"]
4045 SymbolSourceLocation[key] = (ifile, line_number)
4046 elif m2:
4047 real_symbol = m2.group(1)
4048 section_id = None
4050 logging.info("PROGRAM DOCS found in source for '%s'", real_symbol)
4051 for param_name, param_desc in params.items():
4052 logging.info("PROGRAM key %s: '%s'", real_symbol, param_name)
4053 param_name = param_name.lower()
4054 if param_name in ['returns', 'section_id', 'see_also', 'short_description', 'synopsis']:
4055 key = real_symbol + ':' + param_name
4056 logging.info("PROGRAM value %s: '%s'", real_symbol, param_desc.rstrip())
4057 SourceSymbolDocs[key] = param_desc.rstrip()
4058 SymbolSourceLocation[key] = (ifile, line_number)
4059 elif re.search(r'^(-.*)', param_name):
4060 logging.info("PROGRAM opts: '%s': '%s'", param_name, param_desc)
4061 key = real_symbol + ":options"
4062 opts = []
4063 opts_str = SourceSymbolDocs.get(key)
4064 if opts_str:
4065 opts = opts_str.split('\t')
4066 opts.append(param_name)
4067 opts.append(param_desc)
4069 logging.info("Setting options for symbol: %s: '%s'", real_symbol, '\t'.join(opts))
4070 SourceSymbolDocs[key] = '\t'.join(opts)
4072 key = real_symbol + ":long_description"
4073 SourceSymbolDocs[key] = segments["body"]
4074 SymbolSourceLocation[key] = (ifile, line_number)
4076 # TODO(ensonic): we need to track these somehow and output the files
4077 # later, see comment in Run()
4078 section_id = SourceSymbolDocs.get(real_symbol + ":section_id")
4079 if section_id and section_id.strip() != '':
4080 # Remove trailing blanks and use as is
4081 section_id = section_id.rstrip()
4082 else:
4083 section_id = common.CreateValidSGMLID('%s-%s' % (MODULE, real_symbol))
4084 OutputProgramDBFile(real_symbol, section_id)
4086 else:
4087 logging.info("SYMBOL DOCS found in source for : '%s'", symbol)
4088 SourceSymbolDocs[symbol] = segments["body"]
4089 SourceSymbolParams[symbol] = params
4090 SymbolSourceLocation[symbol] = (ifile, line_number)
4092 if "since" in segments:
4093 arr = segments["since"].splitlines()
4094 desc = arr[0].strip()
4095 extra_lines = arr[1:]
4096 logging.info("Since(%s) : [%s]", symbol, desc)
4097 Since[symbol] = ConvertXMLChars(symbol, desc)
4098 if len(extra_lines) > 1:
4099 common.LogWarning(ifile, line_number, "multi-line since docs found")
4101 if "stability" in segments:
4102 desc = ParseStabilityLevel(
4103 segments["stability"], ifile, line_number, "Stability level for %s" % symbol)
4104 StabilityLevel[symbol] = ConvertXMLChars(symbol, desc)
4106 if "deprecated" in segments:
4107 if symbol not in Deprecated:
4108 # don't warn for signals and properties
4109 # if ($symbol !~ m/::?(.*)/)
4110 if symbol in DeclarationTypes:
4111 common.LogWarning(ifile, line_number,
4112 "%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)
4114 Deprecated[symbol] = ConvertXMLChars(symbol, segments["deprecated"])
4117def ParseCommentBlock(lines, line_number=0, ifile=''):
4118 """Parse a single comment block.
4120 Args:
4121 lines (list): the comment block
4122 line_number (int): the first line of the block (for reporting)
4123 ifile (str): file name of the source file (for reporting)
4124 """
4125 (symbol, segments, params) = SegmentCommentBlock(lines, line_number, ifile)
4126 if not symbol:
4127 # maybe its not even meant to be a gtk-doc comment?
4128 common.LogWarning(ifile, line_number, "Symbol name not found at the start of the comment block.")
4129 return
4131 ParseCommentBlockSegments(symbol, segments, params, line_number, ifile)
4134def OutputMissingDocumentation():
4135 """Outputs report of documentation coverage to a file.
4137 Returns:
4138 bool: True if the report was updated
4139 """
4140 old_undocumented_file = os.path.join(ROOT_DIR, MODULE + "-undocumented.txt")
4141 new_undocumented_file = os.path.join(ROOT_DIR, MODULE + "-undocumented.new")
4143 n_documented = 0
4144 n_incomplete = 0
4145 total = 0
4146 symbol = None
4147 percent = None
4148 buffer = ''
4149 buffer_deprecated = ''
4150 buffer_descriptions = ''
4152 UNDOCUMENTED = open(new_undocumented_file, 'w', encoding='utf-8')
4154 for symbol in sorted(AllSymbols.keys()):
4155 # FIXME: should we print common.LogWarnings for undocumented stuff?
4156 m = re.search(
4157 r':(title|long_description|short_description|see_also|stability|include|section_id|image)', symbol)
4158 m2 = re.search(r':(long_description|short_description)', symbol)
4159 if not m:
4160 total += 1
4161 if symbol in AllDocumentedSymbols:
4162 n_documented += 1
4163 if symbol in AllIncompleteSymbols:
4164 n_incomplete += 1
4165 buffer += symbol + " (" + AllIncompleteSymbols[symbol] + ")\n"
4167 elif symbol in Deprecated:
4168 if symbol in AllIncompleteSymbols:
4169 n_incomplete += 1
4170 buffer_deprecated += symbol + " (" + AllIncompleteSymbols[symbol] + ")\n"
4171 else:
4172 buffer_deprecated += symbol + "\n"
4174 else:
4175 if symbol in AllIncompleteSymbols:
4176 n_incomplete += 1
4177 buffer += symbol + " (" + AllIncompleteSymbols[symbol] + ")\n"
4178 else:
4179 buffer += symbol + "\n"
4181 elif m2:
4182 total += 1
4183 if (symbol in SymbolDocs and len(SymbolDocs[symbol]) > 0)\
4184 or (symbol in AllDocumentedSymbols and AllDocumentedSymbols[symbol] > 0):
4185 n_documented += 1
4186 else:
4187 buffer_descriptions += symbol + "\n"
4189 if total == 0:
4190 percent = 100
4191 else:
4192 percent = (n_documented / total) * 100.0
4194 UNDOCUMENTED.write("%.0f%% symbol docs coverage.\n" % percent)
4195 UNDOCUMENTED.write("%s symbols documented.\n" % n_documented)
4196 UNDOCUMENTED.write("%s symbols incomplete.\n" % n_incomplete)
4197 UNDOCUMENTED.write("%d not documented.\n" % (total - n_documented))
4199 if buffer_deprecated != '':
4200 buffer += "\n" + buffer_deprecated
4202 if buffer_descriptions != '':
4203 buffer += "\n" + buffer_descriptions
4205 if buffer != '':
4206 UNDOCUMENTED.write("\n\n" + buffer)
4208 UNDOCUMENTED.close()
4210 return common.UpdateFileIfChanged(old_undocumented_file, new_undocumented_file, 0)
4213def OutputUndeclaredSymbols():
4214 """Reports undeclared symbols.
4216 Outputs symbols that are listed in the section file, but have no declaration
4217 in the sources.
4219 Returns:
4220 bool: True if the report was updated
4221 """
4222 old_undeclared_file = os.path.join(ROOT_DIR, MODULE + "-undeclared.txt")
4223 new_undeclared_file = os.path.join(ROOT_DIR, MODULE + "-undeclared.new")
4225 with open(new_undeclared_file, 'w', encoding='utf-8') as out:
4226 if UndeclaredSymbols:
4227 out.write("\n".join(sorted(UndeclaredSymbols.keys())))
4228 out.write("\n")
4229 print("See %s-undeclared.txt for the list of undeclared symbols." % MODULE)
4231 return common.UpdateFileIfChanged(old_undeclared_file, new_undeclared_file, 0)
4234def OutputUnusedSymbols():
4235 """Reports unused documentation.
4237 Outputs symbols that are documented in comments, but not declared in the
4238 sources.
4240 Returns:
4241 bool: True if the report was updated
4242 """
4243 num_unused = 0
4244 old_unused_file = os.path.join(ROOT_DIR, MODULE + "-unused.txt")
4245 new_unused_file = os.path.join(ROOT_DIR, MODULE + "-unused.new")
4247 with open(new_unused_file, 'w', encoding='utf-8') as out:
4249 for symbol in sorted(Declarations.keys()):
4250 if symbol not in DeclarationOutput:
4251 out.write("%s\n" % symbol)
4252 num_unused += 1
4254 for symbol in sorted(AllUnusedSymbols.keys()):
4255 out.write(symbol + "(" + AllUnusedSymbols[symbol] + ")\n")
4256 num_unused += 1
4258 if num_unused != 0:
4259 common.LogWarning(
4260 old_unused_file, 1, "%d unused declarations. They should be added to %s-sections.txt in the appropriate place." % (num_unused, MODULE))
4262 return common.UpdateFileIfChanged(old_unused_file, new_unused_file, 0)
4265def OutputAllSymbols():
4266 """Outputs list of all symbols to a file."""
4267 new_symbols_file = os.path.join(ROOT_DIR, MODULE + "-symbols.txt")
4268 with open(new_symbols_file, 'w', encoding='utf-8') as out:
4269 for symbol in sorted(AllSymbols.keys()):
4270 out.write(symbol + "\n")
4273def OutputSymbolsWithoutSince():
4274 """Outputs list of all symbols without a since tag to a file."""
4275 new_nosince_file = os.path.join(ROOT_DIR, MODULE + "-nosince.txt")
4276 with open(new_nosince_file, 'w', encoding='utf-8') as out:
4277 for symbol in sorted(SourceSymbolDocs.keys()):
4278 if symbol in Since:
4279 out.write(symbol + "\n")
4282def CheckParamsDocumented(symbol, params):
4283 stype = DeclarationTypes.get(symbol)
4285 item = "Parameter"
4286 if stype:
4287 if stype == 'STRUCT':
4288 item = "Field"
4289 elif stype == 'ENUM':
4290 item = "Value"
4291 elif stype == 'UNION':
4292 item = "Field"
4293 else:
4294 stype = "SIGNAL"
4295 logging.info("Check param docs for %s, params: %s entries, type=%s", symbol, len(params), stype)
4297 if len(params) > 0:
4298 logging.info("params: %s", str(params))
4299 for (param_name, param_desc) in params.items():
4300 # Output a warning if the parameter is empty and remember for stats.
4301 if param_name != "void" and not re.search(r'\S', param_desc):
4302 if symbol in AllIncompleteSymbols:
4303 AllIncompleteSymbols[symbol] += ", " + param_name
4304 else:
4305 AllIncompleteSymbols[symbol] = param_name
4307 common.LogWarning(*GetSymbolSourceLocation(symbol),
4308 "%s description for %s::%s is missing in source code comment block." % (item, symbol, param_name))
4310 elif len(params) == 0:
4311 AllIncompleteSymbols[symbol] = "<items>"
4312 common.LogWarning(*GetSymbolSourceLocation(symbol),
4313 "%s descriptions for %s are missing in source code comment block." % (item, symbol))
4316def MergeSourceDocumentation():
4317 """Merges documentation read from a source file.
4319 Parameter descriptions override any in the template files.
4320 Function descriptions are placed before any description from
4321 the template files.
4322 """
4324 # add what's found in the source
4325 symbols = set(SourceSymbolDocs.keys())
4327 # and add known symbols from -sections.txt
4328 for symbol in KnownSymbols.keys():
4329 if KnownSymbols[symbol] == 1:
4330 symbols.add(symbol)
4332 logging.info("num source entries: %d", len(symbols))
4334 for symbol in symbols:
4335 AllSymbols[symbol] = 1
4337 if symbol in SourceSymbolDocs:
4338 logging.info("merging [%s] from source", symbol)
4340 # remove leading and training whitespaces
4341 src_docs = SourceSymbolDocs[symbol].strip()
4342 if src_docs != '':
4343 AllDocumentedSymbols[symbol] = 1
4345 SymbolDocs[symbol] = src_docs
4347 # merge parameters
4348 if symbol in SourceSymbolParams:
4349 param_docs = SourceSymbolParams[symbol]
4350 SymbolParams[symbol] = param_docs
4351 # if this symbol is documented, check if docs are complete
4352 # remove all xml-tags and whitespaces
4353 check_docs = re.sub(r'\s', '', re.sub(r'<.*?>', '', src_docs))
4354 if check_docs != '' and param_docs:
4355 CheckParamsDocumented(symbol, param_docs)
4356 else:
4357 logging.info("[%s] undocumented", symbol)
4359 logging.info("num doc entries: %d / %d", len(SymbolDocs), len(SourceSymbolDocs))
4362def IsEmptyDoc(doc):
4363 """Check if a doc-string is empty.
4365 It is also regarded as empty if it only consist of whitespace or e.g. FIXME.
4367 Args:
4368 doc (str): the doc-string
4370 Returns:
4371 bool: True if empty
4372 """
4373 if re.search(r'^\s*$', doc):
4374 return True
4375 if re.search(r'^\s*<para>\s*(FIXME)?\s*<\/para>\s*$', doc):
4376 return True
4377 return False
4380def ConvertMarkDown(symbol, text):
4381 md_to_db.Init()
4382 return md_to_db.MarkDownParse(text, symbol)
4385def ReadDeclarationsFile(ifile, override):
4386 """Reads in a file containing the function/macro/enum etc. declarations.
4388 Note that in some cases there are several declarations with
4389 the same name, e.g. for conditional macros. In this case we
4390 set a flag in the DeclarationConditional hash so the
4391 declaration is not shown in the docs.
4393 If a macro and a function have the same name, e.g. for
4394 g_object_ref, the function declaration takes precedence.
4396 Some opaque structs are just declared with 'typedef struct
4397 _name name;' in which case the declaration may be empty.
4398 The structure may have been found later in the header, so
4399 that overrides the empty declaration.
4401 Args:
4402 file (str): the declarations file to read
4403 override (bool): if declarations in this file should override
4404 any current declaration.
4405 """
4406 if override == 0:
4407 Declarations.clear()
4408 DeclarationTypes.clear()
4409 DeclarationConditional.clear()
4410 DeclarationOutput.clear()
4412 INPUT = open(ifile, 'r', encoding='utf-8')
4413 declaration_type = ''
4414 declaration_name = None
4415 declaration = None
4416 is_deprecated = 0
4417 line_number = 0
4418 for line in INPUT:
4419 line_number += 1
4420 # logging.debug("%s:%d: %s", ifile, line_number, line)
4421 if not declaration_type:
4422 m1 = re.search(r'^<([^>]+)>', line)
4423 if m1:
4424 declaration_type = m1.group(1)
4425 declaration_name = ''
4426 logging.info("Found declaration: %s", declaration_type)
4427 declaration = ''
4428 else:
4429 m2 = re.search(r'^<NAME>(.*)</NAME>', line)
4430 m3 = re.search(r'^<DEPRECATED/>', line)
4431 m4 = re.search(r'^</%s>' % declaration_type, line)
4432 if m2:
4433 declaration_name = m2.group(1)
4434 elif m3:
4435 is_deprecated = True
4436 elif m4:
4437 logging.info("Found end of declaration: %s, %s", declaration_type, declaration_name)
4438 # Check that the declaration has a name
4439 if declaration_name == '':
4440 common.LogWarning(ifile, line_number, declaration_type + " has no name.\n")
4442 # If the declaration is an empty typedef struct _XXX XXX
4443 # set the flag to indicate the struct has a typedef.
4444 if (declaration_type == 'STRUCT' or declaration_type == 'UNION') \
4445 and re.search(r'^\s*$', declaration):
4446 logging.info("Struct has typedef: %s", declaration_name)
4447 StructHasTypedef[declaration_name] = 1
4449 # Check if the symbol is already defined.
4450 if declaration_name in Declarations and override == 0:
4451 # Function declarations take precedence.
4452 if DeclarationTypes[declaration_name] == 'FUNCTION':
4453 # Ignore it.
4454 pass
4455 elif declaration_type == 'FUNCTION':
4456 if is_deprecated:
4457 Deprecated[declaration_name] = ''
4459 Declarations[declaration_name] = declaration
4460 DeclarationTypes[declaration_name] = declaration_type
4461 elif DeclarationTypes[declaration_name] == declaration_type:
4462 # If the existing declaration is empty, or is just a
4463 # forward declaration of a struct, override it.
4464 if declaration_type == 'STRUCT' or declaration_type == 'UNION':
4465 if re.search(r'^\s*((struct|union)\s+\w+\s*;)?\s*$', Declarations[declaration_name]):
4466 if is_deprecated:
4467 Deprecated[declaration_name] = ''
4468 Declarations[declaration_name] = declaration
4469 elif re.search(r'^\s*((struct|union)\s+\w+\s*;)?\s*$', declaration):
4470 # Ignore an empty or forward declaration.
4471 pass
4472 else:
4473 common.LogWarning(
4474 ifile, line_number, "Structure %s has multiple definitions." % declaration_name)
4476 else:
4477 # set flag in %DeclarationConditional hash for
4478 # multiply defined macros/typedefs.
4479 DeclarationConditional[declaration_name] = 1
4481 else:
4482 common.LogWarning(ifile, line_number, declaration_name + " has multiple definitions.")
4484 else:
4485 if is_deprecated:
4486 Deprecated[declaration_name] = ''
4488 Declarations[declaration_name] = declaration
4489 DeclarationTypes[declaration_name] = declaration_type
4490 logging.debug("added declaration: %s, %s, [%s]", declaration_type, declaration_name, declaration)
4492 declaration_type = ''
4493 is_deprecated = False
4494 else:
4495 declaration += line
4496 INPUT.close()
4499def ReadSignalsFile(ifile):
4500 """Reads information about object signals.
4502 It creates the arrays @SignalNames and @SignalPrototypes containing details
4503 about the signals. The first line of the SignalPrototype is the return type
4504 of the signal handler. The remaining lines are the parameters passed to it.
4505 The last parameter, "gpointer user_data" is always the same so is not included.
4507 Args:
4508 ifile (str): the file containing the signal handler prototype information.
4509 """
4510 in_signal = 0
4511 signal_object = None
4512 signal_name = None
4513 signal_returns = None
4514 signal_flags = None
4515 signal_prototype = None
4517 # Reset the signal info.
4518 SignalObjects[:] = []
4519 SignalNames[:] = []
4520 SignalReturns[:] = []
4521 SignalFlags[:] = []
4522 SignalPrototypes[:] = []
4524 if not os.path.isfile(ifile):
4525 return
4527 INPUT = open(ifile, 'r', encoding='utf-8')
4528 line_number = 0
4529 for line in INPUT:
4530 line_number += 1
4531 if not in_signal:
4532 if re.search(r'^<SIGNAL>', line):
4533 in_signal = 1
4534 signal_object = ''
4535 signal_name = ''
4536 signal_returns = ''
4537 signal_prototype = ''
4539 else:
4540 m = re.search(r'^<NAME>(.*)<\/NAME>', line)
4541 m2 = re.search(r'^<RETURNS>(.*)<\/RETURNS>', line)
4542 m3 = re.search(r'^<FLAGS>(.*)<\/FLAGS>', line)
4543 if m:
4544 signal_name = m.group(1)
4545 m1_2 = re.search(r'^(.*)::(.*)$', signal_name)
4546 if m1_2:
4547 signal_object = m1_2.group(1)
4548 signal_name = m1_2.group(2).replace('_', '-')
4549 logging.info("Found signal: %s", signal_name)
4550 else:
4551 common.LogWarning(ifile, line_number, "Invalid signal name: %s." % signal_name)
4553 elif m2:
4554 signal_returns = m2.group(1)
4555 elif m3:
4556 signal_flags = m3.group(1)
4557 elif re.search(r'^</SIGNAL>', line):
4558 logging.info("Found end of signal: %s::%s\nReturns: %s\n%s",
4559 signal_object, signal_name, signal_returns, signal_prototype)
4560 SignalObjects.append(signal_object)
4561 SignalNames.append(signal_name)
4562 SignalReturns.append(signal_returns)
4563 SignalFlags.append(signal_flags)
4564 SignalPrototypes.append(signal_prototype)
4565 in_signal = False
4566 else:
4567 signal_prototype += line
4568 INPUT.close()
4571def ReadObjectHierarchy(ifile):
4572 """Reads the $MODULE-hierarchy file.
4574 This contains all the GObject subclasses described in this module (and their
4575 ancestors).
4576 It places them in the Objects array, and places their level
4577 in the object hierarchy in the ObjectLevels array, at the
4578 same index. GObject, the root object, has a level of 1.
4580 Args:
4581 ifile (str): the input filename.
4582 """
4584 if not os.path.isfile(ifile):
4585 logging.debug('no %s file found', ifile)
4586 return
4588 Objects[:] = []
4589 ObjectLevels[:] = []
4591 INPUT = open(ifile, 'r', encoding='utf-8')
4593 # Only emit objects if they are supposed to be documented, or if
4594 # they have documented children. To implement this, we maintain a
4595 # stack of pending objects which will be emitted if a documented
4596 # child turns up.
4597 pending_objects = []
4598 pending_levels = []
4599 root = None
4600 tree = []
4601 for line in INPUT:
4602 m1 = re.search(r'\S+', line)
4603 if not m1:
4604 continue
4606 gobject = m1.group(0)
4607 level = len(line[:m1.start()]) // 2 + 1
4609 if level == 1:
4610 root = gobject
4612 while pending_levels and pending_levels[-1] >= level:
4613 pending_objects.pop()
4614 pending_levels.pop()
4616 pending_objects.append(gobject)
4617 pending_levels.append(level)
4619 if gobject in KnownSymbols:
4620 while len(pending_levels) > 0:
4621 gobject = pending_objects.pop(0)
4622 level = pending_levels.pop(0)
4623 xref = MakeXRef(gobject)
4625 tree.append(' ' * (level * 4) + xref)
4626 Objects.append(gobject)
4627 ObjectLevels.append(level)
4628 ObjectRoots[gobject] = root
4629 # else
4630 # common.LogWarning(ifile, line_number, "unknown type %s" % object)
4631 #
4633 INPUT.close()
4634 logging.debug('got %d entries for hierarchy', len(tree))
4635 return tree
4638def ReadInterfaces(ifile):
4639 """Reads the $MODULE.interfaces file.
4641 Args:
4642 ifile (str): the input filename.
4643 """
4645 Interfaces.clear()
4647 if not os.path.isfile(ifile):
4648 return
4650 INPUT = open(ifile, 'r', encoding='utf-8')
4652 for line in INPUT:
4653 line = line.strip()
4654 ifaces = line.split()
4655 gobject = ifaces.pop(0)
4656 if gobject in KnownSymbols and KnownSymbols[gobject] == 1:
4657 knownIfaces = []
4659 # filter out private interfaces, but leave foreign interfaces
4660 for iface in ifaces:
4661 if iface not in KnownSymbols or KnownSymbols[iface] == 1:
4662 knownIfaces.append(iface)
4664 Interfaces[gobject] = ' '.join(knownIfaces)
4665 logging.info("Interfaces for %s: %s", gobject, Interfaces[gobject])
4666 else:
4667 logging.info("skipping interfaces for unknown symbol: %s", gobject)
4669 INPUT.close()
4672def ReadPrerequisites(ifile):
4673 """This reads in the $MODULE.prerequisites file.
4675 Args:
4676 ifile (str): the input filename.
4677 """
4678 Prerequisites.clear()
4680 if not os.path.isfile(ifile):
4681 return
4683 INPUT = open(ifile, 'r', encoding='utf-8')
4685 for line in INPUT:
4686 line = line.strip()
4687 prereqs = line.split()
4688 iface = prereqs.pop(0)
4689 if iface in KnownSymbols and KnownSymbols[iface] == 1:
4690 knownPrereqs = []
4692 # filter out private prerequisites, but leave foreign prerequisites
4693 for prereq in prereqs:
4694 if prereq not in KnownSymbols or KnownSymbols[prereq] == 1:
4695 knownPrereqs.append(prereq)
4697 Prerequisites[iface] = ' '.join(knownPrereqs)
4699 INPUT.close()
4702def ReadArgsFile(ifile):
4703 """Reads information about object properties
4705 It creates the arrays ArgObjects, ArgNames, ArgTypes, ArgFlags, ArgNicks and
4706 ArgBlurbs containing info on the args.
4708 Args:
4709 ifile (str): the input filename.
4710 """
4711 in_arg = False
4712 arg_object = None
4713 arg_name = None
4714 arg_type = None
4715 arg_flags = None
4716 arg_nick = None
4717 arg_blurb = None
4718 arg_default = None
4719 arg_range = None
4721 # Reset the args info.
4722 ArgObjects[:] = []
4723 ArgNames[:] = []
4724 ArgTypes[:] = []
4725 ArgFlags[:] = []
4726 ArgNicks[:] = []
4727 ArgBlurbs[:] = []
4728 ArgDefaults[:] = []
4729 ArgRanges[:] = []
4731 if not os.path.isfile(ifile):
4732 return
4734 INPUT = open(ifile, 'r', encoding='utf-8')
4735 line_number = 0
4736 for line in INPUT:
4737 line_number += 1
4738 if not in_arg:
4739 if line.startswith('<ARG>'):
4740 in_arg = True
4741 arg_object = ''
4742 arg_name = ''
4743 arg_type = ''
4744 arg_flags = ''
4745 arg_nick = ''
4746 arg_blurb = ''
4747 arg_default = ''
4748 arg_range = ''
4750 else:
4751 m1 = re.search(r'^<NAME>(.*)</NAME>', line)
4752 m2 = re.search(r'^<TYPE>(.*)</TYPE>', line)
4753 m3 = re.search(r'^<RANGE>(.*)</RANGE>', line)
4754 m4 = re.search(r'^<FLAGS>(.*)</FLAGS>', line)
4755 m5 = re.search(r'^<NICK>(.*)</NICK>', line)
4756 m6 = re.search(r'^<BLURB>(.*)</BLURB>', line)
4757 m7 = re.search(r'^<DEFAULT>(.*)</DEFAULT>', line)
4758 if m1:
4759 arg_name = m1.group(1)
4760 m1_1 = re.search(r'^(.*)::(.*)$', arg_name)
4761 if m1_1:
4762 arg_object = m1_1.group(1)
4763 arg_name = m1_1.group(2).replace('_', '-')
4764 logging.info("Found arg: %s", arg_name)
4765 else:
4766 common.LogWarning(ifile, line_number, "Invalid argument name: " + arg_name)
4768 elif m2:
4769 arg_type = m2.group(1)
4770 elif m3:
4771 arg_range = m3.group(1)
4772 elif m4:
4773 arg_flags = m4.group(1)
4774 elif m5:
4775 arg_nick = m5.group(1)
4776 elif m6:
4777 arg_blurb = m6.group(1)
4778 if arg_blurb == "(null)":
4779 arg_blurb = ''
4780 common.LogWarning(
4781 ifile, line_number, "Property %s:%s has no documentation." % (arg_object, arg_name))
4783 elif m7:
4784 arg_default = m7.group(1)
4785 elif re.search(r'^</ARG>', line):
4786 logging.info("Found end of arg: %s::%s\n%s : %s", arg_object, arg_name, arg_type, arg_flags)
4787 ArgObjects.append(arg_object)
4788 ArgNames.append(arg_name)
4789 ArgTypes.append(arg_type)
4790 ArgRanges.append(arg_range)
4791 ArgFlags.append(arg_flags)
4792 ArgNicks.append(arg_nick)
4793 ArgBlurbs.append(arg_blurb)
4794 ArgDefaults.append(arg_default)
4795 in_arg = False
4797 INPUT.close()
4799def ReadActionsFile(ifile):
4800 """Reads information about object actions
4802 It creates the arrays ActionObjects, ActionNames, ActionParams
4803 and ActionProperties containing info on the actions.
4805 Args:
4806 ifile (str): the input filename.
4807 """
4808 in_action = False
4809 action_object = None
4810 action_name = None
4811 action_param = None
4812 action_prop = None
4814 # Reset the args info.
4815 ActionObjects[:] = []
4816 ActionNames[:] = []
4817 ActionParams[:] = []
4818 ActionProperties[:] = []
4820 if not os.path.isfile(ifile):
4821 return
4823 INPUT = open(ifile, 'r', encoding='utf-8')
4824 line_number = 0
4825 for line in INPUT:
4826 line_number += 1
4827 if not in_action:
4828 if line.startswith('<ACTION>'):
4829 in_action = True
4830 action_object = ''
4831 action_name = ''
4832 action_param = ''
4833 action_prop = ''
4835 else:
4836 m1 = re.search(r'^<NAME>(.*)</NAME>', line)
4837 m2 = re.search(r'^<PARAMETER>(.*)</PARAMETER>', line)
4838 m3 = re.search(r'^<PROPERTY>(.*)</PROPERTY>', line)
4839 if m1:
4840 action_name = m1.group(1)
4841 m1_1 = re.search(r'^(.*):::(.*)$', action_name)
4842 if m1_1:
4843 action_object = m1_1.group(1)
4844 action_name = m1_1.group(2).replace('_', '-')
4845 logging.info("Found action: %s", action_name)
4846 else:
4847 common.LogWarning(ifile, line_number, "Invalid action name: " + action_name)
4849 elif m2:
4850 action_param = m2.group(1)
4851 elif m3:
4852 action_prop = m3.group(1)
4853 elif re.search(r'^</ACTION>', line):
4854 logging.info("Found end of action: %s::%s", action_object, action_name)
4855 ActionObjects.append(action_object)
4856 ActionNames.append(action_name)
4857 ActionParams.append(action_param)
4858 ActionProperties.append(action_prop)
4859 in_action = False
4861 INPUT.close()
4864def AddTreeLineArt(tree):
4865 """Generate a line art tree.
4867 Add unicode lineart to a pre-indented string array and returns
4868 it as as multiline string.
4870 Args:
4871 tree (list): of indented strings.
4873 Returns:
4874 str: multiline string with tree line art
4875 """
4876 # iterate bottom up over the tree
4877 for i in range(len(tree) - 1, -1, -1):
4878 # count leading spaces
4879 m = re.search(r'^([^<A-Za-z]*)', tree[i])
4880 indent = len(m.group(1))
4881 # replace with ╰───, if place of ╰ is not space insert ├
4882 if indent > 4:
4883 if tree[i][indent - 4] == " ":
4884 tree[i] = tree[i][:indent - 4] + "--- " + tree[i][indent:]
4885 else:
4886 tree[i] = tree[i][:indent - 4] + "+-- " + tree[i][indent:]
4888 # go lines up while space and insert |
4889 j = i - 1
4890 while j >= 0 and tree[j][indent - 4] == ' ':
4891 tree[j] = tree[j][:indent - 4] + '|' + tree[j][indent - 3:]
4892 j -= 1
4894 res = "\n".join(tree)
4895 # unicode chars for: ╰──
4896 res = re.sub(r'---', '<phrase role=\"lineart\">╰──</phrase>', res)
4897 # unicde chars for: ├──
4898 res = re.sub(r'\+--', '<phrase role=\"lineart\">├──</phrase>', res)
4899 # unicode char for: │
4900 res = re.sub(r'\|', '<phrase role=\"lineart\">│</phrase>', res)
4902 return res
4905def CheckIsObject(name):
4906 """Check if symbols is an object.
4908 It uses the global Objects array. Note that the Objects array only contains
4909 classes in the current module and their ancestors - not all GObject classes.
4911 Args:
4912 name (str): the object name to check.
4914 Returns:
4915 bool: True if the given name is a GObject or a subclass.
4916 """
4917 root = ObjectRoots.get(name)
4918 # Let GBoxed pass as an object here to get -struct appended to the id
4919 # and prevent conflicts with sections.
4920 return root and root != 'GEnum' and root != 'GFlags'
4923def GetSymbolParams(symbol):
4924 """Get the symbol params and check that they are not empty.
4926 If no parameters are filled in, we don't generate the description table,
4927 for backwards compatibility.
4929 Args:
4930 symbol: the symbol to check the parameters for
4932 Returns:
4933 dict: The parameters
4934 bool: True if empty
4935 """
4936 params = SymbolParams.get(symbol, {})
4937 # TODO: strip at parsing stage?
4938 found = next((True for p in params.values() if p.strip() != ''), False)
4939 return (params, found)
4942def GetSymbolSourceLocation(symbol):
4943 """Get the filename and line where the symbol docs where taken from."""
4944 return SymbolSourceLocation.get(symbol, ('', 0))
4947def new_args_parser():
4948 parser = argparse.ArgumentParser()
4949 parser.add_argument("--version", action="version", version=config.version)
4950 parser.add_argument(
4951 "--module", required=True, help="Name of the doc module being parsed"
4952 )
4953 parser.add_argument("--source-dir", action="append", dest="source_dir", default=[])
4954 parser.add_argument("--source-suffixes", dest="source_suffixes", default="")
4955 parser.add_argument("--ignore-files", dest="ignore_files", default="")
4956 parser.add_argument("--output-dir", dest="output_dir", default="")
4957 parser.add_argument("--tmpl-dir", dest="tmpl_dir", help="DEPRECATED")
4958 parser.add_argument("--main-sgml-file", dest="main_sgml_file", default="")
4959 parser.add_argument(
4960 "--expand-content-files", dest="expand_content_files", default=""
4961 )
4962 group = parser.add_mutually_exclusive_group()
4963 group.add_argument(
4964 "--sgml-mode", action="store_true", default=False, dest="sgml_mode"
4965 )
4966 group.add_argument(
4967 "--xml-mode", action="store_true", default=False, dest="xml_mode"
4968 )
4969 parser.add_argument(
4970 "--default-stability",
4971 dest="default_stability",
4972 choices=["", "Stable", "Private", "Unstable"],
4973 default="",
4974 )
4975 parser.add_argument("--default-includes", dest="default_includes", default="")
4976 parser.add_argument("--output-format", default="xml") # MUST be 'xml', deprecate?
4977 parser.add_argument("--name-space", dest="name_space", default="")
4978 parser.add_argument("--outputallsymbols", default=False, action="store_true")
4979 parser.add_argument(
4980 "--outputsymbolswithoutsince", default=False, action="store_true"
4981 )
4982 return parser