mkdb.py 179 KB
Newer Older
Stefan Sauer's avatar
Stefan Sauer committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- python; coding: utf-8 -*-
#
# gtk-doc - GTK DocBook documentation generator.
# Copyright (C) 1998  Damon Chaplin
#               2007-2016  Stefan Sauer
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#

22
23
24
"""
Creates the DocBook files from the source comments.
"""
Stefan Sauer's avatar
Stefan Sauer committed
25
26
27
28
29
30
31

from collections import OrderedDict
import logging
import os
import re
import string

32
from . import common, md_to_db
Stefan Sauer's avatar
Stefan Sauer committed
33
34
35
36
37
38

# Options
MODULE = None
DB_OUTPUT_DIR = None
INLINE_MARKUP_MODE = None
NAME_SPACE = ''
39
ROOT_DIR = '.'
Stefan Sauer's avatar
Stefan Sauer committed
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

# These global arrays store information on signals. Each signal has an entry
# in each of these arrays at the same index, like a multi-dimensional array.
SignalObjects = []        # The GtkObject which emits the signal.
SignalNames = []        # The signal name.
SignalReturns = []        # The return type.
SignalFlags = []        # Flags for the signal
SignalPrototypes = []        # The rest of the prototype of the signal handler.

# These global arrays store information on Args. Each Arg has an entry
# in each of these arrays at the same index, like a multi-dimensional array.
ArgObjects = []                # The GtkObject which has the Arg.
ArgNames = []                # The Arg name.
ArgTypes = []                # The Arg type - gint, GtkArrowType etc.
ArgFlags = []                # How the Arg can be used - readable/writable etc.
ArgNicks = []                # The nickname of the Arg.
ArgBlurbs = []          # Docstring of the Arg.
ArgDefaults = []        # Default value of the Arg.
ArgRanges = []                # The range of the Arg type

60
61
62
63
64
ActionObjects = []
ActionNames = []
ActionParams = []
ActionProperties = []

Stefan Sauer's avatar
Stefan Sauer committed
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# These global hashes store declaration info keyed on a symbol name.
Declarations = {}
DeclarationTypes = {}
DeclarationConditional = {}
DeclarationOutput = {}
Deprecated = {}
Since = {}
StabilityLevel = {}
StructHasTypedef = {}

# These global hashes store the existing documentation.
SymbolDocs = {}
SymbolParams = {}
SymbolAnnotations = {}

# These global hashes store documentation scanned from the source files.
SourceSymbolDocs = {}
SourceSymbolParams = {}
83
SymbolSourceLocation = {}
Stefan Sauer's avatar
Stefan Sauer committed
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104

# all documentation goes in here, so we can do coverage analysis
AllSymbols = {}
AllIncompleteSymbols = {}
AllUnusedSymbols = {}
AllDocumentedSymbols = {}

# Undeclared yet documented symbols
UndeclaredSymbols = {}

# These global arrays store GObject, subclasses and the hierarchy (also of
# non-object derived types).
Objects = []
ObjectLevels = []
ObjectRoots = {}

Interfaces = {}
Prerequisites = {}

# holds the symbols which are mentioned in <MODULE>-sections.txt and in which
# section they are defined
Stefan Sauer's avatar
Stefan Sauer committed
105
KnownSymbols = {}  # values are 1 for public symbols and 0 otherwise
Stefan Sauer's avatar
Stefan Sauer committed
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
SymbolSection = {}
SymbolSectionId = {}

# collects index entries
IndexEntriesFull = {}
IndexEntriesSince = {}
IndexEntriesDeprecated = {}

# Standard C preprocessor directives, which we ignore for '#' abbreviations.
PreProcessorDirectives = {
    'assert', 'define', 'elif', 'else', 'endif', 'error', 'if', 'ifdef', 'ifndef',
    'include', 'line', 'pragma', 'unassert', 'undef', 'warning'
}

# remember used annotation (to write minimal glossary)
AnnotationsUsed = {}

# the regexp that parses the annotation is in ScanSourceFile()
AnnotationDefinition = {
    # the GObjectIntrospection annotations are defined at:
    # https://live.gnome.org/GObjectIntrospection/Annotations
    'allow-none': "NULL is OK, both for passing and for returning.",
    'nullable': "NULL may be passed as the value in, out, in-out; or as a return value.",
    'not nullable': "NULL must not be passed as the value in, out, in-out; or as a return value.",
    'optional': "NULL may be passed instead of a pointer to a location.",
131
    'not optional': "NULL must not be passed as the pointer to a location.",
Stefan Sauer's avatar
Stefan Sauer committed
132
133
134
135
136
137
    'array': "Parameter points to an array of items.",
    'attribute': "Deprecated free-form custom annotation, replaced by (attributes) annotation.",
    'attributes': "Free-form key-value pairs.",
    'closure': "This parameter is a 'user_data', for callbacks; many bindings can pass NULL here.",
    'constructor': "This symbol is a constructor, not a static method.",
    'destroy': "This parameter is a 'destroy_data', for callbacks.",
138
    'default': "Default parameter value (in case a function which shadows this one via <acronym>rename-to</acronym> has fewer parameters).",
Stefan Sauer's avatar
Stefan Sauer committed
139
140
141
142
143
144
145
146
147
148
149
    'element-type': "Generics and defining elements of containers and arrays.",
    'error-domains': "Typed errors. Similar to throws in Java.",
    'foreign': "This is a foreign struct.",
    'get-value-func': "The specified function is used to convert a struct from a GValue, must be a GTypeInstance.",
    'in': "Parameter for input. Default is <acronym>transfer none</acronym>.",
    'inout': "Parameter for input and for returning results. Default is <acronym>transfer full</acronym>.",
    'in-out': "Parameter for input and for returning results. Default is <acronym>transfer full</acronym>.",
    'method': "This is a method",
    'not-error': "A GError parameter is not to be handled like a normal GError.",
    'out': "Parameter for returning results. Default is <acronym>transfer full</acronym>.",
    'out caller-allocates': "Out parameter, where caller must allocate storage.",
150
    'out callee-allocates': "Out parameter, where callee must allocate storage.",
Stefan Sauer's avatar
Stefan Sauer committed
151
152
153
154
155
156
157
    'ref-func': "The specified function is used to ref a struct, must be a GTypeInstance.",
    'rename-to': "Rename the original symbol's name to SYMBOL.",
    'scope call': "The callback is valid only during the call to the method.",
    'scope async': "The callback is valid until first called.",
    'scope notified': "The callback is valid until the GDestroyNotify argument is called.",
    'set-value-func': "The specified function is used to convert from a struct to a GValue, must be a GTypeInstance.",
    'skip': "Exposed in C code, not necessarily available in other languages.",
158
    'transfer container': "The caller owns the data container, but not the data inside it.",
Stefan Sauer's avatar
Stefan Sauer committed
159
    'transfer floating': "Alias for <acronym>transfer none</acronym>, used for objects with floating refs.",
160
161
    'transfer full': "The caller owns the data, and is responsible for free it.",
    'transfer none': "The data is owned by the callee, which is responsible of freeing it.",
Stefan Sauer's avatar
Stefan Sauer committed
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
    'type': "Override the parsed C type with given type.",
    'unref-func': "The specified function is used to unref a struct, must be a GTypeInstance.",
    'virtual': "This is the invoker for a virtual method.",
    'value': "The specified value overrides the evaluated value of the constant.",
    # Stability Level definition
    # https://bugzilla.gnome.org/show_bug.cgi?id=170860
    'Stable': '''The intention of a Stable interface is to enable arbitrary third parties to
develop applications to these interfaces, release them, and have confidence that
they will run on all minor releases of the product (after the one in which the
interface was introduced, and within the same major release). Even at a major
release, incompatible changes are expected to be rare, and to have strong
justifications.
''',
    'Unstable': '''Unstable interfaces are experimental or transitional. They are typically used to
give outside developers early access to new or rapidly changing technology, or
to provide an interim solution to a problem where a more general solution is
anticipated. No claims are made about either source or binary compatibility from
one minor release to the next.

The Unstable interface level is a warning that these interfaces are  subject to
change without warning and should not be used in unbundled products.

Given such caveats, customer impact need not be a factor when considering
incompatible changes to an Unstable interface in a major or minor release.
Nonetheless, when such changes are introduced, the changes should still be
mentioned in the release notes for the affected release.
''',
    'Private': '''An interface that can be used within the GNOME stack itself, but that is not
documented for end-users.  Such functions should only be used in specified and
documented ways.
''',
}

# Function and other declaration output settings.
RETURN_TYPE_FIELD_WIDTH = 20
MAX_SYMBOL_FIELD_WIDTH = 40

# XML header
doctype_header = None

202
203
# docbook templates
DB_REFENTRY = string.Template('''${header}
Stefan Sauer's avatar
Stefan Sauer committed
204
205
206
207
208
209
210
211
212
213
214
<refentry id="${section_id}">
<refmeta>
<refentrytitle role="top_of_page" id="${section_id}.top_of_page">${title}</refentrytitle>
<manvolnum>3</manvolnum>
<refmiscinfo>${MODULE} Library${image}</refmiscinfo>
</refmeta>
<refnamediv>
<refname>${title}</refname>
<refpurpose>${short_desc}</refpurpose>
</refnamediv>
${stability}
215
${functions_synop}${args_synop}${signals_synop}${actions_synop}${object_anchors}${other_synop}${hierarchy}${prerequisites}${derived}${interfaces}${implementations}
Stefan Sauer's avatar
Stefan Sauer committed
216
217
218
219
220
221
222
223
224
${include_output}
<refsect1 id="${section_id}.description" role="desc">
<title role="desc.title">Description</title>
${extralinks}${long_desc}
</refsect1>
<refsect1 id="${section_id}.functions_details" role="details">
<title role="details.title">Functions</title>
${functions_details}
</refsect1>
225
${other_desc}${args_desc}${signals_desc}${actions_desc}${see_also}
Stefan Sauer's avatar
Stefan Sauer committed
226
227
228
</refentry>
''')

229
DB_REFSECT1_SYNOPSIS3 = string.Template('''<refsect1 id="${section_id}.${type}" role="${role}">
230
231
232
233
234
235
236
237
238
239
240
241
242
243
<title role="${role}.title">${title}</title>
<informaltable frame="none">
<tgroup cols="3">
<colspec colname="${role}_type" colwidth="150px"/>
<colspec colname="${role}_name" colwidth="300px"/>
<colspec colname="${role}_flags" colwidth="200px"/>
<tbody>
${content}
</tbody>
</tgroup>
</informaltable>
</refsect1>
''')

244
245
246
247
248
249
250
251
252
253
254
255
256
257
DB_REFSECT1_SYNOPSIS2 = string.Template('''<refsect1 id="${section_id}.${type}" role="${role}">
<title role="${role}.title">${title}</title>
<informaltable pgwide="1" frame="none">
<tgroup cols="2">
<colspec colname="${role}_type" colwidth="150px"/>
<colspec colname="${role}_name"/>
<tbody>
${content}
</tbody>
</tgroup>
</informaltable>
</refsect1>
''')

258
259
260
261
262
263
DB_REFSECT1_DESC = string.Template('''<refsect1 id="${section_id}.${type}" role="${role}">
<title role="${role}.title">${title}</title>
${content}
</refsect1>
''')

Stefan Sauer's avatar
Stefan Sauer committed
264
265

def Run(options):
266
    global MODULE, INLINE_MARKUP_MODE, NAME_SPACE, DB_OUTPUT_DIR, doctype_header
Stefan Sauer's avatar
Stefan Sauer committed
267

Stefan Sauer's avatar
Stefan Sauer committed
268
269
    logging.info('options: %s', str(options.__dict__))

Stefan Sauer's avatar
Stefan Sauer committed
270
271
272
273
274
    # We should pass the options variable around instead of this global variable horror
    # but too much of the code expects these to be around. Fix this once the transition is done.
    MODULE = options.module
    INLINE_MARKUP_MODE = options.xml_mode or options.sgml_mode
    NAME_SPACE = options.name_space
275
    DB_OUTPUT_DIR = options.output_dir or os.path.join(ROOT_DIR, "xml")
Stefan Sauer's avatar
Stefan Sauer committed
276

277
278
    main_sgml_file = options.main_sgml_file
    if not main_sgml_file:
Stefan Sauer's avatar
Stefan Sauer committed
279
280
        # backwards compatibility
        if os.path.exists(MODULE + "-docs.sgml"):
281
            main_sgml_file = MODULE + "-docs.sgml"
Stefan Sauer's avatar
Stefan Sauer committed
282
        else:
283
            main_sgml_file = MODULE + "-docs.xml"
Stefan Sauer's avatar
Stefan Sauer committed
284

Stefan Sauer's avatar
Stefan Sauer committed
285
286
    # -- phase 1: read files produced by previous tools and scane sources

Stefan Sauer's avatar
Stefan Sauer committed
287
    # extract docbook header or define default
288
    doctype_header = GetDocbookHeader(main_sgml_file)
Stefan Sauer's avatar
Stefan Sauer committed
289
290

    ReadKnownSymbols(os.path.join(ROOT_DIR, MODULE + "-sections.txt"))
291
292
    ReadSignalsFile(os.path.join(ROOT_DIR, MODULE + ".signals"))
    ReadArgsFile(os.path.join(ROOT_DIR, MODULE + ".args"))
293
    ReadActionsFile(os.path.join(ROOT_DIR, MODULE + ".actions"))
Stefan Sauer's avatar
Stefan Sauer committed
294
    obj_tree = ReadObjectHierarchy(os.path.join(ROOT_DIR, MODULE + ".hierarchy"))
295
296
    ReadInterfaces(os.path.join(ROOT_DIR, MODULE + ".interfaces"))
    ReadPrerequisites(os.path.join(ROOT_DIR, MODULE + ".prerequisites"))
Stefan Sauer's avatar
Stefan Sauer committed
297
298
299
300
301

    ReadDeclarationsFile(os.path.join(ROOT_DIR, MODULE + "-decl.txt"), 0)
    if os.path.isfile(os.path.join(ROOT_DIR, MODULE + "-overrides.txt")):
        ReadDeclarationsFile(os.path.join(ROOT_DIR, MODULE + "-overrides.txt"), 1)

Stefan Sauer's avatar
Stefan Sauer committed
302
303
304
305
306
307
308
309
    logging.info("Data files read")

    # -- phase 2: scan sources

    # TODO: move this to phase 3 once we fixed the call to OutputProgramDBFile()
    if not os.path.isdir(DB_OUTPUT_DIR):
        os.mkdir(DB_OUTPUT_DIR)

310
311
312
313
314
315
316
317
318
319
320
    # Scan sources
    if options.source_suffixes:
        suffix_list = ['.' + ext for ext in options.source_suffixes.split(',')]
    else:
        suffix_list = ['.c', '.h']

    source_dirs = options.source_dir
    ignore_files = options.ignore_files
    logging.info(" ignore files: " + ignore_files)
    for sdir in source_dirs:
        ReadSourceDocumentation(sdir, suffix_list, source_dirs, ignore_files)
Stefan Sauer's avatar
Stefan Sauer committed
321

Stefan Sauer's avatar
Stefan Sauer committed
322
    logging.info("Sources scanned")
323

Stefan Sauer's avatar
Stefan Sauer committed
324
325
326
327
    # -- phase 3: write docbook files

    changed, book_top, book_bottom = OutputDB(os.path.join(ROOT_DIR, MODULE + "-sections.txt"), options)
    OutputBook(main_sgml_file, book_top, book_bottom, obj_tree)
Stefan Sauer's avatar
Stefan Sauer committed
328
329
330
331

    # If any of the DocBook files have changed, update the timestamp file (so
    # it can be used for Makefile dependencies).
    if changed or not os.path.exists(os.path.join(ROOT_DIR, "sgml.stamp")):
332
333
334
335
336
337
338
339
340
        # TODO: MakeIndexterms() uses NAME_SPACE, but also fills IndexEntriesFull
        # which DetermineNamespace is using
        # Can we use something else?
        # no: AllSymbols, KnownSymbols
        # IndexEntriesFull: consist of all symbols from sections file + signals and properties
        #
        # logging.info('# index_entries_full=%d, # declarations=%d',
        #     len(IndexEntriesFull), len(Declarations))
        # logging.info('known_symbols - index_entries_full: ' + str(Declarations.keys() - IndexEntriesFull.keys()))
Stefan Sauer's avatar
Stefan Sauer committed
341
342
343
344

        # try to detect the common prefix
        # GtkWidget, GTK_WIDGET, gtk_widget -> gtk
        if NAME_SPACE == '':
345
            NAME_SPACE = DetermineNamespace(IndexEntriesFull.keys())
Stefan Sauer's avatar
Stefan Sauer committed
346
347
348

        logging.info('namespace prefix ="%s"', NAME_SPACE)

Stefan Sauer's avatar
Stefan Sauer committed
349
        OutputObjectTree(obj_tree)
350
        OutputObjectList(Objects)
Stefan Sauer's avatar
Stefan Sauer committed
351

352
353
        OutputIndex("api-index-full", IndexEntriesFull)
        OutputIndex("api-index-deprecated", IndexEntriesDeprecated)
Stefan Sauer's avatar
Stefan Sauer committed
354
355
356
        OutputSinceIndexes()
        OutputAnnotationGlossary()

357
358
        with open(os.path.join(ROOT_DIR, 'sgml.stamp'), 'w') as h:
            h.write('timestamp')
Stefan Sauer's avatar
Stefan Sauer committed
359

Stefan Sauer's avatar
Stefan Sauer committed
360
361
    logging.info("All files created: %d", changed)

Stefan Sauer's avatar
Stefan Sauer committed
362

363
def OutputObjectList(obj_list):
364
365
366
    """This outputs the alphabetical list of objects, in a columned table."""
    # FIXME: Currently this also outputs ancestor objects which may not actually
    # be in this module.
367
368
369
370
371

    # TODO(ensonic): consider not writing this unconditionally
    if not obj_list:
        return

Stefan Sauer's avatar
Stefan Sauer committed
372
373
374
375
376
377
    cols = 3

    # FIXME: use .xml
    old_object_index = os.path.join(DB_OUTPUT_DIR, "object_index.sgml")
    new_object_index = os.path.join(DB_OUTPUT_DIR, "object_index.new")

Stefan Sauer's avatar
Stefan Sauer committed
378
    OUTPUT = open(new_object_index, 'w', encoding='utf-8')
Stefan Sauer's avatar
Stefan Sauer committed
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399

    OUTPUT.write('''%s
<informaltable pgwide="1" frame="none">
<tgroup cols="%s">
<colspec colwidth="1*"/>
<colspec colwidth="1*"/>
<colspec colwidth="1*"/>
<tbody>
''' % (MakeDocHeader("informaltable"), cols))

    count = 0
    object = None
    for object in sorted(Objects):
        xref = MakeXRef(object)
        if count % cols == 0:
            OUTPUT.write("<row>\n")
        OUTPUT.write("<entry>%s</entry>\n" % xref)
        if count % cols == cols - 1:
            OUTPUT.write("</row>\n")
        count += 1

400
401
    if count % cols > 0:
        OUTPUT.write("</row>\n")
Stefan Sauer's avatar
Stefan Sauer committed
402
403
404
405
406
407
408

    OUTPUT.write('''</tbody></tgroup></informaltable>\n''')
    OUTPUT.close()

    common.UpdateFileIfChanged(old_object_index, new_object_index, 0)


409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
def trim_leading_and_trailing_nl(text):
    """Trims extra newlines.

    Leaves a single trailing newline.

    Args:
        text (str): the text block to trim. May contain newlines.

    Returns:
       (str): trimmed text
    """
    text = re.sub(r'^\n*', '', text)
    return re.sub(r'\n+$', '\n', text)


def trim_white_spaces(text):
425
    """Trims extra whitespace.
Stefan Sauer's avatar
Stefan Sauer committed
426

427
428
429
    Empty lines inside a block are preserved. All whitespace and the end is
    replaced with a single newline.

430
    Args:
431
432
433
434
        text (str): the text block to trim. May contain newlines.

    Returns:
       (str): trimmed text
435
    """
Stefan Sauer's avatar
Stefan Sauer committed
436

437
    # strip trailing spaces on every line
438
    return re.sub(r'\s+$', '\n', text.lstrip(), flags=re.MULTILINE)
Stefan Sauer's avatar
Stefan Sauer committed
439
440


441
def make_refsect1_synopsis(tmpl, content, title, section_id, section_type, role=None):
442
443
444
445
446
    # TODO(ensonic): canonicalize xml to use the same string for section_type
    # and role. Needs fixes on gtk-doc.xsl
    if role is None:
        role = section_type.replace('-', '_')

447
    return tmpl.substitute({
448
449
450
451
452
453
454
455
        'content': content,
        'role': role,
        'section_id': section_id,
        'title': title,
        'type': section_type,
    })


456
457
458
459
460
461
462
463
def make_refsect1_synopsis2(content, title, section_id, section_type, role=None):
    return make_refsect1_synopsis(DB_REFSECT1_SYNOPSIS2, content, title, section_id, section_type, role)


def make_refsect1_synopsis3(content, title, section_id, section_type, role=None):
    return make_refsect1_synopsis(DB_REFSECT1_SYNOPSIS3, content, title, section_id, section_type, role)


464
def make_refsect1_desc(content, title, section_id, section_type, role=None):
465
    content = trim_white_spaces(content)
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
    if content == '':
        return ''

    # TODO(ensonic): canonicalize xml to use the same string for section_type
    # and role. Needs fixes on gtk-doc.xsl
    if role is None:
        role = section_type.replace('-', '_')

    return DB_REFSECT1_DESC.substitute({
        'content': content,
        'role': role,
        'section_id': section_id,
        'title': title,
        'type': section_type,
    })


Stefan Sauer's avatar
Stefan Sauer committed
483
def OutputDB(file, options):
484
485
486
487
488
489
490
491
492
    """Generate docbook files.

    This collects the output for each section of the docs, and outputs each file
    when the end of the section is found.

    Args:
        file (str): the $MODULE-sections.txt file which contains all of the
                    functions/macros/structs etc. being documented, organised
                    into sections and subsections.
Stefan Sauer's avatar
Stefan Sauer committed
493
        options:    commandline options
494
    """
Stefan Sauer's avatar
Stefan Sauer committed
495
496

    logging.info("Reading: %s", file)
Stefan Sauer's avatar
Stefan Sauer committed
497
    INPUT = open(file, 'r', encoding='utf-8')
Stefan Sauer's avatar
Stefan Sauer committed
498
499
500
    filename = ''
    book_top = ''
    book_bottom = ''
501
    includes = options.default_includes or ''
Stefan Sauer's avatar
Stefan Sauer committed
502
503
504
505
506
507
508
509
510
511
512
    section_includes = ''
    in_section = 0
    title = ''
    section_id = ''
    subsection = ''
    num_symbols = 0
    changed = 0
    functions_synop = ''
    other_synop = ''
    functions_details = ''
    other_details = ''
513
    other_desc = ''
Stefan Sauer's avatar
Stefan Sauer committed
514
515
516
517
518
519
520
521
    signals_synop = ''
    signals_desc = ''
    args_synop = ''
    child_args_synop = ''
    style_args_synop = ''
    args_desc = ''
    child_args_desc = ''
    style_args_desc = ''
522
523
    actions_synop = ''
    actions_desc = ''
Stefan Sauer's avatar
Stefan Sauer committed
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
    hierarchy_str = ''
    hierarchy = []
    interfaces = ''
    implementations = ''
    prerequisites = ''
    derived = ''
    file_objects = []
    file_def_line = {}
    symbol_def_line = {}

    MergeSourceDocumentation()

    line_number = 0
    for line in INPUT:
        line_number += 1

        if line.startswith('#'):
            continue

        logging.info("section file data: %d: %s", line_number, line)

        m1 = re.search(r'^<SUBSECTION\s*(.*)>', line, re.I)
        m2 = re.search(r'^<TITLE>(.*)<\/TITLE', line)
        m3 = re.search(r'^<FILE>(.*)<\/FILE>', line)
        m4 = re.search(r'^<INCLUDE>(.*)<\/INCLUDE>', line)
        m5 = re.search(r'^(\S+)', line)

        if line.startswith('<SECTION>'):
            num_symbols = 0
            in_section = False
            file_objects = []
            symbol_def_line = {}

        elif m1:
            other_synop += "\n"
            functions_synop += "\n"
            subsection = m1.group(1)

        elif line.startswith('<SUBSECTION>'):
            continue
        elif m2:
            title = m2.group(1)
            logging.info("Section: %s", title)

            # We don't want warnings if object & class structs aren't used.
            DeclarationOutput[title] = 1
            DeclarationOutput["%sClass" % title] = 1
            DeclarationOutput["%sIface" % title] = 1
            DeclarationOutput["%sInterface" % title] = 1

        elif m3:
            filename = m3.group(1)
Stefan Sauer's avatar
Stefan Sauer committed
576
            if filename not in file_def_line:
Stefan Sauer's avatar
Stefan Sauer committed
577
578
579
580
581
                file_def_line[filename] = line_number
            else:
                common.LogWarning(file, line_number, "Double <FILE>%s</FILE> entry. Previous occurrence on line %s." %
                                  (filename, file_def_line[filename]))
            if title == '':
582
                key = filename + ":title"
Stefan Sauer's avatar
Stefan Sauer committed
583
584
585
586
587
588
589
                if key in SourceSymbolDocs:
                    title = SourceSymbolDocs[key].rstrip()

        elif m4:
            if in_section:
                section_includes = m4.group(1)
            else:
590
                if options.default_includes:
Stefan Sauer's avatar
Stefan Sauer committed
591
592
593
594
595
596
                    common.LogWarning(file, line_number, "Default <INCLUDE> being overridden by command line option.")
                else:
                    includes = m4.group(1)

        elif re.search(r'^<\/SECTION>', line):
            logging.info("End of section: %s", title)
597
            # TODO: also output if we have sections docs?
598
            # long_desc = SymbolDocs.get(filename + ":long_description")
Stefan Sauer's avatar
Stefan Sauer committed
599
600
601
602
            if num_symbols > 0:
                # collect documents
                book_bottom += "    <xi:include href=\"xml/%s.xml\"/>\n" % filename

603
                key = filename + ":include"
Stefan Sauer's avatar
Stefan Sauer committed
604
605
606
607
608
609
610
611
                if key in SourceSymbolDocs:
                    if section_includes:
                        common.LogWarning(file, line_number, "Section <INCLUDE> being overridden by inline comments.")
                    section_includes = SourceSymbolDocs[key]

                if section_includes == '':
                    section_includes = includes

612
                signals_synop = trim_leading_and_trailing_nl(signals_synop)
Stefan Sauer's avatar
Stefan Sauer committed
613
                if signals_synop != '':
614
                    signals_synop = make_refsect1_synopsis3(
615
616
617
                        signals_synop, 'Signals', section_id, 'signals', 'signal_proto')
                    signals_desc = make_refsect1_desc(signals_desc, 'Signal Details',
                                                      section_id, 'signal-details', 'signals')
Stefan Sauer's avatar
Stefan Sauer committed
618

619
                args_synop = trim_leading_and_trailing_nl(args_synop)
Stefan Sauer's avatar
Stefan Sauer committed
620
                if args_synop != '':
621
                    args_synop = make_refsect1_synopsis3(args_synop, 'Properties', section_id, 'properties')
622
                    args_desc = make_refsect1_desc(args_desc, 'Property Details', section_id, 'property-details')
Stefan Sauer's avatar
Stefan Sauer committed
623

624
                child_args_synop = trim_leading_and_trailing_nl(child_args_synop)
Stefan Sauer's avatar
Stefan Sauer committed
625
                if child_args_synop != '':
626
627
                    args_synop += make_refsect1_synopsis3(child_args_synop,
                                                          'Child Properties', section_id, 'child-properties')
628
629
                    args_desc += make_refsect1_desc(child_args_desc, 'Child Property Details',
                                                    section_id, 'child-property-details')
Stefan Sauer's avatar
Stefan Sauer committed
630

631
                style_args_synop = trim_leading_and_trailing_nl(style_args_synop)
Stefan Sauer's avatar
Stefan Sauer committed
632
                if style_args_synop != '':
633
634
                    args_synop += make_refsect1_synopsis3(style_args_synop,
                                                          'Style Properties', section_id, 'style-properties')
635
636
                    args_desc += make_refsect1_desc(style_args_desc, 'Style Property Details',
                                                    section_id, 'style-property-details')
Stefan Sauer's avatar
Stefan Sauer committed
637

638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
                actions_synop = re.sub(r'^\n*', '', actions_synop)
                actions_synop = re.sub(r'\n+$', '\n', actions_synop)
                if actions_synop != '':
                    actions_synop = '''<refsect1 id="%s.actions" role="actions">
<title role="actions.title">Actions</title>
<informaltable frame="none">
<tgroup cols="3">
<colspec colname="actions_none" colwidth="150px"/>
<colspec colname="actions_name" colwidth="300px"/>
<colspec colname="actions_param" colwidth="200px"/>
<tbody>
%s
</tbody>
</tgroup>
</informaltable>
</refsect1>
''' % (section_id, actions_synop)
                    actions_desc = trim_leading_and_trailing_nl(actions_desc)
                    actions_desc = '''<refsect1 id="%s.action-details" role="action_details">
<title role="action_details.title">Action Details</title>
%s
</refsect1>
''' % (section_id, actions_desc)

Stefan Sauer's avatar
Stefan Sauer committed
662
663
                hierarchy_str = AddTreeLineArt(hierarchy)
                if hierarchy_str != '':
664
665
                    hierarchy_str = make_refsect1_desc('<screen>' + hierarchy_str + '\n</screen>',
                                                       'Object Hierarchy', section_id, 'object-hierarchy')
Stefan Sauer's avatar
Stefan Sauer committed
666

667
668
669
670
671
672
                interfaces = make_refsect1_desc(interfaces, 'Implemented Interfaces', section_id,
                                                'implemented-interfaces', 'impl_interfaces')
                implementations = make_refsect1_desc(
                    implementations, 'Known Implementations', section_id, 'implementations')
                prerequisites = make_refsect1_desc(prerequisites, 'Prerequisites', section_id, 'prerequisites')
                derived = make_refsect1_desc(derived, 'Known Derived Interfaces', section_id, 'derived-interfaces')
Stefan Sauer's avatar
Stefan Sauer committed
673

674
                functions_synop = trim_leading_and_trailing_nl(functions_synop)
Stefan Sauer's avatar
Stefan Sauer committed
675
                if functions_synop != '':
676
677
                    functions_synop = make_refsect1_synopsis2(
                        functions_synop, 'Functions', section_id, 'functions', 'functions_proto')
Stefan Sauer's avatar
Stefan Sauer committed
678

679
                other_synop = trim_leading_and_trailing_nl(other_synop)
Stefan Sauer's avatar
Stefan Sauer committed
680
                if other_synop != '':
681
682
                    other_synop = make_refsect1_synopsis2(
                        other_synop, 'Types and Values', section_id, 'other', 'other_proto')
683
684
                    other_desc += make_refsect1_desc(other_details, 'Types and Values',
                                                     section_id, 'other_details', 'details')
Stefan Sauer's avatar
Stefan Sauer committed
685
686
687
688

                file_changed = OutputDBFile(filename, title, section_id,
                                            section_includes,
                                            functions_synop, other_synop,
689
                                            functions_details, other_desc,
Stefan Sauer's avatar
Stefan Sauer committed
690
691
                                            signals_synop, signals_desc,
                                            args_synop, args_desc,
692
                                            actions_synop, actions_desc,
Stefan Sauer's avatar
Stefan Sauer committed
693
694
695
                                            hierarchy_str, interfaces,
                                            implementations,
                                            prerequisites, derived,
696
697
                                            file_objects,
                                            options.default_stability)
Stefan Sauer's avatar
Stefan Sauer committed
698
699
700
701
702
703
704
705
706
707
708
709
                if file_changed:
                    changed = True

            title = ''
            section_id = ''
            subsection = ''
            in_section = 0
            section_includes = ''
            functions_synop = ''
            other_synop = ''
            functions_details = ''
            other_details = ''
710
            other_desc = ''
Stefan Sauer's avatar
Stefan Sauer committed
711
712
713
714
715
716
717
718
            signals_synop = ''
            signals_desc = ''
            args_synop = ''
            child_args_synop = ''
            style_args_synop = ''
            args_desc = ''
            child_args_desc = ''
            style_args_desc = ''
719
720
            actions_synop = ''
            actions_desc = ''
Stefan Sauer's avatar
Stefan Sauer committed
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
            hierarchy_str = ''
            hierarchy = []
            interfaces = ''
            implementations = ''
            prerequisites = ''
            derived = ''

        elif m5:
            symbol = m5.group(1)
            logging.info('  Symbol: "%s" in subsection: "%s"', symbol, subsection)

            # check for duplicate entries
            if symbol not in symbol_def_line:
                declaration = Declarations.get(symbol)
                # FIXME: with this we'll output empty declaration
                if declaration is not None:
                    if CheckIsObject(symbol):
                        file_objects.append(symbol)

                    # We don't want standard macros/functions of GObjects,
                    # or private declarations.
                    if subsection != "Standard" and subsection != "Private":
                        synop, desc = OutputDeclaration(symbol, declaration)
                        type = DeclarationTypes[symbol]

                        if type == 'FUNCTION' or type == 'USER_FUNCTION':
                            functions_synop += synop
                            functions_details += desc
                        elif type == 'MACRO' and re.search(symbol + r'\(', declaration):
                            functions_synop += synop
                            functions_details += desc
                        else:
                            other_synop += synop
                            other_details += desc

                    sig_synop, sig_desc = GetSignals(symbol)
                    arg_synop, child_arg_synop, style_arg_synop, arg_desc, child_arg_desc, style_arg_desc = GetArgs(
                        symbol)
759
                    action_synop, action_desc = GetActions(symbol)
Stefan Sauer's avatar
Stefan Sauer committed
760
761
762
763
764
765
766
767
768
769
770
771
772
773
                    ifaces = GetInterfaces(symbol)
                    impls = GetImplementations(symbol)
                    prereqs = GetPrerequisites(symbol)
                    der = GetDerived(symbol)
                    hierarchy = GetHierarchy(symbol, hierarchy)

                    signals_synop += sig_synop
                    signals_desc += sig_desc
                    args_synop += arg_synop
                    child_args_synop += child_arg_synop
                    style_args_synop += style_arg_synop
                    args_desc += arg_desc
                    child_args_desc += child_arg_desc
                    style_args_desc += style_arg_desc
774
775
                    actions_synop += action_synop
                    actions_desc += action_desc
Stefan Sauer's avatar
Stefan Sauer committed
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
                    interfaces += ifaces
                    implementations += impls
                    prerequisites += prereqs
                    derived += der

                    # Note that the declaration has been output.
                    DeclarationOutput[symbol] = True
                elif subsection != "Standard" and subsection != "Private":
                    UndeclaredSymbols[symbol] = True
                    common.LogWarning(file, line_number, "No declaration found for %s." % symbol)

                num_symbols += 1
                symbol_def_line[symbol] = line_number

                if section_id == '':
                    if title == '' and filename == '':
                        common.LogWarning(file, line_number, "Section has no title and no file.")

                    # FIXME: one of those would be enough
                    # filename should be an internal detail for gtk-doc
                    if title == '':
                        title = filename
                    elif filename == '':
                        filename = title

                    filename = filename.replace(' ', '_')

803
                    section_id = SourceSymbolDocs.get(filename + ":section_id")
Stefan Sauer's avatar
Stefan Sauer committed
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
                    if section_id and section_id.strip() != '':
                        # Remove trailing blanks and use as is
                        section_id = section_id.rstrip()
                    elif CheckIsObject(title):
                        # GObjects use their class name as the ID.
                        section_id = common.CreateValidSGMLID(title)
                    else:
                        section_id = common.CreateValidSGMLID(MODULE + '-' + title)

                SymbolSection[symbol] = title
                SymbolSectionId[symbol] = section_id

            else:
                common.LogWarning(file, line_number, "Double symbol entry for %s. "
                                  "Previous occurrence on line %d." % (symbol, symbol_def_line[symbol]))
    INPUT.close()

    OutputMissingDocumentation()
    OutputUndeclaredSymbols()
    OutputUnusedSymbols()

Stefan Sauer's avatar
Stefan Sauer committed
825
    if options.outputallsymbols:
Stefan Sauer's avatar
Stefan Sauer committed
826
827
        OutputAllSymbols()

Stefan Sauer's avatar
Stefan Sauer committed
828
    if options.outputsymbolswithoutsince:
Stefan Sauer's avatar
Stefan Sauer committed
829
830
        OutputSymbolsWithoutSince()

Stefan Sauer's avatar
Stefan Sauer committed
831
    for filename in options.expand_content_files.split():
Stefan Sauer's avatar
Stefan Sauer committed
832
833
834
835
        file_changed = OutputExtraFile(filename)
        if file_changed:
            changed = True

836
    return (changed, book_top, book_bottom)
Stefan Sauer's avatar
Stefan Sauer committed
837
838


839
840
841
842
843
844
845
846
847
def DetermineNamespace(symbols):
    """Find common set of characters.

    Args:
         symbols (list): a list of symbols to scan for a common prefix

    Returns:
        str: a common namespace prefix (might be empty)
    """
848
849
850
851
852
853
    name_space = ''
    pos = 0
    ratio = 0.0
    while True:
        prefix = {}
        letter = ''
854
        for symbol in symbols:
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
            if name_space == '' or name_space.lower() in symbol.lower():
                if len(symbol) > pos:
                    letter = symbol[pos:pos + 1]
                    # stop prefix scanning
                    if letter == "_":
                        # stop on "_"
                        break
                    # Should we also stop on a uppercase char, if last was lowercase
                    #   GtkWidget, if we have the 'W' and had the 't' before
                    # or should we count upper and lowercase, and stop one 2nd uppercase, if we already had a lowercase
                    #   GtkWidget, the 'W' would be the 2nd uppercase and with 't','k' we had lowercase chars before
                    # need to recound each time as this is per symbol
                    ul = letter.upper()
                    if ul in prefix:
                        prefix[ul] += 1
                    else:
                        prefix[ul] = 1

        if letter != '' and letter != "_":
            maxletter = ''
            maxsymbols = 0
Stefan Sauer's avatar
Stefan Sauer committed
876
            for letter in prefix.keys():
877
878
879
880
881
                logging.debug("ns prefix: %s: %s", letter, prefix[letter])
                if prefix[letter] > maxsymbols:
                    maxletter = letter
                    maxsymbols = prefix[letter]

882
            ratio = float(len(symbols)) / prefix[maxletter]
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
            logging.debug('most symbols start with %s, that is %f', maxletter, (100 * ratio))
            if ratio > 0.9:
                # do another round
                name_space += maxletter

            pos += 1

        else:
            ratio = 0.0

        if ratio < 0.9:
            break
    return name_space


Stefan Sauer's avatar
Stefan Sauer committed
898
def OutputIndex(basename, apiindex):
899
900
901
902
903
904
    """Writes an index that can be included into the main-document into an <index> tag.

    Args:
        basename (str): name of the index file without extension
        apiindex (dict): the index data
    """
Stefan Sauer's avatar
Stefan Sauer committed
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
    old_index = os.path.join(DB_OUTPUT_DIR, basename + '.xml')
    new_index = os.path.join(DB_OUTPUT_DIR, basename + '.new')
    lastletter = " "
    divopen = 0
    symbol = None
    short_symbol = None

    OUTPUT = open(new_index, 'w')

    OUTPUT.write(MakeDocHeader("indexdiv") + "\n<indexdiv id=\"%s\">\n" % basename)

    logging.info("generate %s index (%d entries) with namespace %s", basename, len(apiindex), NAME_SPACE)

    # do a case insensitive sort while chopping off the prefix
    mapped_keys = [
        {
            'original': x,
            'short': re.sub(r'^' + NAME_SPACE + r'\_?(.*)', r'\1', x.upper(), flags=re.I),
Stefan Sauer's avatar
Stefan Sauer committed
923
        } for x in apiindex.keys()]
Stefan Sauer's avatar
Stefan Sauer committed
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
    sorted_keys = sorted(mapped_keys, key=lambda d: (d['short'], d['original']))

    for key in sorted_keys:
        symbol = key['original']
        short = key['short']
        if short != '':
            short_symbol = short
        else:
            short_symbol = symbol

        # generate a short symbol description
        symbol_desc = ''
        symbol_section = ''
        symbol_section_id = ''
        symbol_type = ''
        if symbol in DeclarationTypes:
            symbol_type = DeclarationTypes[symbol].lower()

        if symbol_type == '':
            logging.info("trying symbol %s", symbol)
            m1 = re.search(r'(.*)::(.*)', symbol)
            m2 = re.search(r'(.*):(.*)', symbol)
946
            m3 = re.search(r'(.*)\|(.*)', symbol)
Stefan Sauer's avatar
Stefan Sauer committed
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
            if m1:
                oname = m1.group(1)
                osym = m1.group(2)
                logging.info("  trying object signal %s:%s in %d signals", oname, osym, len(SignalNames))
                for name in SignalNames:
                    logging.info("    " + name)
                    if name == osym:
                        symbol_type = "object signal"
                        if oname in SymbolSection:
                            symbol_section = SymbolSection[oname]
                            symbol_section_id = SymbolSectionId[oname]
                        break
            elif m2:
                oname = m2.group(1)
                osym = m2.group(2)
                logging.info("  trying object property %s::%s in %d properties", oname, osym, len(ArgNames))
                for name in ArgNames:
                    logging.info("    " + name)
                    if name == osym:
                        symbol_type = "object property"
                        if oname in SymbolSection:
                            symbol_section = SymbolSection[oname]
                            symbol_section_id = SymbolSectionId[oname]
                        break
971
972
973
974
975
976
977
978
979
980
981
982
            elif m3:
                oname = m3.group(1)
                osym = m3.group(2)
                logging.info("  trying action %s|%s in %d actions", oname, osym, len(ActionNames))
                for name in ActionNames:
                    logging.info("    " + name)
                    if name == osym:
                        symbol_type = "action"
                        if oname in SymbolSection:
                            symbol_section = SymbolSection[oname]
                            symbol_section_id = SymbolSectionId[oname]
                        break
Stefan Sauer's avatar
Stefan Sauer committed
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
        else:
            if symbol in SymbolSection:
                symbol_section = SymbolSection[symbol]
                symbol_section_id = SymbolSectionId[symbol]

        if symbol_type != '':
            symbol_desc = ", " + symbol_type
            if symbol_section != '':
                symbol_desc += " in <link linkend=\"%s\">%s</link>" % (symbol_section_id, symbol_section)
                # symbol_desc +=" in " + ExpandAbbreviations(symbol, "#symbol_section")

        curletter = short_symbol[0].upper()
        ixid = apiindex[symbol]

        logging.info("  add symbol %s with %s to index in section '%s' (derived from %s)",
                     symbol, ixid, curletter, short_symbol)

        if curletter != lastletter:
            lastletter = curletter

            if divopen:
                OUTPUT.write("</indexdiv>\n")

            OUTPUT.write("<indexdiv><title>%s</title>\n" % curletter)
            divopen = True

        OUTPUT.write('<indexentry><primaryie linkends="%s"><link linkend="%s">%s</link>%s</primaryie></indexentry>\n' %
                     (ixid, ixid, symbol, symbol_desc))

    if divopen:
        OUTPUT.write("</indexdiv>\n")

    OUTPUT.write("</indexdiv>\n")
    OUTPUT.close()

    common.UpdateFileIfChanged(old_index, new_index, 0)


def OutputSinceIndexes():
1022
    """Generate the 'since' api index files."""
1023
    for version in sorted(set(Since.values())):
Stefan Sauer's avatar
Stefan Sauer committed
1024
        logging.info("Since : [%s]", version)
Stefan Sauer's avatar
Stefan Sauer committed
1025
        index = {x: IndexEntriesSince[x] for x in IndexEntriesSince.keys() if Since[x] == version}
Stefan Sauer's avatar
Stefan Sauer committed
1026
1027
1028
1029
        OutputIndex("api-index-" + version, index)


def OutputAnnotationGlossary():
1030
    """Writes a glossary of the used annotation terms.
Stefan Sauer's avatar
Stefan Sauer committed
1031

1032
1033
    The glossary file can be included into the main document.
    """
Stefan Sauer's avatar
Stefan Sauer committed
1034
1035
1036
1037
    # if there are no annotations used return
    if not AnnotationsUsed:
        return

1038
1039
1040
1041
1042
    old_glossary = os.path.join(DB_OUTPUT_DIR, "annotation-glossary.xml")
    new_glossary = os.path.join(DB_OUTPUT_DIR, "annotation-glossary.new")
    lastletter = " "
    divopen = False

Stefan Sauer's avatar
Stefan Sauer committed
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
    # add acronyms that are referenced from acronym text
    rerun = True
    while rerun:
        rerun = False
        for annotation in AnnotationsUsed:
            if annotation not in AnnotationDefinition:
                continue
            m = re.search(r'<acronym>([\w ]+)<\/acronym>', AnnotationDefinition[annotation])
            if m and m.group(1) not in AnnotationsUsed:
                AnnotationsUsed[m.group(1)] = 1
                rerun = True
                break

Stefan Sauer's avatar
Stefan Sauer committed
1056
    OUTPUT = open(new_glossary, 'w', encoding='utf-8')
Stefan Sauer's avatar
Stefan Sauer committed
1057
1058
1059
1060
1061
1062

    OUTPUT.write('''%s
<glossary id="annotation-glossary">
  <title>Annotation Glossary</title>
''' % MakeDocHeader("glossary"))

Stefan Sauer's avatar
Stefan Sauer committed
1063
    for annotation in sorted(AnnotationsUsed.keys(), key=str.lower):
Stefan Sauer's avatar
Stefan Sauer committed
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
        if annotation in AnnotationDefinition:
            definition = AnnotationDefinition[annotation]
            curletter = annotation[0].upper()

            if curletter != lastletter:
                lastletter = curletter

                if divopen:
                    OUTPUT.write("</glossdiv>\n")

                OUTPUT.write("<glossdiv><title>%s</title>\n" % curletter)
                divopen = True

            OUTPUT.write('''    <glossentry>
      <glossterm><anchor id="annotation-glossterm-%s"/>%s</glossterm>
      <glossdef>
        <para>%s</para>
      </glossdef>
    </glossentry>
''' % (annotation, annotation, definition))

    if divopen:
        OUTPUT.write("</glossdiv>\n")

    OUTPUT.write("</glossary>\n")
    OUTPUT.close()

    common.UpdateFileIfChanged(old_glossary, new_glossary, 0)


Stefan Sauer's avatar
Stefan Sauer committed
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
def OutputObjectTree(tree):
    if not tree:
        return

    # FIXME: use xml
    old_tree_index = os.path.join(DB_OUTPUT_DIR, "tree_index.sgml")
    new_tree_index = os.path.join(DB_OUTPUT_DIR, "tree_index.new")

    with open(new_tree_index, 'w', encoding='utf-8') as out:
        out.write(MakeDocHeader("screen"))
        out.write("\n<screen>\n")
        out.write(AddTreeLineArt(tree))
        out.write("\n</screen>\n")

    common.UpdateFileIfChanged(old_tree_index, new_tree_index, 0)


Stefan Sauer's avatar
Stefan Sauer committed
1111
def ReadKnownSymbols(file):
1112
1113
1114
1115
1116
    """Collect the names of non-private symbols from the $MODULE-sections.txt file.

    Args:
        file: the $MODULE-sections.txt file
    """
Stefan Sauer's avatar
Stefan Sauer committed
1117
1118
1119
1120

    subsection = ''

    logging.info("Reading: %s", file)
Stefan Sauer's avatar
Stefan Sauer committed
1121
    INPUT = open(file, 'r', encoding='utf-8')
Stefan Sauer's avatar
Stefan Sauer committed
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
    for line in INPUT:
        if line.startswith('#'):
            continue

        if line.startswith('<SECTION>'):
            subsection = ''
            continue

        m = re.search(r'^<SUBSECTION\s*(.*)>', line, flags=re.I)
        if m:
            subsection = m.group(1)
            continue

        if line.startswith('<SUBSECTION>'):
            continue

        if re.search(r'^<TITLE>(.*)<\/TITLE>', line):
            continue

        m = re.search(r'^<FILE>(.*)<\/FILE>', line)
        if m:
1143
1144
            KnownSymbols[m.group(1) + ":long_description"] = 1
            KnownSymbols[m.group(1) + ":short_description"] = 1
Stefan Sauer's avatar
Stefan Sauer committed
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
            continue

        m = re.search(r'^<INCLUDE>(.*)<\/INCLUDE>', line)
        if m:
            continue

        m = re.search(r'^<\/SECTION>', line)
        if m:
            continue

        m = re.search(r'^(\S+)', line)
        if m:
            symbol = m.group(1)
            if subsection != "Standard" and subsection != "Private":
                KnownSymbols[symbol] = 1
            else:
                KnownSymbols[symbol] = 0
    INPUT.close()


def OutputDeclaration(symbol, declaration):
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
    """Returns the formatted documentation block for a symbol.

    Args:
        symbol (str): the name of the function/macro/...
        declaration (str): the declaration of the function/macro.

    Returns:
        str: the formatted documentation
    """

Stefan Sauer's avatar
Stefan Sauer committed
1176
    dtype = DeclarationTypes[symbol]
1177
    logging.info('Output  Symbol: "%s" "%s"', symbol, dtype)
Stefan Sauer's avatar
Stefan Sauer committed
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
    if dtype == 'MACRO':
        return OutputMacro(symbol, declaration)
    elif dtype == 'TYPEDEF':
        return OutputTypedef(symbol, declaration)
    elif dtype == 'STRUCT':
        return OutputStruct(symbol, declaration)
    elif dtype == 'ENUM':
        return OutputEnum(symbol, declaration)
    elif dtype == 'UNION':
        return OutputUnion(symbol, declaration)
    elif dtype == 'VARIABLE':
        return OutputVariable(symbol, declaration)
    elif dtype == 'FUNCTION':
        return OutputFunction(symbol, declaration, dtype)
    elif dtype == 'USER_FUNCTION':
        return OutputFunction(symbol, declaration, dtype)
    else:
1195
1196
        logging.warning("Unknown symbol type %s for symbol %s", dtype, symbol)
        return ('', '')
Stefan Sauer's avatar
Stefan Sauer committed
1197
1198
1199


def OutputSymbolTraits(symbol):
1200
1201
1202
1203
1204
1205
1206
1207
1208
    """Returns the Since and StabilityLevel paragraphs for a symbol.

    Args:
        symbol (str): the name to describe

    Returns:
        str: paragraph or empty string
    """

Stefan Sauer's avatar
Stefan Sauer committed
1209
1210
1211
1212
1213
1214
1215
1216
    desc = ''

    if symbol in Since:
        link_id = "api-index-" + Since[symbol]
        desc += "<para role=\"since\">Since: <link linkend=\"%s\">%s</link></para>" % (link_id, Since[symbol])

    if symbol in StabilityLevel:
        stability = StabilityLevel[symbol]
1217
1218
1219
1220
        if stability in AnnotationDefinition:
            AnnotationsUsed[stability] = True
            stability = "<acronym>%s</acronym>" % stability
        desc += "<para role=\"stability\">Stability Level: %s</para>" % stability
Stefan Sauer's avatar
Stefan Sauer committed
1221
1222
1223
1224
1225
1226
1227
1228
    return desc


def uri_escape(text):
    if text is None:
        return None

    # Build a char to hex map
1229
    escapes = {chr(i): ("%%%02X" % i) for i in range(256)}
Stefan Sauer's avatar
Stefan Sauer committed
1230
1231
1232
1233

    # Default unsafe characters.  RFC 2732 ^(uric - reserved)
    def do_escape(char):
        return escapes[char]
1234
    return re.sub(r"([^A-Za-z0-9\-_.!~*'()]", do_escape, text)
Stefan Sauer's avatar
Stefan Sauer committed
1235
1236


1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
def extract_struct_body(symbol, decl, has_typedef, public):
    """Returns a normalized struct body.

    Normalizes whitespace and newlines and supresses non public members.

    Returns:
      str: the nomalized struct declaration
    """
    decl_out = ''

    m = re.search(
        r'^\s*(typedef\s+)?struct\s*\w*\s*(?:\/\*.*\*\/)?\s*{(.*)}\s*\w*\s*;\s*$', decl, flags=re.DOTALL)
    if m:
        new_boby = ''
        for line in m.group(2).splitlines():
            logging.info("Struct line: %s", line)
            m2 = re.search(r'/\*\s*<\s*public\s*>\s*\*/', line)
            m3 = re.search(r'/\*\s*<\s*(private|protected)\s*>\s*\*/', line)
            if m2:
                public = True
            elif m3:
                public = False
            elif public:
                new_boby += line + "\n"

        if new_boby:
            # Strip any blank lines off the ends.
            new_boby = re.sub(r'^\s*\n', '', new_boby)
            new_boby = re.sub(r'\n\s*$', r'\n', new_boby)

            if has_typedef:
                decl_out = "typedef struct {\n%s} %s;\n" % (new_boby, symbol)
            else:
                decl_out = "struct %s {\n%s};\n" % (symbol, new_boby)
    else:
        common.LogWarning(*GetSymbolSourceLocation(symbol),
                          "Couldn't parse struct:\n%s" % decl)

    if decl_out == '':
        # If we couldn't parse the struct or it was all private, output an
        # empty struct declaration.
        if has_typedef:
            decl_out = "typedef struct _%s %s;" % (symbol, symbol)
        else:
            decl_out = "struct %s;" % symbol

    return decl_out


Stefan Sauer's avatar
Stefan Sauer committed
1286
def OutputSymbolExtraLinks(symbol):
1287
1288
1289
1290
1291
1292
1293
1294
    """Returns extralinks for the symbol (if enabled).

    Args:
        symbol (str): the name to describe

    Returns:
        str: paragraph or empty string
    """
Stefan Sauer's avatar
Stefan Sauer committed
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
    desc = ''

    if False:   # NEW FEATURE: needs configurability
        sstr = uri_escape(symbol)
        mstr = uri_escape(MODULE)
        desc += '''<ulink role="extralinks" url="http://www.google.com/codesearch?q=%s">code search</ulink>
<ulink role="extralinks" url="http://library.gnome.org/edit?module=%s&amp;symbol=%s">edit documentation</ulink>
''' % (sstr, mstr, sstr)

    return desc


def OutputSectionExtraLinks(symbol, docsymbol):
    desc = ''

    if False:   # NEW FEATURE: needs configurability
        sstr = uri_escape(symbol)
        mstr = uri_escape(MODULE)
        dsstr = uri_escape(docsymbol)
        desc += '''<ulink role="extralinks" url="http://www.google.com/codesearch?q=%s">code search</ulink>
<ulink role="extralinks" url="http://library.gnome.org/edit?module=%s&amp;symbol=%s">edit documentation</ulink>
''' % (sstr, mstr, dsstr)
    return desc


def OutputMacro(symbol, declaration):
1321
1322
1323
1324
1325
1326
1327
    """Returns the synopsis and detailed description of a macro.

    Args:
        symbol (str): the macro name.
        declaration (str): the declaration of the macro.

    Returns:
Rafael Fontenelle's avatar
Rafael Fontenelle committed
1328
        str: the formatted docs
1329
    """
Stefan Sauer's avatar
Stefan Sauer committed
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
    sid = common.CreateValidSGMLID(symbol)
    condition = MakeConditionDescription(symbol)
    synop = "<row><entry role=\"define_keyword\">#define</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link>" % (
        sid, symbol)

    fields = common.ParseMacroDeclaration(declaration, CreateValidSGML)
    title = symbol
    if len(fields) > 0:
        title += '()'

    desc = '<refsect2 id="%s" role="macro"%s>\n<title>%s</title>\n' % (sid, condition, title)
    desc += MakeIndexterms(symbol, sid)
    desc += "\n"
    desc += OutputSymbolExtraLinks(symbol)

    if len(fields) > 0:
        synop += "<phrase role=\"c_punctuation\">()</phrase>"

    synop += "</entry></row>\n"

    # Don't output the macro definition if is is a conditional macro or it
    # looks like a function, i.e. starts with "g_" or "_?gnome_", or it is
    # longer than 2 lines, otherwise we get lots of complicated macros like
    # g_assert.
    if symbol not in DeclarationConditional and not symbol.startswith('g_') \
            and not re.search(r'^_?gnome_', symbol) and declaration.count('\n') < 2:
        decl_out = CreateValidSGML(declaration)
        desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out
    else:
1359
        desc += "<programlisting language=\"C\">" + "#define".ljust(RETURN_TYPE_FIELD_WIDTH) + symbol
Stefan Sauer's avatar
Stefan Sauer committed
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
        m = re.search(r'^\s*#\s*define\s+\w+(\([^\)]*\))', declaration)
        if m:
            args = m.group(1)
            pad = ' ' * (RETURN_TYPE_FIELD_WIDTH - len("#define "))
            # Align each line so that if should all line up OK.
            args = args.replace('\n', '\n' + pad)
            desc += CreateValidSGML(args)

        desc += "</programlisting>\n"

    desc += MakeDeprecationNote(symbol)

    parameters = OutputParamDescriptions("MACRO", symbol, fields)

    if symbol in SymbolDocs:
        symbol_docs = ConvertMarkDown(symbol, SymbolDocs[symbol])
        desc += symbol_docs

    desc += parameters
    desc += OutputSymbolTraits(symbol)
    desc += "</refsect2>\n"
    return (synop, desc)


def OutputTypedef(symbol, declaration):
1385
1386
1387
1388
1389
1390
1391
1392
    """Returns the synopsis and detailed description of a typedef.

    Args:
        symbol (str): the typedef.
        declaration (str): the declaration of the typedef,
                           e.g. 'typedef unsigned int guint;'

    Returns:
Rafael Fontenelle's avatar
Rafael Fontenelle committed
1393
        str: the formatted docs
1394
    """
Stefan Sauer's avatar
Stefan Sauer committed
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
    sid = common.CreateValidSGMLID(symbol)
    condition = MakeConditionDescription(symbol)
    desc = "<refsect2 id=\"%s\" role=\"typedef\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
    synop = "<row><entry role=\"typedef_keyword\">typedef</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
        sid, symbol)

    desc += MakeIndexterms(symbol, sid)
    desc += "\n"
    desc += OutputSymbolExtraLinks(symbol)

1405
    if symbol not in DeclarationConditional:
Stefan Sauer's avatar
Stefan Sauer committed
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
        decl_out = CreateValidSGML(declaration)
        desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out

    desc += MakeDeprecationNote(symbol)

    if symbol in SymbolDocs:
        desc += ConvertMarkDown(symbol, SymbolDocs[symbol])

    desc += OutputSymbolTraits(symbol)
    desc += "</refsect2>\n"
    return (synop, desc)


def OutputStruct(symbol, declaration):
1420
    """Returns the synopsis and detailed description of a struct.
Stefan Sauer's avatar
Stefan Sauer committed
1421

1422
1423
1424
1425
1426
1427
1428
1429
1430
    We check if it is a object struct, and if so we only output parts of it that
    are noted as public fields. We also use a different IDs for object structs,
    since the original ID is used for the entire RefEntry.

    Args:
        symbol (str): the struct.
        declaration (str): the declaration of the struct.

    Returns:
Rafael Fontenelle's avatar
Rafael Fontenelle committed
1431
        str: the formatted docs
1432
    """
Stefan Sauer's avatar
Stefan Sauer committed
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
    is_gtype = False
    default_to_public = True
    if CheckIsObject(symbol):
        logging.info("Found struct gtype: %s", symbol)
        is_gtype = True
        default_to_public = ObjectRoots[symbol] == 'GBoxed'

    sid = None
    condition = None
    if is_gtype:
        sid = common.CreateValidSGMLID(symbol + "_struct")
        condition = MakeConditionDescription(symbol + "_struct")
    else:
        sid = common.CreateValidSGMLID(symbol)
        condition = MakeConditionDescription(symbol)

    # Determine if it is a simple struct or it also has a typedef.
    has_typedef = False
    if symbol in StructHasTypedef or re.search(r'^\s*typedef\s+', declaration):
        has_typedef = True

    type_output = None
    desc = None
    if has_typedef:
        # For structs with typedefs we just output the struct name.
        type_output = ''
        desc = "<refsect2 id=\"%s\" role=\"struct\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
    else:
        type_output = "struct"
        desc = "<refsect2 id=\"%s\" role=\"struct\"%s>\n<title>struct %s</title>\n" % (sid, condition, symbol)

    synop = "<row><entry role=\"datatype_keyword\">%s</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
        type_output, sid, symbol)

    desc += MakeIndexterms(symbol, sid)
    desc += "\n"
    desc += OutputSymbolExtraLinks(symbol)

    # Form a pretty-printed, private-data-removed form of the declaration

    decl_out = ''
    if re.search(r'^\s*$', declaration):
        logging.info("Found opaque struct: %s", symbol)
        decl_out = "typedef struct _%s %s;" % (symbol, symbol)
    elif re.search(r'^\s*struct\s+\w+\s*;\s*$', declaration):
        logging.info("Found opaque struct: %s", symbol)
        decl_out = "struct %s;" % symbol
    else:
1481
        decl_out = extract_struct_body(symbol, declaration, has_typedef, default_to_public)
Stefan Sauer's avatar
Stefan Sauer committed
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498

    decl_out = CreateValidSGML(decl_out)
    desc += "<programlisting language=\"C\">%s</programlisting>\n" % decl_out

    desc += MakeDeprecationNote(symbol)

    if symbol in SymbolDocs:
        desc += ConvertMarkDown(symbol, SymbolDocs[symbol])

    # Create a table of fields and descriptions

    # FIXME: Inserting &#160's into the produced type declarations here would
    #        improve the output in most situations ... except for function
    #        members of structs!
    def pfunc(*args):
        return '<structfield id="%s">%s</structfield>' % (common.CreateValidSGMLID(sid + '.' + args[0]), args[0])
    fields = common.ParseStructDeclaration(declaration, not default_to_public, 0, MakeXRef, pfunc)
1499
    field_descrs, found = GetSymbolParams(symbol)
Stefan Sauer's avatar
Stefan Sauer committed
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514

    if found:
        missing_parameters = ''
        unused_parameters = ''
        sid = common.CreateValidSGMLID(symbol + ".members")

        desc += '''<refsect3 id="%s" role="struct_members">\n<title>Members</title>
<informaltable role="struct_members_table" pgwide="1" frame="none">
<tgroup cols="3">
<colspec colname="struct_members_name" colwidth="300px"/>
<colspec colname="struct_members_description"/>
<colspec colname="struct_members_annotations" colwidth="200px"/>
<tbody>
''' % sid

Stefan Sauer's avatar
Stefan Sauer committed
1515
        for field_name, text in fields.items():
Stefan Sauer's avatar
Stefan Sauer committed
1516
1517
1518
1519
            param_annotations = ''

            desc += "<row role=\"member\"><entry role=\"struct_member_name\"><para>%s</para></entry>\n" % text
            if field_name in field_descrs:
1520
                field_descr, param_annotations = ExpandAnnotation(symbol, field_descrs[field_name])
Stefan Sauer's avatar
Stefan Sauer committed
1521
1522
1523
1524
1525
1526
1527
1528
                field_descr = ConvertMarkDown(symbol, field_descr)
                # trim
                field_descr = re.sub(r'^(\s|\n)+', '', field_descr, flags=re.M | re.S)
                field_descr = re.sub(r'(\s|\n)+$', '', field_descr, flags=re.M | re.S)
                desc += "<entry role=\"struct_member_description\">%s</entry>\n<entry role=\"struct_member_annotations\">%s</entry>\n" % (
                    field_descr, param_annotations)
                del field_descrs[field_name]
            else:
1529
                common.LogWarning(*GetSymbolSourceLocation(symbol),
Stefan Sauer's avatar
Stefan Sauer committed
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
                                  "Field description for %s::%s is missing in source code comment block." % (symbol, field_name))
                if missing_parameters != '':
                    missing_parameters += ", " + field_name
                else:
                    missing_parameters = field_name

                desc += "<entry /><entry />\n"

            desc += "</row>\n"

        desc += "</tbody></tgroup></informaltable>\n</refsect3>\n"
        for field_name in field_descrs:
            # Documenting those standard fields is not required anymore, but
            # we don't want to warn if they are documented anyway.
            m = re.search(r'(g_iface|parent_instance|parent_class)', field_name)
            if m:
                continue

1548
            common.LogWarning(*GetSymbolSourceLocation(symbol),
Stefan Sauer's avatar
Stefan Sauer committed
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
                              "Field description for %s::%s is not used from source code comment block." % (symbol, field_name))
            if unused_parameters != '':
                unused_parameters += ", " + field_name
            else:
                unused_parameters = field_name

        # remember missing/unused parameters (needed in tmpl-free build)
        if missing_parameters != '' and (symbol not in AllIncompleteSymbols):
            AllIncompleteSymbols[symbol] = missing_parameters

        if unused_parameters != '' and (symbol not in AllUnusedSymbols):
            AllUnusedSymbols[symbol] = unused_parameters
    else:
        if fields:
            if symbol not in AllIncompleteSymbols:
                AllIncompleteSymbols[symbol] = "<items>"
1565
                common.LogWarning(*GetSymbolSourceLocation(symbol),
Stefan Sauer's avatar
Stefan Sauer committed
1566
1567
1568
1569
1570
1571
1572
1573
1574
                                  "Field descriptions for struct %s are missing in source code comment block." % symbol)
                logging.info("Remaining structs fields: " + ':'.join(fields) + "\n")

    desc += OutputSymbolTraits(symbol)
    desc += "</refsect2>\n"
    return (synop, desc)


def OutputUnion(symbol, declaration):
1575
1576
1577
1578
1579
    """Returns the synopsis and detailed description of a union.

    Args:
        symbol (str): the union.
        declaration (str): the declaration of the union.
Stefan Sauer's avatar
Stefan Sauer committed
1580

1581
    Returns:
Rafael Fontenelle's avatar
Rafael Fontenelle committed
1582
        str: the formatted docs
1583
    """
Stefan Sauer's avatar
Stefan Sauer committed
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
    is_gtype = False
    if CheckIsObject(symbol):
        logging.info("Found union gtype: %s", symbol)
        is_gtype = True

    sid = None
    condition = None
    if is_gtype:
        sid = common.CreateValidSGMLID(symbol + "_union")
        condition = MakeConditionDescription(symbol + "_union")
    else:
        sid = common.CreateValidSGMLID(symbol)
        condition = MakeConditionDescription(symbol)

    # Determine if it is a simple struct or it also has a typedef.
    has_typedef = False
    if symbol in StructHasTypedef or re.search(r'^\s*typedef\s+', declaration):
        has_typedef = True

    type_output = None
    desc = None
    if has_typedef:
        # For unions with typedefs we just output the union name.
        type_output = ''
        desc = "<refsect2 id=\"%s\" role=\"union\"%s>\n<title>%s</title>\n" % (sid, condition, symbol)
    else:
        type_output = "union"
        desc = "<refsect2 id=\"%s\" role=\"union\"%s>\n<title>union %s</title>\n" % (sid, condition, symbol)

    synop = "<row><entry role=\"datatype_keyword\">%s</entry><entry role=\"function_name\"><link linkend=\"%s\">%s</link></entry></row>\n" % (
        type_output, sid, symbol)

    desc += MakeIndexterms(symbol, sid)
    desc += "\n"
    desc += OutputSymbolExtraLinks(symbol)
    desc += MakeDeprecationNote(symbol)

    if symbol in SymbolDocs:
        desc += ConvertMarkDown(symbol, SymbolDocs[symbol])

    # Create a table of fields and descriptions

    # FIXME: Inserting &#160's into the produced type declarations here would
    #        improve the output in most situations ... except for function
    #        members of structs!
    def pfunc(*args):
        return '<structfield id="%s">%s</structfield>' % (common.CreateValidSGMLID(sid + '.' + args[0]), args[0])
    fields = common.ParseStructDeclaration(declaration, 0, 0, MakeXRef, pfunc)
1632
    field_descrs, found = GetSymbolParams(symbol)
Stefan Sauer's avatar
Stefan Sauer committed
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649

    logging.debug('Union %s has %d entries, found=%d, has_typedef=%d', symbol, len(fields), found, has_typedef)

    if found:
        missing_parameters = ''
        unused_parameters = ''
        sid = common.CreateValidSGMLID('%s.members' % symbol)

        desc += '''<refsect3 id="%s" role="union_members">\n<title>Members</title>
<informaltable role="union_members_table" pgwide="1" frame="none">
<tgroup cols="3">
<colspec colname="union_members_name" colwidth="300px"/>
<colspec colname="union_members_description"/>
<colspec colname="union_members_annotations" colwidth="200px"/>
<tbody>
''' % sid

Stefan Sauer's avatar
Stefan Sauer committed
1650
        for field_name, text in fields.items():
Stefan Sauer's avatar
Stefan Sauer committed
1651
1652
1653
1654
            param_annotations = ''

            desc += "<row><entry role=\"union_member_name\"><para>%s</para></entry>\n" % text
            if field_name in field_descrs:
1655
                field_descr, param_annotations = ExpandAnnotation(symbol, field_descrs[field_name])
Stefan Sauer's avatar
Stefan Sauer committed
1656
1657
1658
1659
1660
1661
1662
1663
1664
                field_descr = ConvertMarkDown(symbol, field_descr)

                # trim
                field_descr = re.sub(r'^(\s|\n)+', '', field_descr, flags=re.M | re.S)
                field_descr = re.sub(r'(\s|\n)+$', '', field_descr, flags=re.M | re.S)
                desc += "<entry role=\"union_member_description\">%s</entry>\n<entry role=\"union_member_annotations\">%s</entry>\n" % (
                    field_descr, param_annotations)
                del field_descrs[field_name]
            else:
1665
                common.LogWarning(*GetSymbolSourceLocation(symbol),
Stefan Sauer's avatar
Stefan Sauer committed
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
                                  "Field description for %s::%s is missing in source code comment block." % (symbol, field_name))
                if missing_parameters != '':
                    missing_parameters += ", " + field_name
                else:
                    missing_parameters = field_name

                desc += "<entry /><entry />\n"

            desc += "</row>\n"

        desc += "</tbody></tgroup></informaltable>\n</refsect3>"
        for field_name in field_descrs:
1678
            common.LogWarning(*GetSymbolSourceLocation(symbol),