1# SPDX-FileCopyrightText: 2021 GNOME Foundation
2# SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later
3
4import argparse
5import concurrent.futures
6import jinja2
7import markdown
8import os
9import shutil
10import sys
11
12import xml.etree.ElementTree as etree
13
14from markupsafe import Markup
15
16from . import config, gir, log, utils
17from . import gdgenindices
18
19
20HELP_MSG = "Generates the reference"
21
22MISSING_DESCRIPTION = "No description available."
23
24STRING_TYPES = {
25    'utf8': 'The string is a NUL terminated UTF-8 string',
26    'filename': 'The string is a file system path, using the OS encoding',
27}
28
29ARG_TRANSFER_MODES = {
30    'none': 'The data is owned by the caller of the function',
31    'container': 'The called function takes ownership of the data container, but not the data inside it',
32    'full': 'The called function takes ownership of the data, and is responsible for freeing it',
33}
34
35RETVAL_TRANSFER_MODES = {
36    'none': 'The data is owned by the called function',
37    'container': 'The caller of the function takes ownership of the data container, but not the data inside it',
38    'full': 'The caller of the function takes ownership of the data, and is responsible for freeing it',
39    'floating': 'The returned data has a floating reference',
40}
41
42DIRECTION_MODES = {
43    'in': 'in',
44    'inout': 'in-out',
45    'out': 'out',
46}
47
48SCOPE_MODES = {
49    'none': '-',
50    'call': 'Arguments are valid during the call',
51    'notified': 'Arguments are valid until the notify function is called',
52    'async': 'Arguments are valid until the call is completed',
53}
54
55SIGNAL_WHEN = {
56    'first': "The default handler is called before the handlers added via `g_signal_connect()`",
57    'last': "The default handler is called after the handlers added via `g_signal_connect()`",
58    'cleanup': "The default handler is called after the handlers added via `g_signal_connect_after()`",
59}
60
61FRAGMENT = {
62    "aliases": "alias",
63    "bitfields": "flags",
64    "callbacks": "callback",
65    "classes": "class",
66    "constants": "const",
67    "domains": "error",
68    "enums": "enum",
69    "functions": "func",
70    "function_macros": "func",
71    "interfaces": "iface",
72    "structs": "struct",
73    "unions": "union",
74}
75
76
77def type_name_to_cname(fqtn, is_pointer=False):
78    res = []
79    try:
80        ns, name = fqtn.split('.', 1)
81        res.append(ns)
82        res.append(name)
83    except ValueError:
84        res.append(fqtn.replace('.', ''))
85    if is_pointer:
86        res.append('*')
87    return "".join(res)
88
89
90def gen_index_func(func, namespace, md=None):
91    """Generates a dictionary with the callable metadata required by an index template"""
92    name = func.name
93    if getattr(func, "identifier"):
94        identifier = func.identifier
95    else:
96        identifier = None
97    if func.doc is not None:
98        summary = utils.preprocess_docs(func.doc.content, namespace, summary=True, md=md)
99    else:
100        summary = MISSING_DESCRIPTION
101    if func.available_since is not None:
102        available_since = func.available_since
103    else:
104        available_since = None
105    if func.deprecated_since is not None:
106        (version, msg) = func.deprecated_since
107        deprecated_since = version
108    else:
109        deprecated_since = None
110    return {
111        "name": name,
112        "identifier": identifier,
113        "summary": summary,
114        "available_since": available_since,
115        "deprecated_since": deprecated_since,
116    }
117
118
119def gen_index_property(prop, namespace, md=None):
120    name = prop.name
121    if prop.doc is not None:
122        summary = utils.preprocess_docs(prop.doc.content, namespace, summary=True, md=md)
123    else:
124        summary = MISSING_DESCRIPTION
125    if prop.available_since is not None:
126        available_since = prop.available_since
127    else:
128        available_since = None
129    if prop.deprecated_since is not None:
130        (version, msg) = prop.deprecated_since
131        deprecated_since = version
132    else:
133        deprecated_since = None
134    return {
135        "name": name,
136        "summary": summary,
137        "available_since": available_since,
138        "deprecated_since": deprecated_since,
139    }
140
141
142def gen_index_signal(signal, namespace, md=None):
143    name = signal.name
144    if signal.doc is not None:
145        summary = utils.preprocess_docs(signal.doc.content, namespace, summary=True, md=md)
146    else:
147        summary = MISSING_DESCRIPTION
148    if signal.available_since is not None:
149        available_since = signal.available_since
150    else:
151        available_since = None
152    if signal.deprecated_since is not None:
153        (version, msg) = signal.deprecated_since
154        deprecated_since = version
155    else:
156        deprecated_since = None
157    return {
158        "name": name,
159        "summary": summary,
160        "available_since": available_since,
161        "deprecated_since": deprecated_since,
162    }
163
164
165def gen_type_link(namespace, name):
166    t = namespace.find_real_type(name)
167    if t is not None:
168        if isinstance(t, gir.Alias):
169            return f"<a href=\"alias.{name}.html\"><code>{t.ctype}</code></a>"
170        elif isinstance(t, gir.BitField):
171            return f"<a href=\"flags.{name}.html\"><code>{t.ctype}</code></a>"
172        elif isinstance(t, gir.Class):
173            return f"<a href=\"class.{name}.html\"><code>{t.ctype}</code></a>"
174        elif isinstance(t, gir.ErrorDomain):
175            return f"<a href=\"error.{name}.html\"><code>{t.ctype}</code></a>"
176        elif isinstance(t, gir.Enumeration):
177            return f"<a href=\"enum.{name}.html\"><code>{t.ctype}</code></a>"
178        elif isinstance(t, gir.Interface):
179            return f"<a href=\"iface.{name}.html\"><code>{t.ctype}</code></a>"
180        elif isinstance(t, gir.Record):
181            return f"<a href=\"struct.{name}.html\"><code>{t.ctype}</code></a>"
182        elif isinstance(t, gir.Union):
183            return f"<a href=\"union.{name}.html\"><code>{t.ctype}</code></a>"
184    return f"<code>{namespace.identifier_prefix[0]}{name}</code>"
185
186
187class TemplateConstant:
188    def __init__(self, namespace, const):
189        self.value = const.value
190        self.identifier = const.ctype
191        self.type_cname = const.target.ctype
192        self.namespace = namespace.name
193        self.name = const.name
194        self.fqtn = f"{namespace.name}.{const.name}"
195
196        if const.doc is not None:
197            self.summary = utils.preprocess_docs(const.doc.content, namespace, summary=True)
198            self.description = utils.preprocess_docs(const.doc.content, namespace)
199            filename = const.doc.filename
200            if filename.startswith('../'):
201                filename = filename.replace('../', '')
202            line = const.doc.line
203            const.docs_location = (filename, line)
204        else:
205            self.description = MISSING_DESCRIPTION
206
207        self.stability = const.stability
208        self.attributes = const.attributes
209        self.available_since = const.available_since
210        if const.deprecated_since is not None:
211            (version, msg) = const.deprecated_since
212            self.deprecated_since = {
213                "version": version,
214                "message": utils.preprocess_docs(msg, namespace),
215            }
216        else:
217            self.deprecated_since = None
218
219        self.introspectable = const.introspectable
220        self.hierarchy_svg = None
221
222    @property
223    def c_decl(self):
224        return utils.code_highlight(f"#define {self.identifier} {self.value}")
225
226
227class TemplateProperty:
228    def __init__(self, namespace, type_, prop):
229        self.name = prop.name
230        self.type_name = prop.target.name
231        self.type_cname = prop.target.ctype
232        if self.type_cname is None:
233            self.type_cname = type_name_to_cname(prop.target.name, True)
234        self.readable = prop.readable
235        self.writable = prop.writable
236        self.construct = prop.construct
237        self.construct_only = prop.construct_only
238        if prop.doc is not None:
239            self.summary = utils.preprocess_docs(prop.doc.content, namespace, summary=True)
240            self.description = utils.preprocess_docs(prop.doc.content, namespace)
241            filename = prop.doc.filename
242            if filename.startswith('../'):
243                filename = filename.replace('../', '')
244            line = prop.doc.line
245            self.docs_location = (filename, line)
246        else:
247            self.description = MISSING_DESCRIPTION
248
249        self.stability = prop.stability
250        self.available_since = prop.available_since
251        if prop.deprecated_since is not None:
252            (version, msg) = prop.deprecated_since
253            self.deprecated_since = {
254                "version": version,
255                "message": utils.preprocess_docs(msg, namespace),
256            }
257        else:
258            self.deprecated_since = None
259
260        self.introspectable = prop.introspectable
261
262        def transform_set_attribute(namespace, prop, setter_func):
263            if setter_func is None:
264                log.warning(f"Missing value in the set attribute for {prop.name}")
265                return None
266            t = namespace.find_symbol(setter_func)
267            if t is None:
268                log.warning(f"Invalid Property.set attribute for {prop.name}: {setter_func}")
269                return setter_func
270            if not (isinstance(t, gir.Class) or isinstance(t, gir.Interface)):
271                log.warning(f"Invalid setter function {setter_func} for property {namespace.name}.{t.name}:{prop.name}")
272                return setter_func
273            func_name = setter_func.replace(namespace.symbol_prefix[0] + '_', '')
274            func_name = func_name.replace(t.symbol_prefix + '_', '')
275            href = f"method.{t.name}.{func_name}.html"
276            return Markup(f"<a href=\"{href}\"><code>{setter_func}</code></a>")
277
278        def transform_get_attribute(namespace, prop, getter_func):
279            if getter_func is None:
280                log.warning(f"Missing value in the get attribute for {prop.name}")
281                return None
282            t = namespace.find_symbol(getter_func)
283            if t is None:
284                log.warning(f"Invalid Property.get attribute for {prop.name}: {getter_func}")
285                return getter_func
286            if not (isinstance(t, gir.Class) or isinstance(t, gir.Interface)):
287                log.warning(f"Invalid getter function {getter_func} for property {namespace.name}.{t.name}:{prop.name}")
288                return getter_func
289            func_name = getter_func.replace(namespace.symbol_prefix[0] + '_', '')
290            func_name = func_name.replace(t.symbol_prefix + '_', '')
291            href = f"method.{t.name}.{func_name}.html"
292            return Markup(f"<a href=\"{href}\"><code>{getter_func}</code></a>")
293
294        ATTRIBUTE_NAMES = {
295            "org.gtk.Property.set": {
296                "label": "Setter method",
297                "transform": transform_set_attribute,
298            },
299            "org.gtk.Property.get": {
300                "label": "Getter method",
301                "transform": transform_get_attribute,
302            },
303        }
304
305        self.attributes = {}
306        for name in (prop.attributes or {}):
307            value = prop.attributes[name]
308            if name in ATTRIBUTE_NAMES:
309                label = ATTRIBUTE_NAMES[name].get("label")
310                transform = ATTRIBUTE_NAMES[name].get("transform")
311                if transform is not None:
312                    self.attributes[label] = transform(namespace, prop, value)
313            else:
314                self.attributes[name] = value
315        if self.type_name is not None:
316            name = self.type_name
317        else:
318            name = None
319        if name is not None:
320            if name.startswith(namespace.name):
321                self.link = gen_type_link(namespace, name[len(namespace.name) + 1:])
322
323    @property
324    def c_decl(self):
325        flags = []
326        if self.readable:
327            flags += ['read']
328        if self.writable:
329            flags += ['write']
330        if self.construct:
331            flags += ['construct']
332        if self.construct_only:
333            flags += ['construct-only']
334        flags = ", ".join(flags)
335        return f"property {self.name}: {self.type_name} [ {flags} ]"
336
337
338class TemplateArgument:
339    def __init__(self, namespace, call, argument):
340        self.name = argument.name
341        self.type_name = argument.target.name
342        self.type_cname = argument.target.ctype
343        if self.type_cname is None:
344            self.type_cname = type_name_to_cname(argument.target.name, True)
345        self.is_array = isinstance(argument.target, gir.ArrayType)
346        self.is_list = isinstance(argument.target, gir.ListType)
347        self.is_map = isinstance(argument.target, gir.MapType)
348        self.is_varargs = isinstance(argument.target, gir.VarArgs)
349        self.is_macro = isinstance(call, gir.FunctionMacro)
350        self.transfer = argument.transfer or 'none'
351        self.transfer_note = ARG_TRANSFER_MODES[argument.transfer or 'none']
352        self.direction = DIRECTION_MODES[argument.direction]
353        self.nullable = argument.nullable
354        self.scope = SCOPE_MODES[argument.scope or 'none']
355        self.introspectable = argument.introspectable
356        if self.type_name in ['utf8', 'filename']:
357            self.string_note = STRING_TYPES[self.type_name]
358        if argument.closure != -1:
359            self.closure = call.parameters[argument.closure]
360        else:
361            self.closure = None
362        if self.is_array:
363            self.value_type = argument.target.value_type.name
364            self.value_type_cname = argument.target.value_type.ctype
365            self.fixed_size = argument.target.fixed_size
366            self.zero_terminated = argument.target.zero_terminated
367            self.len_arg = argument.target.length != -1 and call.parameters[argument.target.length].name
368        if self.is_list:
369            self.value_type = argument.target.value_type.name
370            self.value_type_cname = argument.target.value_type.ctype
371        if argument.doc is not None:
372            self.summary = utils.preprocess_docs(argument.doc.content, namespace, summary=True)
373            self.description = utils.preprocess_docs(argument.doc.content, namespace)
374        else:
375            self.description = MISSING_DESCRIPTION
376        if self.is_array:
377            name = self.value_type
378        elif self.is_list:
379            name = self.value_type
380        elif self.type_name is not None:
381            name = self.type_name
382        else:
383            name = None
384        if name is not None:
385            if name.startswith(namespace.name):
386                self.link = gen_type_link(namespace, name[len(namespace.name) + 1:])
387            else:
388                if self.is_array:
389                    self.link = f"<code>{self.value_type_cname}</code>"
390                elif self.is_list:
391                    self.link = f"<code>{self.value_type_cname}</code>"
392
393    @property
394    def is_pointer(self):
395        return '*' in self.type_cname
396
397    @property
398    def c_decl(self):
399        if self.is_varargs:
400            return "..."
401        elif self.is_macro:
402            return f"{self.name}"
403        else:
404            return f"{self.type_cname} {self.name}"
405
406
407class TemplateReturnValue:
408    def __init__(self, namespace, call, retval):
409        self.name = retval.name
410        self.type_name = retval.target.name
411        self.type_cname = retval.target.ctype
412        if self.type_cname is None:
413            self.type_cname = type_name_to_cname(retval.target.name, True)
414        self.is_array = isinstance(retval.target, gir.ArrayType)
415        self.is_list = isinstance(retval.target, gir.ListType)
416        self.transfer = retval.transfer or 'none'
417        self.transfer_note = RETVAL_TRANSFER_MODES[retval.transfer or 'none']
418        self.nullable = retval.nullable
419        if self.is_array:
420            self.value_type = retval.target.value_type.name
421            self.value_type_cname = retval.target.value_type.ctype
422            self.fixed_size = retval.target.fixed_size
423            self.zero_terminated = retval.target.zero_terminated
424            self.len_arg = retval.target.length != -1 and call.parameters[retval.target.length].name
425        if self.is_list:
426            self.value_type = retval.target.value_type.name
427            self.value_type_cname = retval.target.value_type.ctype
428        if self.type_name in ['utf8', 'filename']:
429            self.string_note = STRING_TYPES[self.type_name]
430        if retval.doc is not None:
431            self.summary = utils.preprocess_docs(retval.doc.content, namespace, summary=True)
432            self.description = utils.preprocess_docs(retval.doc.content, namespace)
433        else:
434            self.description = MISSING_DESCRIPTION
435        self.introspectable = retval.introspectable
436        if self.is_array:
437            name = self.value_type
438        elif self.is_list:
439            name = self.value_type
440        elif self.type_name is not None:
441            name = self.type_name
442        else:
443            name = None
444        if name is not None:
445            if name.startswith(namespace.name):
446                self.link = gen_type_link(namespace, name[len(namespace.name) + 1:])
447            else:
448                if self.is_array:
449                    self.link = f"<code>{self.value_type_cname}</code>"
450                elif self.is_list:
451                    self.link = f"<code>{self.value_type_cname}</code>"
452
453    @property
454    def is_pointer(self):
455        return '*' in self.type_cname
456
457
458class TemplateSignal:
459    def __init__(self, namespace, type_, signal):
460        self.name = signal.name
461        self.type_cname = type_.base_ctype
462        self.identifier = signal.name.replace("-", "_")
463
464        if signal.doc is not None:
465            self.summary = utils.preprocess_docs(signal.doc.content, namespace, summary=True)
466            self.description = utils.preprocess_docs(signal.doc.content, namespace)
467            filename = signal.doc.filename
468            if filename.startswith('../'):
469                filename = filename.replace('../', '')
470            line = signal.doc.line
471            self.docs_location = (filename, line)
472        else:
473            self.description = MISSING_DESCRIPTION
474
475        self.is_detailed = signal.detailed
476        self.is_action = signal.action
477        self.no_recurse = signal.no_recurse
478        self.no_hooks = signal.no_hooks
479        if signal.when:
480            self.when = utils.preprocess_docs(SIGNAL_WHEN[signal.when], namespace)
481
482        self.arguments = []
483        for arg in signal.parameters:
484            self.arguments.append(TemplateArgument(namespace, signal, arg))
485
486        self.return_value = None
487        if not isinstance(signal.return_value.target, gir.VoidType):
488            self.return_value = TemplateReturnValue(namespace, signal, signal.return_value)
489
490        self.stability = signal.stability
491        self.attributes = signal.attributes
492        self.available_since = signal.available_since
493        if signal.deprecated_since is not None:
494            (version, msg) = signal.deprecated_since
495            self.deprecated_since = {
496                "version": version,
497                "message": utils.preprocess_docs(msg, namespace),
498            }
499        else:
500            self.deprecated_since = None
501
502        self.introspectable = signal.introspectable
503
504    @property
505    def c_decl(self):
506        res = []
507        if self.return_value is None:
508            res += ["void"]
509        else:
510            res += [f"{self.return_value.type_cname}"]
511        res += [f"{self.identifier} ("]
512        res += [f"  {self.type_cname} self,"]
513        for arg in self.arguments:
514            res += [f"  {arg.c_decl},"]
515        res += ["  gpointer user_data"]
516        res += [")"]
517        return utils.code_highlight("\n".join(res))
518
519
520class TemplateMethod:
521    def __init__(self, namespace, type_, method):
522        self.name = method.name
523        self.identifier = method.identifier
524
525        if method.doc is not None:
526            self.summary = utils.preprocess_docs(method.doc.content, namespace, summary=True)
527            self.description = utils.preprocess_docs(method.doc.content, namespace)
528            filename = method.doc.filename
529            line = method.doc.line
530            if filename.startswith('../'):
531                filename = filename.replace('../', '')
532            self.docs_location = (filename, line)
533        else:
534            self.description = MISSING_DESCRIPTION
535
536        self.throws = method.throws
537
538        self.instance_parameter = TemplateArgument(namespace, method, method.instance_param)
539
540        self.arguments = []
541        for arg in method.parameters:
542            self.arguments.append(TemplateArgument(namespace, method, arg))
543
544        self.return_value = None
545        if not isinstance(method.return_value.target, gir.VoidType):
546            self.return_value = TemplateReturnValue(namespace, method, method.return_value)
547
548        self.stability = method.stability
549        self.available_since = method.available_since
550        if method.deprecated_since is not None:
551            (version, msg) = method.deprecated_since
552            self.deprecated_since = {
553                "version": version,
554                "message": utils.preprocess_docs(msg, namespace),
555            }
556        else:
557            self.deprecated_since = None
558
559        if method.source_position is not None:
560            filename, line = method.source_position
561            if filename.startswith('../'):
562                filename = filename.replace('../', '')
563            self.source_location = (filename, line)
564
565        self.introspectable = method.introspectable
566
567        def transform_property_attribute(namespace, type_, method, value):
568            if value in type_.properties:
569                text = f"{namespace.name}.{type_.name}:{value}"
570                href = f"property.{type_.name}.{value}.html"
571                return Markup(f"<a href=\"{href}\"><code>{text}</code></a>")
572            log.warning(f"Property {value} linked to method {method.name} not found in {namespace.name}.{type_.name}")
573            return value
574
575        def transform_signal_attribute(namespace, type_, method, value):
576            if value in type_.signals:
577                text = f"{namespace.name}.{type_.name}::{value}"
578                href = f"signal.{type_.name}.{value}.html"
579                return Markup(f"<a href=\"{href}\"><code>{text}</code></a>")
580            log.warning(f"Signal {value} linked to method {method.name} not found in {namespace.name}.{type_.name}")
581            return value
582
583        ATTRIBUTE_NAMES = {
584            "org.gtk.Method.set_property": {
585                "label": "Sets property",
586                "transform": transform_property_attribute,
587            },
588            "org.gtk.Method.get_property": {
589                "label": "Gets property",
590                "transform": transform_property_attribute,
591            },
592            "org.gtk.Method.signal": {
593                "label": "Emits signal",
594                "transform": transform_signal_attribute,
595            }
596        }
597
598        self.attributes = {}
599        for name in (method.attributes or {}):
600            value = method.attributes[name]
601            if name in ATTRIBUTE_NAMES:
602                label = ATTRIBUTE_NAMES[name].get("label")
603                transform = ATTRIBUTE_NAMES[name].get("transform")
604                if transform is not None:
605                    self.attributes[label] = transform(namespace, type_, method, value)
606            else:
607                self.attributes[name] = value
608
609    @property
610    def c_decl(self):
611        res = []
612        if self.return_value is None:
613            res += ["void"]
614        else:
615            res += [f"{self.return_value.type_cname}"]
616        if self.identifier is not None:
617            res += [f"{self.identifier} ("]
618        else:
619            res += [f"{self.name} ("]
620        n_args = len(self.arguments)
621        if n_args == 0:
622            res += [f"  {self.instance_parameter.type_cname} {self.instance_parameter.name}"]
623        else:
624            res += [f"  {self.instance_parameter.type_cname} {self.instance_parameter.name},"]
625            for (idx, arg) in enumerate(self.arguments):
626                if idx == n_args - 1 and not self.throws:
627                    res += [f"  {arg.c_decl}"]
628                else:
629                    res += [f"  {arg.c_decl},"]
630        if self.throws:
631            res += ["  GError** error"]
632        res += [")"]
633        return utils.code_highlight("\n".join(res))
634
635
636class TemplateClassMethod:
637    def __init__(self, namespace, cls, method):
638        self.name = method.name
639        self.identifier = method.identifier
640        self.class_type_cname = cls.type_struct
641
642        self.throws = method.throws
643
644        if method.doc is not None:
645            self.summary = utils.preprocess_docs(method.doc.content, namespace, summary=True)
646            self.description = utils.preprocess_docs(method.doc.content, namespace)
647            filename = method.doc.filename
648            line = method.doc.line
649            if filename.startswith('../'):
650                filename = filename.replace('../', '')
651            self.docs_location = (filename, line)
652        else:
653            self.description = MISSING_DESCRIPTION
654
655        self.arguments = []
656        for arg in method.parameters:
657            self.arguments.append(TemplateArgument(namespace, method, arg))
658
659        self.return_value = None
660        if not isinstance(method.return_value.target, gir.VoidType):
661            self.return_value = TemplateReturnValue(namespace, method, method.return_value)
662
663        self.stability = method.stability
664        self.attributes = method.attributes
665        self.available_since = method.available_since
666        if method.deprecated_since is not None:
667            (version, msg) = method.deprecated_since
668            self.deprecated_since = {
669                "version": version,
670                "message": utils.preprocess_docs(msg, namespace),
671            }
672        else:
673            self.deprecated_since = None
674
675        if method.source_position is not None:
676            filename, line = method.source_position
677            if filename.startswith('../'):
678                filename = filename.replace('../', '')
679            self.source_location = (filename, line)
680
681        self.introspectable = method.introspectable
682
683    @property
684    def c_decl(self):
685        res = []
686        if self.return_value is None:
687            res += ["void"]
688        else:
689            res += [f"{self.return_value.type_cname}"]
690        res += [f"{self.identifier} ("]
691        n_args = len(self.arguments)
692        if n_args == 1:
693            res += [f"  {self.class_type_cname}* self"]
694        else:
695            res += [f"  {self.class_type_cname}* self,"]
696            for (idx, arg) in enumerate(self.arguments, start=1):
697                if idx == n_args - 1 and not self.throws:
698                    res += [f"  {arg.c_decl}"]
699                else:
700                    res += [f"  {arg.c_decl},"]
701        if self.throws:
702            res += ["  GError** error"]
703        res += [")"]
704        return utils.code_highlight("\n".join(res))
705
706
707class TemplateFunction:
708    def __init__(self, namespace, func):
709        self.identifier = func.identifier
710        self.name = func.name
711        self.namespace = namespace.name
712
713        self.is_macro = isinstance(func, gir.FunctionMacro)
714
715        self.throws = func.throws
716
717        if func.doc is not None:
718            self.summary = utils.preprocess_docs(func.doc.content, namespace, summary=True)
719            self.description = utils.preprocess_docs(func.doc.content, namespace)
720            filename = func.doc.filename
721            line = func.doc.line
722            if filename.startswith('../'):
723                filename = filename.replace('../', '')
724            self.docs_location = (filename, line)
725        else:
726            self.description = MISSING_DESCRIPTION
727
728        self.arguments = []
729        for arg in func.parameters:
730            self.arguments.append(TemplateArgument(namespace, func, arg))
731
732        self.return_value = None
733        if not isinstance(func.return_value.target, gir.VoidType):
734            self.return_value = TemplateReturnValue(namespace, func, func.return_value)
735
736        self.stability = func.stability
737        self.attributes = func.attributes
738        self.available_since = func.available_since
739        if func.deprecated_since is not None:
740            (version, msg) = func.deprecated_since
741            self.deprecated_since = {
742                "version": version,
743                "message": utils.preprocess_docs(msg, namespace),
744            }
745        else:
746            self.deprecated_since = None
747
748        if func.source_position is not None:
749            filename, line = func.source_position
750            if filename.startswith('../'):
751                filename = filename.replace('../', '')
752            self.source_location = (filename, line)
753
754        self.introspectable = func.introspectable
755
756    @property
757    def c_decl(self):
758        res = []
759        if self.is_macro:
760            res += [f"#define {self.identifier} ("]
761        else:
762            if self.return_value is None:
763                res += ["void"]
764            else:
765                res += [f"{self.return_value.type_cname}"]
766            res += [f"{self.identifier} ("]
767        n_args = len(self.arguments)
768        if n_args == 0:
769            res += ["  void"]
770        else:
771            for (idx, arg) in enumerate(self.arguments):
772                if idx == n_args - 1 and not self.throws:
773                    res += [f"  {arg.c_decl}"]
774                else:
775                    res += [f"  {arg.c_decl},"]
776        if self.throws:
777            res += ["  GError** error"]
778        res += [")"]
779        return utils.code_highlight("\n".join(res))
780
781
782class TemplateCallback:
783    def __init__(self, namespace, cb, field=False):
784        self.name = cb.name
785        self.identifier = cb.name.replace("-", "_")
786        self.field = field
787
788        if cb.doc is not None:
789            self.summary = utils.preprocess_docs(cb.doc.content, namespace, summary=True)
790            self.description = utils.preprocess_docs(cb.doc.content, namespace)
791            filename = cb.doc.filename
792            line = cb.doc.line
793            if filename.startswith('../'):
794                filename = filename.replace('../', '')
795            self.docs_location = (filename, line)
796        else:
797            self.description = MISSING_DESCRIPTION
798
799        self.arguments = []
800        for arg in cb.parameters:
801            self.arguments.append(TemplateArgument(namespace, cb, arg))
802
803        self.return_value = None
804        if not isinstance(cb.return_value.target, gir.VoidType):
805            self.return_value = TemplateReturnValue(namespace, cb, cb.return_value)
806
807        self.throws = cb.throws
808
809        self.stability = cb.stability
810        self.attributes = cb.attributes
811        self.available_since = cb.available_since
812        if cb.deprecated_since is not None:
813            (version, msg) = cb.deprecated_since
814            self.deprecated_since = {
815                "version": version,
816                "message": utils.preprocess_docs(msg, namespace),
817            }
818        else:
819            self.deprecated_since = None
820
821        self.introspectable = cb.introspectable
822
823    @property
824    def c_decl(self):
825        res = []
826        if self.field:
827            arg_indent = "    "
828        else:
829            arg_indent = "  "
830        if self.return_value is None:
831            retval = "void"
832        else:
833            retval = f"{self.return_value.type_cname}"
834        if self.field:
835            res += [f"{retval} (* {self.identifier}) ("]
836        else:
837            res += [retval]
838            res += [f"{self.identifier} ("]
839        n_args = len(self.arguments)
840        if n_args == 0:
841            res += ["void"]
842        else:
843            for (idx, arg) in enumerate(self.arguments):
844                if idx == n_args - 1 and not self.throws:
845                    res += [f"{arg_indent}{arg.type_cname} {arg.name}"]
846                else:
847                    res += [f"{arg_indent}{arg.type_cname} {arg.name},"]
848        if self.throws:
849            res += [f"{arg_indent}GError** error"]
850        if self.field:
851            res += ["  )"]
852        else:
853            res += [")"]
854        if self.field:
855            return "\n".join(res)
856        else:
857            return utils.code_highlight("\n".join(res))
858
859
860class TemplateField:
861    def __init__(self, namespace, field):
862        self.name = field.name
863        if field.target is not None:
864            if isinstance(field.target, gir.Callback):
865                self.is_callback = True
866                self.type_name: field.target.name
867                self.type_cname = TemplateCallback(namespace, field.target, field=True).c_decl
868            else:
869                self.is_callback = False
870                self.type_name = field.target.name
871                self.type_cname = field.target.ctype
872        else:
873            self.is_callback = False
874            self.type_name = 'none'
875            self.type_cname = 'gpointer'
876        self.private = field.private
877        if field.doc is not None:
878            self.description = utils.preprocess_docs(field.doc.content, namespace)
879        else:
880            self.description = MISSING_DESCRIPTION
881        self.introspectable = field.introspectable
882
883
884class TemplateInterface:
885    def __init__(self, namespace, interface):
886        if isinstance(interface, gir.Interface):
887            if '.' in interface.name:
888                self.namespace, self.name = interface.name.split('.')
889                self.fqtn = interface.name
890            else:
891                self.namespace = interface.namespace
892                self.name = interface.name
893                self.fqtn = f"{self.namespace}.{self.name}"
894        elif isinstance(interface, gir.Type):
895            if '.' in interface.name:
896                self.namespace, self.name = interface.name.split('.')
897            else:
898                self.namespace = interface.namespace or namespace.name
899                self.name = interface.name
900            self.fqtn = f"{self.namespace}.{self.name}"
901            self.requires = "GObject.Object"
902            self.link_prefix = "iface"
903            self.description = MISSING_DESCRIPTION
904            return
905
906        md = markdown.Markdown(extensions=utils.MD_EXTENSIONS,
907                               extension_configs=utils.MD_EXTENSIONS_CONF)
908
909        requires = interface.prerequisite
910        if requires is None:
911            self.requires_namespace = "GObject"
912            self.requires_name = "Object"
913        elif '.' in requires.name:
914            self.requires_namespace, self.requires_name = requires.name.split('.')
915        else:
916            self.requires_namespace = requires.namespace or namespace.name
917            self.requires_name = requires.name
918
919        self.requires_fqtn = f"{self.requires_namespace}.{self.requires_name}"
920
921        self.symbol_prefix = f"{namespace.symbol_prefix[0]}_{interface.symbol_prefix}"
922        self.type_cname = interface.base_ctype
923
924        self.link_prefix = "iface"
925
926        if interface.doc is not None:
927            self.summary = utils.preprocess_docs(interface.doc.content, namespace, summary=True, md=md)
928            self.description = utils.preprocess_docs(interface.doc.content, namespace, md=md)
929            filename = interface.doc.filename
930            line = interface.doc.line
931            if filename.startswith('../'):
932                filename = filename.replace('../', '')
933            self.docs_location = (filename, line)
934        else:
935            self.description = MISSING_DESCRIPTION
936
937        self.stability = interface.stability
938        self.attributes = interface.attributes
939        self.available_since = interface.available_since
940        if interface.deprecated_since is not None:
941            (version, msg) = interface.deprecated_since
942            self.deprecated_since = {
943                "version": version,
944                "message": utils.preprocess_docs(msg, namespace),
945            }
946        else:
947            self.deprecated_since = None
948
949        self.introspectable = interface.introspectable
950
951        self.class_name = interface.type_struct
952
953        self.class_struct = namespace.find_record(interface.type_struct)
954        if self.class_struct is not None:
955            self.class_fields = []
956            self.class_methods = []
957
958            for field in self.class_struct.fields:
959                if not field.private:
960                    self.class_fields.append(TemplateField(namespace, field))
961
962            for method in self.class_struct.methods:
963                self.class_methods.append(gen_index_func(method, namespace, md))
964
965        if len(interface.properties) != 0:
966            self.properties = []
967            for pname, prop in interface.properties.items():
968                self.properties.append(gen_index_property(prop, namespace, md))
969
970        if len(interface.signals) != 0:
971            self.signals = []
972            for sname, signal in interface.signals.items():
973                self.signals.append(gen_index_signal(signal, namespace, md))
974
975        if len(interface.methods) != 0:
976            self.methods = []
977            for method in interface.methods:
978                self.methods.append(gen_index_func(method, namespace, md))
979
980        if len(interface.virtual_methods) != 0:
981            self.virtual_methods = []
982            for vfunc in interface.virtual_methods:
983                self.virtual_methods.append(gen_index_func(vfunc, namespace, md))
984
985        if len(interface.functions) != 0:
986            self.type_funcs = []
987            for func in interface.functions:
988                self.type_funcs.append(gen_index_func(func, namespace, md))
989
990    @property
991    def c_decl(self):
992        return f"interface {self.fqtn} : {self.requires_fqtn}"
993
994
995class TemplateClass:
996    def __init__(self, namespace, cls, recurse=True):
997        self.symbol_prefix = f"{namespace.symbol_prefix[0]}_{cls.symbol_prefix}"
998        self.type_cname = cls.base_ctype
999        self.link_prefix = "class"
1000        self.fundamental = cls.fundamental
1001        self.abstract = cls.abstract
1002
1003        md = markdown.Markdown(extensions=utils.MD_EXTENSIONS,
1004                               extension_configs=utils.MD_EXTENSIONS_CONF)
1005
1006        if '.' in cls.name:
1007            self.namespace = cls.name.split('.')[0]
1008            self.name = cls.name.split('.')[1]
1009            self.fqtn = cls.name
1010        else:
1011            self.namespace = namespace.name
1012            self.name = cls.name
1013            self.fqtn = f"{namespace.name}.{self.name}"
1014
1015        if cls.parent is None or cls.fundamental:
1016            self.parent_fqtn = 'GObject.TypeInstance'
1017            self.parent_cname = 'GTypeInstance*'
1018            self.parent_name = 'TypeInstance'
1019            self.parent_namespace = 'GObject'
1020        elif '.' in cls.parent.name:
1021            self.parent_fqtn = cls.parent.name
1022            self.parent_cname = cls.parent.ctype
1023            self.parent_namespace = self.parent_fqtn.split('.')[0]
1024            self.parent_name = self.parent_fqtn.split('.')[1]
1025        else:
1026            self.parent_cname = cls.parent.ctype
1027            self.parent_name = cls.parent.name
1028            self.parent_namespace = cls.parent.namespace or namespace.name
1029            self.parent_fqtn = f"{self.parent_namespace}.{self.parent_name}"
1030
1031        if recurse:
1032            self.ancestors = []
1033            for ancestor_type in cls.ancestors:
1034                if '.' in ancestor_type.name:
1035                    ancestor_ns, ancestor_name = ancestor_type.name.split('.')
1036                else:
1037                    ancestor_ns = ancestor_type.namespace or namespace.name
1038                    ancestor_name = ancestor_type.name
1039                ancestor = namespace.find_class(ancestor_name)
1040                # We don't use real Template objects, here, because it can be
1041                # extremely expensive, unless we add a cache somewhere
1042                if ancestor is not None:
1043                    # Set a hard-limit on the number of methods; base types can
1044                    # add *a lot* of them; two dozens feel like a good compromise
1045                    if len(ancestor.methods) < 24:
1046                        methods = [gen_index_func(m, namespace, md) for m in ancestor.methods]
1047                    else:
1048                        methods = []
1049                    self.ancestors.append({
1050                        "namespace": ancestor_ns,
1051                        "name": ancestor_name,
1052                        "fqtn": f"{ancestor_ns}.{ancestor_name}",
1053                        "type_cname": ancestor_type.base_ctype,
1054                        "properties": [gen_index_property(p, namespace, md) for p in ancestor.properties.values()],
1055                        "n_properties": len(ancestor.properties),
1056                        "signals": [gen_index_signal(s, namespace, md) for s in ancestor.signals.values()],
1057                        "n_signals": len(ancestor.signals),
1058                        "methods": methods,
1059                        "n_methods": len(ancestor.methods),
1060                    })
1061                else:
1062                    self.ancestors.append({
1063                        "namespace": ancestor_ns,
1064                        "name": ancestor_name,
1065                        "fqtn": f"{ancestor_ns}.{ancestor_name}",
1066                        "type_cname": ancestor_type.base_ctype,
1067                        "properties": [],
1068                        "n_properties": 0,
1069                        "signals": [],
1070                        "n_signals": 0,
1071                        "methods": [],
1072                        "n_methods": 0,
1073                    })
1074
1075        self.class_name = cls.type_struct
1076
1077        self.instance_struct = None
1078        if len(cls.fields) != 0:
1079            self.instance_struct = self.class_name
1080
1081        if cls.type_struct is not None:
1082            self.class_struct = namespace.find_record(cls.type_struct)
1083        else:
1084            self.class_struct = None
1085
1086        # "Final", in the absence of an actual flag or annotation,
1087        # is determined through an heuristic; if either the instance
1088        # or the class structures are missing or disguised, then the
1089        # type cannot be derived
1090        if self.instance_struct is None or self.class_struct is None or self.class_struct.disguised:
1091            self.final = True
1092        else:
1093            self.final = False
1094
1095        if cls.doc is not None:
1096            self.summary = utils.preprocess_docs(cls.doc.content, namespace, summary=True, md=md)
1097            self.description = utils.preprocess_docs(cls.doc.content, namespace, md=md)
1098            filename = cls.doc.filename
1099            line = cls.doc.line
1100            if filename.startswith('../'):
1101                filename = filename.replace('../', '')
1102            self.docs_location = (filename, line)
1103        else:
1104            self.description = MISSING_DESCRIPTION
1105
1106        self.stability = cls.stability
1107        self.attributes = cls.attributes
1108        self.available_since = cls.available_since
1109        if cls.deprecated_since is not None:
1110            (version, msg) = cls.deprecated_since
1111            self.deprecated_since = {
1112                "version": version,
1113                "message": utils.preprocess_docs(msg, namespace, md=md),
1114            }
1115        else:
1116            self.deprecated_since = None
1117
1118        self.introspectable = cls.introspectable
1119
1120        self.fields = []
1121        for field in cls.fields:
1122            if not field.private:
1123                self.fields.append(TemplateField(namespace, field))
1124
1125        if len(cls.properties) != 0:
1126            self.properties = []
1127            for pname, prop in cls.properties.items():
1128                self.properties.append(gen_index_property(prop, namespace, md))
1129
1130        if len(cls.signals) != 0:
1131            self.signals = []
1132            for sname, signal in cls.signals.items():
1133                self.signals.append(gen_index_signal(signal, namespace, md))
1134
1135        if len(cls.constructors) != 0:
1136            self.ctors = []
1137            for ctor in cls.constructors:
1138                self.ctors.append(gen_index_func(ctor, namespace, md))
1139
1140        if len(cls.methods) != 0:
1141            self.methods = []
1142            for method in cls.methods:
1143                self.methods.append(gen_index_func(method, namespace, md))
1144
1145        if self.class_struct is not None:
1146            self.class_ctype = self.class_struct.ctype
1147            self.class_fields = []
1148            self.class_methods = []
1149
1150            for field in self.class_struct.fields:
1151                if not field.private:
1152                    self.class_fields.append(TemplateField(namespace, field))
1153
1154            for method in self.class_struct.methods:
1155                self.class_methods.append(gen_index_func(method, namespace, md))
1156
1157        if len(cls.implements) != 0:
1158            self.interfaces = []
1159            for iface_type in cls.implements:
1160                if '.' in iface_type.name:
1161                    iface_ns, iface_name = iface_type.name.split('.')
1162                else:
1163                    iface_ns = iface_type.namespace or namespace.name
1164                    iface_name = iface_type.name
1165                iface = namespace.find_interface(iface_name)
1166                if iface is not None:
1167                    # Set a hard-limit on the number of methods; base types can
1168                    # add *a lot* of them; two dozens feel like a good compromise
1169                    if len(iface.methods) < 24:
1170                        methods = [gen_index_func(m, namespace, md) for m in iface.methods]
1171                    else:
1172                        methods = []
1173                    self.interfaces.append({
1174                        "namespace": iface_ns,
1175                        "name": iface_name,
1176                        "fqtn": f"{iface_ns}.{iface_name}",
1177                        "type_cname": iface_type.base_ctype,
1178                        "properties": [gen_index_property(p, namespace, md) for p in iface.properties.values()],
1179                        "n_properties": len(iface.properties),
1180                        "signals": [gen_index_signal(s, namespace, md) for s in iface.signals.values()],
1181                        "n_signals": len(iface.signals),
1182                        "methods": methods,
1183                        "n_methods": len(iface.methods),
1184                    })
1185                else:
1186                    self.interfaces.append({
1187                        "namespace": iface_ns,
1188                        "name": iface_name,
1189                        "fqtn": f"{iface_ns}.{iface_name}",
1190                        "type_cname": iface_type.base_ctype,
1191                        "properties": [],
1192                        "n_properties": 0,
1193                        "signals": [],
1194                        "n_signals": 0,
1195                        "methods": [],
1196                        "n_methods": 0,
1197                    })
1198
1199        if len(cls.virtual_methods) != 0:
1200            self.virtual_methods = []
1201            for vfunc in cls.virtual_methods:
1202                self.virtual_methods.append(gen_index_func(vfunc, namespace, md))
1203
1204        if len(cls.functions) != 0:
1205            self.type_funcs = []
1206            for func in cls.functions:
1207                self.type_funcs.append(gen_index_func(func, namespace, md))
1208
1209    @property
1210    def c_decl(self):
1211        if self.abstract:
1212            res = [f"abstract class {self.fqtn} : {self.parent_fqtn} {{"]
1213        elif self.final:
1214            res = [f"final class {self.fqtn} : {self.parent_fqtn} {{"]
1215        else:
1216            res = [f"class {self.fqtn} : {self.parent_fqtn} {{"]
1217        n_fields = len(self.fields)
1218        if n_fields > 0:
1219            for (idx, field) in enumerate(self.fields):
1220                if idx < n_fields - 1:
1221                    res += [f"  {field.name}: {field.type_cname},"]
1222                else:
1223                    res += [f"  {field.name}: {field.type_cname}"]
1224        else:
1225            res += ["  /* No available fields */"]
1226        res += ["}"]
1227        return "\n".join(res)
1228
1229    @property
1230    def dot(self):
1231
1232        def fmt_attrs(attrs):
1233            return ','.join(f'{k}="{v}"' for k, v in attrs.items())
1234
1235        def add_link(attrs, other):
1236            if other['namespace'] == self.namespace:
1237                attrs['href'] = f"class.{other['name']}.html"
1238                attrs['class'] = 'link'
1239            else:
1240                attrs['tooltip'] = other['fqtn']
1241
1242        ancestors = []
1243        implements = []
1244        res = ["graph hierarchy {"]
1245        res.append("  bgcolor=\"transparent\";")
1246        node_attrs = {
1247            'shape': 'box',
1248            'style': 'rounded',
1249            'border': 0
1250        }
1251        this_attrs = {
1252            'label': self.type_cname,
1253            'tooltip': self.type_cname
1254        }
1255        this_attrs.update(node_attrs)
1256        res.append(f"  this [{fmt_attrs(this_attrs)}];")
1257        for idx, ancestor in enumerate(self.ancestors):
1258            node_id = f"ancestor_{idx}"
1259            ancestor_attrs = {
1260                'label': ancestor['type_cname']
1261            }
1262            ancestor_attrs.update(node_attrs)
1263            add_link(ancestor_attrs, ancestor)
1264            res.append(f"  {node_id} [{fmt_attrs(ancestor_attrs)}];")
1265            ancestors.append(node_id)
1266        ancestors.reverse()
1267        for idx, iface in enumerate(getattr(self, "interfaces", [])):
1268            node_id = f"implements_{idx}"
1269            iface_attrs = {
1270                'label': iface['type_cname'],
1271                'fontname': 'sans-serif',
1272                'shape': 'box',
1273            }
1274            add_link(iface_attrs, iface)
1275            res.append(f"  {node_id} [{fmt_attrs(iface_attrs)}];")
1276            implements.append(node_id)
1277        if len(ancestors) > 0:
1278            res.append("  " + " -- ".join(ancestors) + " -- this;")
1279        for node in implements:
1280            res.append(f"  this -- {node} [style=dotted];")
1281        res.append("}")
1282        return "\n".join(res)
1283
1284
1285class TemplateRecord:
1286    def __init__(self, namespace, record):
1287        self.symbol_prefix = f"{namespace.symbol_prefix[0]}_{record.symbol_prefix}"
1288        self.type_cname = record.ctype
1289        self.link_prefix = "struct"
1290
1291        self.name = record.name
1292        self.namespace = record.name or namespace.name
1293        self.fqtn = f"{self.namespace}.{self.name}"
1294
1295        md = markdown.Markdown(extensions=utils.MD_EXTENSIONS,
1296                               extension_configs=utils.MD_EXTENSIONS_CONF)
1297
1298        if record.doc is not None:
1299            self.summary = utils.preprocess_docs(record.doc.content, namespace, summary=True, md=md)
1300            self.description = utils.preprocess_docs(record.doc.content, namespace, md=md)
1301            filename = record.doc.filename
1302            line = record.doc.line
1303            if filename.startswith('../'):
1304                filename = filename.replace('../', '')
1305            self.docs_location = (filename, line)
1306        else:
1307            self.description = MISSING_DESCRIPTION
1308
1309        self.stability = record.stability
1310        self.attributes = record.attributes
1311        self.available_since = record.available_since
1312        if record.deprecated_since is not None:
1313            (version, msg) = record.deprecated_since
1314            self.deprecated_since = {
1315                "version": version,
1316                "message": utils.preprocess_docs(msg, namespace, md=md),
1317            }
1318        else:
1319            self.deprecated_since = None
1320
1321        self.introspectable = record.introspectable
1322
1323        self.fields = []
1324        for field in record.fields:
1325            if not field.private:
1326                self.fields.append(TemplateField(namespace, field))
1327
1328        if len(record.constructors) != 0:
1329            self.ctors = []
1330            for ctor in record.constructors:
1331                self.ctors.append(gen_index_func(ctor, namespace, md))
1332
1333        if len(record.methods) != 0:
1334            self.methods = []
1335            for method in record.methods:
1336                self.methods.append(gen_index_func(method, namespace, md))
1337
1338        if len(record.functions) != 0:
1339            self.type_funcs = []
1340            for func in record.functions:
1341                self.type_funcs.append(gen_index_func(func, namespace, md))
1342
1343    @property
1344    def c_decl(self):
1345        res = [f"struct {self.type_cname} {{"]
1346        n_fields = len(self.fields)
1347        if n_fields > 0:
1348            for field in self.fields:
1349                if field.is_callback:
1350                    res += [f"  {field.type_cname};"]
1351                else:
1352                    res += [f"  {field.type_cname} {field.name};"]
1353        else:
1354            res += ["  /* No available fields */"]
1355        res += ["}"]
1356        return utils.code_highlight("\n".join(res))
1357
1358
1359class TemplateUnion:
1360    def __init__(self, namespace, union):
1361        self.symbol_prefix = f"{namespace.symbol_prefix[0]}_{union.symbol_prefix}"
1362        self.type_cname = union.ctype
1363        self.link_prefix = "union"
1364        self.name = union.name
1365        self.namespace = union.namespace or namespace.name
1366        self.fqtn = f"{self.namespace}.{self.name}"
1367
1368        md = markdown.Markdown(extensions=utils.MD_EXTENSIONS,
1369                               extension_configs=utils.MD_EXTENSIONS_CONF)
1370
1371        if union.doc is not None:
1372            self.summary = utils.preprocess_docs(union.doc.content, namespace, summary=True, md=md)
1373            self.description = utils.preprocess_docs(union.doc.content, namespace, md=md)
1374            filename = union.doc.filename
1375            line = union.doc.line
1376            if filename.startswith('../'):
1377                filename = filename.replace('../', '')
1378            self.docs_location = (filename, line)
1379        else:
1380            self.description = MISSING_DESCRIPTION
1381
1382        self.stability = union.stability
1383        self.attributes = union.attributes
1384        self.available_since = union.available_since
1385        if union.deprecated_since is not None:
1386            (version, msg) = union.deprecated_since
1387            self.deprecated_since = {
1388                "version": version,
1389                "message": utils.preprocess_docs(msg, namespace, md=md),
1390            }
1391        else:
1392            self.deprecated_since = None
1393
1394        self.introspectable = union.introspectable
1395
1396        self.fields = []
1397        for field in union.fields:
1398            if not field.private:
1399                self.fields.append(TemplateField(namespace, field))
1400
1401        if len(union.constructors) != 0:
1402            self.ctors = []
1403            for ctor in union.constructors:
1404                self.ctors.append(gen_index_func(ctor, namespace, md))
1405
1406        if len(union.methods) != 0:
1407            self.methods = []
1408            for method in union.methods:
1409                self.methods.append(gen_index_func(method, namespace, md))
1410
1411        if len(union.functions) != 0:
1412            self.type_funcs = []
1413            for func in union.functions:
1414                self.type_funcs.append(gen_index_func(func, namespace, md))
1415
1416    @property
1417    def c_decl(self):
1418        res = [f"union {self.type_cname} {{"]
1419        n_fields = len(self.fields)
1420        if n_fields > 0:
1421            for field in self.fields:
1422                if field.is_callback:
1423                    res += [f"  {field.type_cname};"]
1424                else:
1425                    res += [f"  {field.type_cname} {field.name};"]
1426        else:
1427            res += ["  /* No available fields */"]
1428        res += ["}"]
1429        return utils.code_highlight("\n".join(res))
1430
1431
1432class TemplateAlias:
1433    def __init__(self, namespace, alias):
1434        self.type_cname = alias.base_ctype
1435        self.target_ctype = alias.target.ctype
1436        self.link_prefix = "alias"
1437
1438        self.namespace = alias.namespace or namespace.name
1439        self.name = alias.name
1440        self.fqtn = f"{self.namespace}.{self.name}"
1441
1442        md = markdown.Markdown(extensions=utils.MD_EXTENSIONS,
1443                               extension_configs=utils.MD_EXTENSIONS_CONF)
1444
1445        if alias.doc is not None:
1446            self.summary = utils.preprocess_docs(alias.doc.content, namespace, summary=True, md=md)
1447            self.description = utils.preprocess_docs(alias.doc.content, namespace, md=md)
1448            filename = alias.doc.filename
1449            line = alias.doc.line
1450            if filename.startswith('../'):
1451                filename = filename.replace('../', '')
1452            self.docs_location = (filename, line)
1453        else:
1454            self.description = MISSING_DESCRIPTION
1455
1456        self.stability = alias.stability
1457        self.attributes = alias.attributes
1458        self.available_since = alias.available_since
1459        self.deprecated_since = alias.deprecated_since
1460        if alias.deprecated_since is not None:
1461            (version, msg) = alias.deprecated_since
1462            self.deprecated_since = {
1463                "version": version,
1464                "message": utils.preprocess_docs(msg, namespace),
1465            }
1466        else:
1467            self.deprecated_since = None
1468
1469        self.introspectable = alias.introspectable
1470
1471    @property
1472    def c_decl(self):
1473        return f"typedef {self.target_ctype} {self.type_cname}"
1474
1475
1476class TemplateMember:
1477    def __init__(self, namespace, enum, member):
1478        self.name = member.identifier
1479        self.nick = member.nick
1480        self.value = member.value
1481        if member.doc is not None:
1482            self.description = utils.preprocess_docs(member.doc.content, namespace)
1483            filename = member.doc.filename
1484            line = member.doc.line
1485            if filename.startswith('../'):
1486                filename = filename.replace('../', '')
1487            self.docs_location = (filename, line)
1488        else:
1489            self.description = MISSING_DESCRIPTION
1490
1491
1492class TemplateEnum:
1493    def __init__(self, namespace, enum):
1494        self.symbol_prefix = None
1495        self.type_cname = enum.ctype
1496        self.bitfield = False
1497        self.error = False
1498        self.domain = None
1499
1500        self.namespace = namespace.name
1501        self.name = enum.name
1502        self.fqtn = f"{namespace.name}.{enum.name}"
1503
1504        md = markdown.Markdown(extensions=utils.MD_EXTENSIONS,
1505                               extension_configs=utils.MD_EXTENSIONS_CONF)
1506
1507        if enum.doc is not None:
1508            self.summary = utils.preprocess_docs(enum.doc.content, namespace, summary=True, md=md)
1509            self.description = utils.preprocess_docs(enum.doc.content, namespace, md=md)
1510            filename = enum.doc.filename
1511            line = enum.doc.line
1512            if filename.startswith('../'):
1513                filename = filename.replace('../', '')
1514            self.docs_location = (filename, line)
1515        else:
1516            self.description = MISSING_DESCRIPTION
1517
1518        self.stability = enum.stability
1519        self.attributes = enum.attributes
1520        self.available_since = enum.available_since
1521        self.deprecated_since = enum.deprecated_since
1522        if enum.deprecated_since is not None:
1523            (version, msg) = enum.deprecated_since
1524            self.deprecated_since = {
1525                "version": version,
1526                "message": utils.preprocess_docs(msg, namespace, md=md),
1527            }
1528        else:
1529            self.deprecated_since = None
1530
1531        self.introspectable = enum.introspectable
1532
1533        if isinstance(enum, gir.BitField):
1534            self.link_prefix = "flags"
1535            self.bitfield = True
1536        elif isinstance(enum, gir.ErrorDomain):
1537            self.link_prefix = "error"
1538            self.error = True
1539            self.domain = enum.domain
1540        else:
1541            self.link_prefix = "enum"
1542
1543        if len(enum.members) != 0:
1544            self.members = []
1545            for member in enum.members:
1546                self.members.append(TemplateMember(namespace, enum, member))
1547
1548        if len(enum.functions) != 0:
1549            self.type_funcs = []
1550            for func in enum.functions:
1551                self.type_funcs.append(gen_index_func(func, namespace, md))
1552
1553    @property
1554    def c_decl(self):
1555        if self.error:
1556            return f"error-domain {self.fqtn}"
1557        elif self.bitfield:
1558            return f"flags {self.fqtn}"
1559        else:
1560            return f"enum {self.fqtn}"
1561
1562
1563class TemplateNamespace:
1564    def __init__(self, namespace):
1565        self.name = namespace.name
1566        self.version = namespace.version
1567        self.symbol_prefix = namespace.symbol_prefix[0]
1568        self.identifier_prefix = namespace.identifier_prefix[0]
1569
1570
1571def _gen_classes(config, theme_config, output_dir, jinja_env, repository, all_classes):
1572    namespace = repository.namespace
1573
1574    class_tmpl = jinja_env.get_template(theme_config.class_template)
1575    method_tmpl = jinja_env.get_template(theme_config.method_template)
1576    property_tmpl = jinja_env.get_template(theme_config.property_template)
1577    signal_tmpl = jinja_env.get_template(theme_config.signal_template)
1578    class_method_tmpl = jinja_env.get_template(theme_config.class_method_template)
1579    ctor_tmpl = jinja_env.get_template(theme_config.ctor_template)
1580    type_func_tmpl = jinja_env.get_template(theme_config.type_func_template)
1581    vfunc_tmpl = jinja_env.get_template(theme_config.vfunc_template)
1582
1583    template_classes = []
1584
1585    for cls in all_classes:
1586        if config.is_hidden(cls.name):
1587            log.debug(f"Skipping hidden class {cls.name}")
1588            continue
1589        class_file = os.path.join(output_dir, f"class.{cls.name}.html")
1590        log.info(f"Creating class file for {namespace.name}.{cls.name}: {class_file}")
1591
1592        tmpl = TemplateClass(namespace, cls)
1593        template_classes.append(tmpl)
1594
1595        if config.show_class_hierarchy and utils.find_program('dot') is not None:
1596            tmpl.hierarchy_svg = utils.render_dot(tmpl.dot, output_format="svg")
1597
1598        with open(class_file, "w") as out:
1599            content = class_tmpl.render({
1600                'CONFIG': config,
1601                'namespace': namespace,
1602                'class': tmpl,
1603            })
1604
1605            out.write(content)
1606
1607        for ctor in cls.constructors:
1608            c = TemplateFunction(namespace, ctor)
1609            ctor_file = os.path.join(output_dir, f"ctor.{cls.name}.{ctor.name}.html")
1610            log.debug(f"Creating ctor file for {namespace.name}.{cls.name}.{ctor.name}: {ctor_file}")
1611
1612            with open(ctor_file, "w") as out:
1613                out.write(ctor_tmpl.render({
1614                    'CONFIG': config,
1615                    'namespace': namespace,
1616                    'class': tmpl,
1617                    'type_func': c,
1618                }))
1619
1620        for method in cls.methods:
1621            m = TemplateMethod(namespace, cls, method)
1622            method_file = os.path.join(output_dir, f"method.{cls.name}.{method.name}.html")
1623            log.debug(f"Creating method file for {namespace.name}.{cls.name}.{method.name}: {method_file}")
1624
1625            with open(method_file, "w") as out:
1626                out.write(method_tmpl.render({
1627                    'CONFIG': config,
1628                    'namespace': namespace,
1629                    'class': tmpl,
1630                    'method': m,
1631                }))
1632
1633        for prop in cls.properties.values():
1634            if config.is_hidden(cls.name, 'property', prop.name):
1635                log.debug(f"Skipping hidden property {cls.name}.{prop.name}")
1636                continue
1637            p = TemplateProperty(namespace, cls, prop)
1638            prop_file = os.path.join(output_dir, f"property.{cls.name}.{prop.name}.html")
1639            log.debug(f"Creating property file for {namespace.name}.{cls.name}.{prop.name}: {prop_file}")
1640
1641            with open(prop_file, "w") as out:
1642                out.write(property_tmpl.render({
1643                    'CONFIG': config,
1644                    'namespace': namespace,
1645                    'class': tmpl,
1646                    'property': p,
1647                }))
1648
1649        for signal in cls.signals.values():
1650            if config.is_hidden(cls.name, 'signal', signal.name):
1651                log.debug(f"Skipping hidden signal {cls.name}.{signal.name}")
1652                continue
1653            s = TemplateSignal(namespace, cls, signal)
1654            signal_file = os.path.join(output_dir, f"signal.{cls.name}.{signal.name}.html")
1655            log.debug(f"Creating signal file for {namespace.name}.{cls.name}.{signal.name}: {signal_file}")
1656
1657            with open(signal_file, "w") as out:
1658                out.write(signal_tmpl.render({
1659                    'CONFIG': config,
1660                    'namespace': namespace,
1661                    'class': tmpl,
1662                    'signal': s,
1663                }))
1664
1665        if cls.type_struct is not None:
1666            class_struct = namespace.find_record(cls.type_struct)
1667            for cls_method in class_struct.methods:
1668                c = TemplateClassMethod(namespace, cls, cls_method)
1669                cls_method_file = os.path.join(output_dir, f"class_method.{cls.name}.{cls_method.name}.html")
1670                log.debug(f"Creating class method file for {namespace.name}.{cls.name}.{cls_method.name}: {cls_method_file}")
1671
1672                with open(cls_method_file, "w") as out:
1673                    out.write(class_method_tmpl.render({
1674                        'CONFIG': config,
1675                        'namespace': namespace,
1676                        'class': tmpl,
1677                        'class_method': c,
1678                    }))
1679
1680        for vfunc in cls.virtual_methods:
1681            f = TemplateMethod(namespace, cls, vfunc)
1682            vfunc_file = os.path.join(output_dir, f"vfunc.{cls.name}.{vfunc.name}.html")
1683            log.debug(f"Creating vfunc file for {namespace.name}.{cls.name}.{vfunc.name}: {vfunc_file}")
1684
1685            with open(vfunc_file, "w") as out:
1686                out.write(vfunc_tmpl.render({
1687                    'CONFIG': config,
1688                    'namespace': namespace,
1689                    'class': tmpl,
1690                    'vfunc': f,
1691                }))
1692
1693        for type_func in cls.functions:
1694            f = TemplateFunction(namespace, type_func)
1695            type_func_file = os.path.join(output_dir, f"type_func.{cls.name}.{type_func.name}.html")
1696            log.debug(f"Creating type func file for {namespace.name}.{cls.name}.{type_func.name}: {type_func_file}")
1697
1698            with open(type_func_file, "w") as out:
1699                out.write(type_func_tmpl.render({
1700                    'CONFIG': config,
1701                    'namespace': namespace,
1702                    'class': tmpl,
1703                    'type_func': f,
1704                }))
1705
1706    return template_classes
1707
1708
1709def _gen_interfaces(config, theme_config, output_dir, jinja_env, repository, all_interfaces):
1710    namespace = repository.namespace
1711
1712    iface_tmpl = jinja_env.get_template(theme_config.interface_template)
1713    method_tmpl = jinja_env.get_template(theme_config.method_template)
1714    property_tmpl = jinja_env.get_template(theme_config.property_template)
1715    signal_tmpl = jinja_env.get_template(theme_config.signal_template)
1716    class_method_tmpl = jinja_env.get_template(theme_config.class_method_template)
1717    type_func_tmpl = jinja_env.get_template(theme_config.type_func_template)
1718    vfunc_tmpl = jinja_env.get_template(theme_config.vfunc_template)
1719
1720    template_interfaces = []
1721
1722    for iface in all_interfaces:
1723        if config.is_hidden(iface.name):
1724            log.debug(f"Skipping hidden interface {iface.name}")
1725            continue
1726        iface_file = os.path.join(output_dir, f"iface.{iface.name}.html")
1727        log.info(f"Creating interface file for {namespace.name}.{iface.name}: {iface_file}")
1728
1729        tmpl = TemplateInterface(namespace, iface)
1730        template_interfaces.append(tmpl)
1731
1732        with open(iface_file, "w") as out:
1733            out.write(iface_tmpl.render({
1734                'CONFIG': config,
1735                'namespace': namespace,
1736                'interface': tmpl,
1737            }))
1738
1739        for method in iface.methods:
1740            m = TemplateMethod(namespace, iface, method)
1741            method_file = os.path.join(output_dir, f"method.{iface.name}.{method.name}.html")
1742            log.debug(f"Creating method file for {namespace.name}.{iface.name}.{method.name}: {method_file}")
1743
1744            with open(method_file, "w") as out:
1745                out.write(method_tmpl.render({
1746                    'CONFIG': config,
1747                    'namespace': namespace,
1748                    'class': tmpl,
1749                    'method': m,
1750                }))
1751
1752        for prop in iface.properties.values():
1753            if config.is_hidden(iface.name, 'property', prop.name):
1754                log.debug(f"Skipping hidden property {iface.name}.{prop.name}")
1755                continue
1756            p = TemplateProperty(namespace, iface, prop)
1757            prop_file = os.path.join(output_dir, f"property.{iface.name}.{prop.name}.html")
1758            log.debug(f"Creating property file for {namespace.name}.{iface.name}.{prop.name}: {prop_file}")
1759
1760            with open(prop_file, "w") as out:
1761                out.write(property_tmpl.render({
1762                    'CONFIG': config,
1763                    'namespace': namespace,
1764                    'class': tmpl,
1765                    'property': p,
1766                }))
1767
1768        for signal in iface.signals.values():
1769            if config.is_hidden(iface.name, 'signal', signal.name):
1770                log.debug(f"Skipping hidden property {iface.name}.{signal.name}")
1771                continue
1772            s = TemplateSignal(namespace, iface, signal)
1773            signal_file = os.path.join(output_dir, f"signal.{iface.name}.{signal.name}.html")
1774            log.debug(f"Creating signal file for {namespace.name}.{iface.name}.{signal.name}: {signal_file}")
1775
1776            with open(signal_file, "w") as out:
1777                out.write(signal_tmpl.render({
1778                    'CONFIG': config,
1779                    'namespace': namespace,
1780                    'class': tmpl,
1781                    'signal': s,
1782                }))
1783
1784        for vfunc in iface.virtual_methods:
1785            v = TemplateMethod(namespace, iface, vfunc)
1786            vfunc_file = os.path.join(output_dir, f"vfunc.{iface.name}.{vfunc.name}.html")
1787            log.debug(f"Creating vfunc file for {namespace.name}.{iface.name}.{vfunc.name}: {vfunc_file}")
1788
1789            with open(vfunc_file, "w") as out:
1790                out.write(vfunc_tmpl.render({
1791                    'CONFIG': config,
1792                    'namespace': namespace,
1793                    'class': tmpl,
1794                    'vfunc': v,
1795                }))
1796
1797        if iface.type_struct is not None:
1798            iface_struct = namespace.find_record(iface.type_struct)
1799            for cls_method in iface_struct.methods:
1800                m = TemplateClassMethod(namespace, iface, cls_method)
1801                cls_method_file = os.path.join(output_dir, f"class_method.{iface.name}.{cls_method.name}.html")
1802                log.debug(f"Creating class method file for {namespace.name}.{iface.name}.{cls_method.name}: {cls_method_file}")
1803
1804                with open(cls_method_file, "w") as out:
1805                    out.write(class_method_tmpl.render({
1806                        'CONFIG': config,
1807                        'namespace': namespace,
1808                        'class': tmpl,
1809                        'class_method': m,
1810                    }))
1811
1812        for type_func in iface.functions:
1813            f = TemplateFunction(namespace, type_func)
1814            type_func_file = os.path.join(output_dir, f"type_func.{iface.name}.{type_func.name}.html")
1815            log.debug(f"Creating type func file for {namespace.name}.{iface.name}.{type_func.name}: {type_func_file}")
1816
1817            with open(type_func_file, "w") as out:
1818                out.write(type_func_tmpl.render({
1819                    'CONFIG': config,
1820                    'namespace': namespace,
1821                    'class': tmpl,
1822                    'type_func': f,
1823                }))
1824
1825    return template_interfaces
1826
1827
1828def _gen_enums(config, theme_config, output_dir, jinja_env, repository, all_enums):
1829    namespace = repository.namespace
1830
1831    enum_tmpl = jinja_env.get_template(theme_config.enum_template)
1832    type_func_tmpl = jinja_env.get_template(theme_config.type_func_template)
1833
1834    template_enums = []
1835
1836    for enum in all_enums:
1837        if config.is_hidden(enum.name):
1838            log.debug(f"Skipping hidden enum {enum.name}")
1839            continue
1840        enum_file = os.path.join(output_dir, f"enum.{enum.name}.html")
1841        log.info(f"Creating enum file for {namespace.name}.{enum.name}: {enum_file}")
1842
1843        tmpl = TemplateEnum(namespace, enum)
1844        template_enums.append(tmpl)
1845
1846        with open(enum_file, "w") as out:
1847            out.write(enum_tmpl.render({
1848                'CONFIG': config,
1849                'namespace': namespace,
1850                'enum': tmpl,
1851            }))
1852
1853        for type_func in enum.functions:
1854            f = TemplateFunction(namespace, type_func)
1855            type_func_file = os.path.join(output_dir, f"type_func.{enum.name}.{type_func.name}.html")
1856            log.debug(f"Creating type func file for {namespace.name}.{enum.name}.{type_func.name}: {type_func_file}")
1857
1858            with open(type_func_file, "w") as out:
1859                out.write(type_func_tmpl.render({
1860                    'CONFIG': config,
1861                    'namespace': namespace,
1862                    'class': tmpl,
1863                    'type_func': f,
1864                }))
1865
1866    return template_enums
1867
1868
1869def _gen_bitfields(config, theme_config, output_dir, jinja_env, repository, all_enums):
1870    namespace = repository.namespace
1871
1872    enum_tmpl = jinja_env.get_template(theme_config.flags_template)
1873    type_func_tmpl = jinja_env.get_template(theme_config.type_func_template)
1874
1875    template_bitfields = []
1876
1877    for enum in all_enums:
1878        if config.is_hidden(enum.name):
1879            log.debug(f"Skipping hidden bitfield {enum.name}")
1880            continue
1881        enum_file = os.path.join(output_dir, f"flags.{enum.name}.html")
1882        log.info(f"Creating enum file for {namespace.name}.{enum.name}: {enum_file}")
1883
1884        tmpl = TemplateEnum(namespace, enum)
1885        template_bitfields.append(tmpl)
1886
1887        with open(enum_file, "w") as out:
1888            out.write(enum_tmpl.render({
1889                'CONFIG': config,
1890                'namespace': namespace,
1891                'enum': tmpl,
1892            }))
1893
1894        for type_func in enum.functions:
1895            f = TemplateFunction(namespace, type_func)
1896            type_func_file = os.path.join(output_dir, f"type_func.{enum.name}.{type_func.name}.html")
1897            log.debug(f"Creating type func file for {namespace.name}.{enum.name}.{type_func.name}: {type_func_file}")
1898
1899            with open(type_func_file, "w") as out:
1900                out.write(type_func_tmpl.render({
1901                    'CONFIG': config,
1902                    'namespace': namespace,
1903                    'class': tmpl,
1904                    'type_func': f,
1905                }))
1906
1907    return template_bitfields
1908
1909
1910def _gen_domains(config, theme_config, output_dir, jinja_env, repository, all_enums):
1911    namespace = repository.namespace
1912
1913    enum_tmpl = jinja_env.get_template(theme_config.error_template)
1914    type_func_tmpl = jinja_env.get_template(theme_config.type_func_template)
1915
1916    template_domains = []
1917
1918    for enum in all_enums:
1919        if config.is_hidden(enum.name):
1920            log.debug(f"Skipping hidden domain {enum.name}")
1921            continue
1922        enum_file = os.path.join(output_dir, f"error.{enum.name}.html")
1923        log.info(f"Creating enum file for {namespace.name}.{enum.name}: {enum_file}")
1924
1925        tmpl = TemplateEnum(namespace, enum)
1926        template_domains.append(tmpl)
1927
1928        with open(enum_file, "w") as out:
1929            out.write(enum_tmpl.render({
1930                'CONFIG': config,
1931                'namespace': namespace,
1932                'enum': tmpl,
1933            }))
1934
1935        for type_func in enum.functions:
1936            f = TemplateFunction(namespace, type_func)
1937            type_func_file = os.path.join(output_dir, f"type_func.{enum.name}.{type_func.name}.html")
1938            log.debug(f"Creating type func file for {namespace.name}.{enum.name}.{type_func.name}: {type_func_file}")
1939
1940            with open(type_func_file, "w") as out:
1941                out.write(type_func_tmpl.render({
1942                    'CONFIG': config,
1943                    'namespace': namespace,
1944                    'class': tmpl,
1945                    'type_func': f,
1946                }))
1947
1948    return template_domains
1949
1950
1951def _gen_constants(config, theme_config, output_dir, jinja_env, repository, all_constants):
1952    namespace = repository.namespace
1953
1954    const_tmpl = jinja_env.get_template(theme_config.constant_template)
1955
1956    template_constants = []
1957
1958    for const in all_constants:
1959        if config.is_hidden(const.name):
1960            log.debug(f"Skipping hidden constant {const.name}")
1961            continue
1962        const_file = os.path.join(output_dir, f"const.{const.name}.html")
1963        log.info(f"Creating constant file for {namespace.name}.{const.name}: {const_file}")
1964
1965        tmpl = TemplateConstant(namespace, const)
1966        template_constants.append(tmpl)
1967
1968        with open(const_file, "w") as out:
1969            out.write(const_tmpl.render({
1970                'CONFIG': config,
1971                'namespace': namespace,
1972                'constant': tmpl,
1973            }))
1974
1975    return template_constants
1976
1977
1978def _gen_aliases(config, theme_config, output_dir, jinja_env, repository, all_aliases):
1979    namespace = repository.namespace
1980
1981    alias_tmpl = jinja_env.get_template(theme_config.alias_template)
1982
1983    template_aliases = []
1984
1985    for alias in all_aliases:
1986        if config.is_hidden(alias.name):
1987            log.debug(f"Skipping hidden alias {alias.name}")
1988            continue
1989        alias_file = os.path.join(output_dir, f"alias.{alias.name}.html")
1990        log.info(f"Creating alias file for {namespace.name}.{alias.name}: {alias_file}")
1991
1992        tmpl = TemplateAlias(namespace, alias)
1993        template_aliases.append(tmpl)
1994
1995        with open(alias_file, "w") as out:
1996            content = alias_tmpl.render({
1997                'CONFIG': config,
1998                'namespace': namespace,
1999                'struct': tmpl,
2000            })
2001
2002            out.write(content)
2003
2004    return template_aliases
2005
2006
2007def _gen_records(config, theme_config, output_dir, jinja_env, repository, all_records):
2008    namespace = repository.namespace
2009
2010    record_tmpl = jinja_env.get_template(theme_config.record_template)
2011    method_tmpl = jinja_env.get_template(theme_config.method_template)
2012    type_func_tmpl = jinja_env.get_template(theme_config.type_func_template)
2013
2014    template_records = []
2015
2016    for record in all_records:
2017        if config.is_hidden(record.name):
2018            log.debug(f"Skipping hidden record {record.name}")
2019            continue
2020        record_file = os.path.join(output_dir, f"struct.{record.name}.html")
2021        log.info(f"Creating record file for {namespace.name}.{record.name}: {record_file}")
2022
2023        tmpl = TemplateRecord(namespace, record)
2024        template_records.append(tmpl)
2025
2026        with open(record_file, "w") as out:
2027            content = record_tmpl.render({
2028                'CONFIG': config,
2029                'namespace': namespace,
2030                'struct': tmpl,
2031            })
2032
2033            out.write(content)
2034
2035        for ctor in record.constructors:
2036            c = TemplateFunction(namespace, ctor)
2037            ctor_file = os.path.join(output_dir, f"ctor.{record.name}.{ctor.name}.html")
2038            log.debug(f"Creating ctor file for {namespace.name}.{record.name}.{ctor.name}: {ctor_file}")
2039
2040            with open(ctor_file, "w") as out:
2041                out.write(type_func_tmpl.render({
2042                    'CONFIG': config,
2043                    'namespace': namespace,
2044                    'class': tmpl,
2045                    'type_func': c,
2046                }))
2047
2048        for method in record.methods:
2049            m = TemplateMethod(namespace, record, method)
2050            method_file = os.path.join(output_dir, f"method.{record.name}.{method.name}.html")
2051            log.debug(f"Creating method file for {namespace.name}.{record.name}.{method.name}: {method_file}")
2052
2053            with open(method_file, "w") as out:
2054                out.write(method_tmpl.render({
2055                    'CONFIG': config,
2056                    'namespace': namespace,
2057                    'class': tmpl,
2058                    'method': m,
2059                }))
2060
2061        for type_func in record.functions:
2062            f = TemplateFunction(namespace, type_func)
2063            type_func_file = os.path.join(output_dir, f"type_func.{record.name}.{type_func.name}.html")
2064            log.debug(f"Creating type func file for {namespace.name}.{record.name}.{type_func.name}: {type_func_file}")
2065
2066            with open(type_func_file, "w") as out:
2067                out.write(type_func_tmpl.render({
2068                    'CONFIG': config,
2069                    'namespace': namespace,
2070                    'class': tmpl,
2071                    'type_func': f,
2072                }))
2073
2074    return template_records
2075
2076
2077def _gen_unions(config, theme_config, output_dir, jinja_env, repository, all_unions):
2078    namespace = repository.namespace
2079
2080    union_tmpl = jinja_env.get_template(theme_config.union_template)
2081    method_tmpl = jinja_env.get_template(theme_config.method_template)
2082    type_func_tmpl = jinja_env.get_template(theme_config.type_func_template)
2083
2084    template_unions = []
2085
2086    for union in all_unions:
2087        if config.is_hidden(union.name):
2088            log.debug(f"Skipping hidden union {union.name}")
2089            continue
2090        union_file = os.path.join(output_dir, f"union.{union.name}.html")
2091        log.info(f"Creating union file for {namespace.name}.{union.name}: {union_file}")
2092
2093        tmpl = TemplateUnion(namespace, union)
2094        template_unions.append(tmpl)
2095
2096        with open(union_file, "w") as out:
2097            content = union_tmpl.render({
2098                'CONFIG': config,
2099                'namespace': namespace,
2100                'struct': tmpl,
2101            })
2102
2103            out.write(content)
2104
2105        for ctor in union.constructors:
2106            c = TemplateFunction(namespace, ctor)
2107            ctor_file = os.path.join(output_dir, f"ctor.{union.name}.{ctor.name}.html")
2108            log.debug(f"Creating ctor file for {namespace.name}.{union.name}.{ctor.name}: {ctor_file}")
2109
2110            with open(ctor_file, "w") as out:
2111                out.write(type_func_tmpl.render({
2112                    'CONFIG': config,
2113                    'namespace': namespace,
2114                    'class': tmpl,
2115                    'type_func': c,
2116                }))
2117
2118        for method in union.methods:
2119            m = TemplateMethod(namespace, union, method)
2120            method_file = os.path.join(output_dir, f"method.{union.name}.{method.name}.html")
2121            log.debug(f"Creating method file for {namespace.name}.{union.name}.{method.name}: {method_file}")
2122
2123            with open(method_file, "w") as out:
2124                out.write(method_tmpl.render({
2125                    'CONFIG': config,
2126                    'namespace': namespace,
2127                    'class': tmpl,
2128                    'method': m,
2129                }))
2130
2131        for type_func in union.functions:
2132            f = TemplateFunction(namespace, type_func)
2133            type_func_file = os.path.join(output_dir, f"type_func.{union.name}.{type_func.name}.html")
2134            log.debug(f"Creating type func file for {namespace.name}.{union.name}.{type_func.name}: {type_func_file}")
2135
2136            with open(type_func_file, "w") as out:
2137                out.write(type_func_tmpl.render({
2138                    'CONFIG': config,
2139                    'namespace': namespace,
2140                    'class': tmpl,
2141                    'type_func': f,
2142                }))
2143
2144    return template_unions
2145
2146
2147def _gen_functions(config, theme_config, output_dir, jinja_env, repository, all_functions):
2148    namespace = repository.namespace
2149
2150    func_tmpl = jinja_env.get_template(theme_config.func_template)
2151
2152    template_functions = []
2153
2154    for func in all_functions:
2155        if config.is_hidden(func.name):
2156            log.debug(f"Skipping hidden function {func.name}")
2157            continue
2158        func_file = os.path.join(output_dir, f"func.{func.name}.html")
2159        log.info(f"Creating function file for {namespace.name}.{func.name}: {func_file}")
2160
2161        tmpl = TemplateFunction(namespace, func)
2162        template_functions.append(tmpl)
2163
2164        with open(func_file, "w") as out:
2165            content = func_tmpl.render({
2166                'CONFIG': config,
2167                'namespace': namespace,
2168                'func': tmpl,
2169            })
2170
2171            out.write(content)
2172
2173    return template_functions
2174
2175
2176def _gen_callbacks(config, theme_config, output_dir, jinja_env, repository, all_callbacks):
2177    namespace = repository.namespace
2178
2179    func_tmpl = jinja_env.get_template(theme_config.func_template)
2180
2181    template_callbacks = []
2182
2183    for func in all_callbacks:
2184        if config.is_hidden(func.name):
2185            log.debug(f"Skipping hidden callback {func.name}")
2186            continue
2187        func_file = os.path.join(output_dir, f"callback.{func.name}.html")
2188        log.info(f"Creating callback file for {namespace.name}.{func.name}: {func_file}")
2189
2190        tmpl = TemplateCallback(namespace, func)
2191        template_callbacks.append(tmpl)
2192
2193        with open(func_file, "w") as out:
2194            content = func_tmpl.render({
2195                'CONFIG': config,
2196                'namespace': namespace,
2197                'func': tmpl,
2198            })
2199
2200            out.write(content)
2201
2202    return template_callbacks
2203
2204
2205def _gen_function_macros(config, theme_config, output_dir, jinja_env, repository, all_functions):
2206    namespace = repository.namespace
2207
2208    func_tmpl = jinja_env.get_template(theme_config.func_template)
2209
2210    template_functions = []
2211
2212    for func in all_functions:
2213        if config.is_hidden(func.name):
2214            log.debug(f"Skipping hidden macro {func.name}")
2215            continue
2216        func_file = os.path.join(output_dir, f"func.{func.name}.html")
2217        log.info(f"Creating function macro file for {namespace.name}.{func.name}: {func_file}")
2218
2219        tmpl = TemplateFunction(namespace, func)
2220        template_functions.append(tmpl)
2221
2222        with open(func_file, "w") as out:
2223            content = func_tmpl.render({
2224                'CONFIG': config,
2225                'namespace': namespace,
2226                'func': tmpl,
2227            })
2228
2229            out.write(content)
2230
2231    return template_functions
2232
2233
2234def gen_content_files(config, theme_config, content_dir, output_dir, jinja_env, namespace):
2235    content_files = []
2236
2237    content_tmpl = jinja_env.get_template(theme_config.content_template)
2238    md = markdown.Markdown(extensions=utils.MD_EXTENSIONS, extension_configs=utils.MD_EXTENSIONS_CONF)
2239
2240    for file_name in config.content_files:
2241        src_file = os.path.join(content_dir, file_name)
2242
2243        src_data = ""
2244        with open(src_file, encoding='utf-8') as infile:
2245            source = []
2246            for line in infile:
2247                source.append(line)
2248            src_data = "".join(source)
2249
2250        dst_data = utils.preprocess_docs(src_data, namespace, md=md)
2251        title = "\n".join(md.Meta.get("title", ["Unknown document"]))
2252
2253        content_file = file_name.replace(".md", ".html")
2254        dst_file = os.path.join(output_dir, content_file)
2255
2256        content = {
2257            "abs_input_file": src_file,
2258            "abs_output_file": dst_file,
2259            "source_file": file_name,
2260            "output_file": content_file,
2261            "meta": md.Meta,
2262            "title": title,
2263            "data": dst_data,
2264        }
2265
2266        log.info(f"Generating content file {file_name}: {dst_file}")
2267        with open(dst_file, "w", encoding='utf-8') as outfile:
2268            outfile.write(content_tmpl.render({
2269                "CONFIG": config,
2270                "namespace": namespace,
2271                "content": content,
2272            }))
2273
2274        content_files.append({
2275            "title": title,
2276            "href": content_file,
2277        })
2278
2279        md.reset()
2280
2281    return content_files
2282
2283
2284def gen_content_images(config, content_dir, output_dir):
2285    content_images = []
2286
2287    for image_file in config.content_images:
2288        infile = os.path.join(content_dir, image_file)
2289        outfile = os.path.join(output_dir, os.path.basename(image_file))
2290        log.debug(f"Adding extra content image: {infile} -> {outfile}")
2291        content_images += [(infile, outfile)]
2292
2293    return content_images
2294
2295
2296def gen_types_hierarchy(config, theme_config, output_dir, jinja_env, repository):
2297    namespace = repository.namespace
2298
2299    # Gather all class types and their parent to build a flat list
2300    flat_tree = []
2301    for cls in namespace.get_classes():
2302        name = cls.name
2303        if cls.parent is not None:
2304            parent = cls.parent.name
2305        else:
2306            parent = None
2307        flat_tree.append((name, parent))
2308
2309    # A subtly elegant way to rebuild the tree from a flat
2310    # list of (name, parent) tuples. See:
2311    #
2312    #   https://stackoverflow.com/a/43728268/771066
2313    def subtree(cls, rel):
2314        return {
2315            v: subtree(v, rel)
2316            for v in [x[0] for x in rel if x[1] == cls]
2317        }
2318
2319    # All GObject sub-types
2320    objects_tree = subtree('GObject.Object', flat_tree)
2321
2322    # All GInitiallyUnowned sub-types
2323    unowned_tree = subtree('GObject.InitiallyUnowned', flat_tree)
2324
2325    # All GTypeInstance sub-types
2326    typed_tree = subtree(None, flat_tree)
2327
2328    res = ["<h1>Classes Hierarchy</h1>"]
2329
2330    def dump_tree(node, out):
2331        for k in node:
2332            out.append(f"<li class=\"type\"><a href=\"class.{k}.html\"><code>{k}</code></a>")
2333            if len(node[k]) != 0:
2334                out.append("<ul class=\"type\">")
2335                dump_tree(node[k], out)
2336                out.append("</ul>")
2337            out.append("</li>")
2338
2339    if len(objects_tree) != 0:
2340        res += ["<div class=\"docblock\">"]
2341        res += ["<ul class=\"type root\">"]
2342        res += [" <li class=\"type\"><code>GObject</code></li><ul class=\"type\">"]
2343        dump_tree(objects_tree, res)
2344        res += [" </ul></li>"]
2345        res += ["</ul>"]
2346        res += ["</div>"]
2347
2348    if len(unowned_tree) != 0:
2349        res += ["<div class=\"docblock\">"]
2350        res += ["<ul class=\"type root\">"]
2351        res += [" <li class=\"type\"><code>GInitiallyUnowned</code></li><ul class=\"type\">"]
2352        dump_tree(unowned_tree, res)
2353        res += [" </ul></li>"]
2354        res += ["</ul>"]
2355        res += ["</div>"]
2356
2357    if len(typed_tree) != 0:
2358        res += ["<div class=\"docblock\">"]
2359        res += ["<ul class=\"type root\">"]
2360        dump_tree(typed_tree, res)
2361        res += ["</ul>"]
2362        res += ["</div>"]
2363
2364    content = {
2365        "output_file": "classes_hierarchy.html",
2366        "meta": {
2367            "keywords": "types, hierarchy, classes",
2368        },
2369        "title": "Classes Hierarchy",
2370        "data": Markup("\n".join(res)),
2371    }
2372
2373    content_tmpl = jinja_env.get_template(theme_config.content_template)
2374
2375    dst_file = os.path.join(output_dir, content["output_file"])
2376    log.info(f"Generating type hierarchy file: {dst_file}")
2377    with open(dst_file, "w") as outfile:
2378        outfile.write(content_tmpl.render({
2379            "CONFIG": config,
2380            "namespace": namespace,
2381            "content": content,
2382        }))
2383
2384    return {
2385        "title": content["title"],
2386        "href": content["output_file"],
2387    }
2388
2389
2390def gen_devhelp(config, repository, namespace, symbols, content_files):
2391    book = etree.Element('book')
2392    book.set("xmlns", "http://www.devhelp.net/book")
2393    book.set("title", f"{namespace.name}-{namespace.version} Reference Manual")
2394    book.set("link", "index.html")
2395    book.set("author", f"{config.authors}")
2396    book.set("name", f"{namespace.name}")
2397    book.set("version", "2")
2398    book.set("language", "c")
2399
2400    chapters = etree.SubElement(book, 'chapters')
2401
2402    for f in content_files:
2403        sub = etree.SubElement(chapters, 'sub')
2404        sub.set("name", f["title"])
2405        sub.set("link", f["href"])
2406
2407    for section, types in symbols.items():
2408        if len(types) == 0:
2409            continue
2410
2411        sub = etree.SubElement(chapters, "sub")
2412        sub.set("name", section.replace("_", " ").capitalize())
2413        sub.set("link", f"index.html#{section}")
2414
2415        for t in types:
2416            sub_section = etree.SubElement(sub, "sub")
2417            sub_section.set("name", t.name)
2418            sub_section.set("link", f"{FRAGMENT[section]}.{t.name}.html")
2419
2420    functions = etree.SubElement(book, "functions")
2421    for section, types in symbols.items():
2422        if len(types) == 0:
2423            continue
2424
2425        for t in types:
2426            if section in ["functions", "function_macros"]:
2427                keyword = etree.SubElement(functions, "keyword")
2428                if section == "functions":
2429                    keyword.set("type", "function")
2430                else:
2431                    keyword.set("type", "macro")
2432                keyword.set("name", t.identifier)
2433                keyword.set("link", f"func.{t.name}.html")
2434                if t.available_since is not None:
2435                    keyword.set("since", t.available_since)
2436                if t.deprecated_since is not None and t.deprecated_since["version"] is not None:
2437                    keyword.set("deprecated", t.deprecated_since["version"])
2438                continue
2439
2440            if section == "constants":
2441                keyword = etree.SubElement(functions, "keyword")
2442                keyword.set("type", "constant")
2443                keyword.set("name", t.identifier)
2444                keyword.set("link", f"constant.{t.name}.html")
2445                if t.available_since is not None:
2446                    keyword.set("since", t.available_since)
2447                if t.deprecated_since is not None and t.deprecated_since["version"] is not None:
2448                    keyword.set("deprecated", t.deprecated_since["version"])
2449                continue
2450
2451            if section in ["aliases", "bitfields", "classes", "domains", "enums", "interfaces", "structs", "unions"]:
2452                # Skip anonymous types; e.g. GValue's anonymous union
2453                if t.type_cname is None:
2454                    continue
2455                keyword = etree.SubElement(functions, "keyword")
2456                if section == "aliases":
2457                    keyword.set("type", "typedef")
2458                elif section in ["bitfields", "domains", "enums"]:
2459                    keyword.set("type", "enum")
2460                elif section == "unions":
2461                    keyword.set("type", "union")
2462                else:
2463                    keyword.set("type", "struct")
2464                keyword.set("name", t.type_cname)
2465                keyword.set("link", f"{FRAGMENT[section]}.{t.name}.html")
2466                if t.available_since is not None:
2467                    keyword.set("since", t.available_since)
2468                if t.deprecated_since is not None and t.deprecated_since["version"] is not None:
2469                    keyword.set("deprecated", t.deprecated_since["version"])
2470
2471            for m in getattr(t, "members", []):
2472                keyword = etree.SubElement(functions, "keyword")
2473                keyword.set("type", "constant")
2474                keyword.set("name", m.name)
2475                keyword.set("link", f"{FRAGMENT[section]}.{t.name}.html")
2476
2477            for f in getattr(t, "fields", []):
2478                keyword = etree.SubElement(functions, "keyword")
2479                keyword.set("type", "member")
2480                keyword.set("name", f"{t.type_cname}.{f.name}")
2481                keyword.set("link", f"{FRAGMENT[section]}.{t.name}.html")
2482
2483            class_struct = getattr(t, "class_struct", None)
2484            if class_struct is not None:
2485                for f in getattr(class_struct, "fields", []):
2486                    keyword = etree.SubElement(functions, "keyword")
2487                    keyword.set("type", "member")
2488                    keyword.set("name", f"{t.class_name}.{f.name}")
2489                    if section == "class":
2490                        keyword.set("link", f"class.{t.name}.html#class-struct")
2491                    elif section == "interface":
2492                        keyword.set("link", f"iface.{t.name}.html#interface-struct")
2493                    else:
2494                        keyword.set("link", f"{FRAGMENT[section]}.{t.name}.html")
2495
2496            for m in getattr(t, "methods", []):
2497                keyword = etree.SubElement(functions, "keyword")
2498                keyword.set("type", "function")
2499                keyword.set("name", m['identifier'])
2500                keyword.set("link", f"method.{t.name}.{m['name']}.html")
2501                if m["available_since"] is not None:
2502                    keyword.set("since", m["available_since"])
2503                if m["deprecated_since"] is not None:
2504                    keyword.set("deprecated", m["deprecated_since"])
2505
2506            for c in getattr(t, "ctors", []):
2507                keyword = etree.SubElement(functions, "keyword")
2508                keyword.set("type", "function")
2509                keyword.set("name", c['identifier'])
2510                keyword.set("link", f"ctor.{t.name}.{c['name']}.html")
2511                if c["available_since"] is not None:
2512                    keyword.set("since", c["available_since"])
2513                if c["deprecated_since"] is not None:
2514                    keyword.set("deprecated", c["deprecated_since"])
2515
2516            for f in getattr(t, "type_funcs", []):
2517                keyword = etree.SubElement(functions, "keyword")
2518                keyword.set("type", "function")
2519                keyword.set("name", f['identifier'])
2520                keyword.set("link", f"type_func.{t.name}.{f['name']}.html")
2521                if f["available_since"] is not None:
2522                    keyword.set("since", f["available_since"])
2523                if f["deprecated_since"] is not None:
2524                    keyword.set("deprecated", f["deprecated_since"])
2525
2526            for m in getattr(t, "class_methods", []):
2527                keyword = etree.SubElement(functions, "keyword")
2528                keyword.set("type", "function")
2529                keyword.set("name", m['identifier'])
2530                keyword.set("link", f"class_method.{t.name}.{m['name']}.html")
2531                if m["available_since"] is not None:
2532                    keyword.set("since", m["available_since"])
2533                if m["deprecated_since"] is not None:
2534                    keyword.set("deprecated", m["deprecated_since"])
2535
2536            for p in getattr(t, "properties", []):
2537                keyword = etree.SubElement(functions, "keyword")
2538                keyword.set("type", "property")
2539                keyword.set("name", f"The {t.type_cname}:{p['name']} property")
2540                keyword.set("link", f"property.{t.name}.{p['name']}.html")
2541                if p["available_since"] is not None:
2542                    keyword.set("since", p["available_since"])
2543                if p["deprecated_since"] is not None:
2544                    keyword.set("deprecated", p["deprecated_since"])
2545
2546            for s in getattr(t, "signals", []):
2547                keyword = etree.SubElement(functions, "keyword")
2548                keyword.set("type", "signal")
2549                keyword.set("name", f"The {t.type_cname}::{s['name']} signal")
2550                keyword.set("link", f"signal.{t.name}.{s['name']}.html")
2551                if s["available_since"] is not None:
2552                    keyword.set("since", s["available_since"])
2553                if s["deprecated_since"] is not None:
2554                    keyword.set("deprecated", s["deprecated_since"])
2555
2556    return etree.ElementTree(book)
2557
2558
2559def gen_reference(config, options, repository, templates_dir, theme_config, content_dir, output_dir):
2560    theme_dir = os.path.join(templates_dir, theme_config.name.lower())
2561    log.debug(f"Loading jinja templates from {theme_dir}")
2562
2563    fs_loader = jinja2.FileSystemLoader(theme_dir)
2564    jinja_env = jinja2.Environment(loader=fs_loader, autoescape=jinja2.select_autoescape(['html']))
2565
2566    namespace = repository.namespace
2567
2568    symbols = {
2569        "aliases": sorted(namespace.get_aliases(), key=lambda alias: alias.name.lower()),
2570        "bitfields": sorted(namespace.get_bitfields(), key=lambda bitfield: bitfield.name.lower()),
2571        "callbacks": sorted(namespace.get_callbacks(), key=lambda callback: callback.name.lower()),
2572        "classes": sorted(namespace.get_classes(), key=lambda cls: cls.name.lower()),
2573        "constants": sorted(namespace.get_constants(), key=lambda const: const.name.lower()),
2574        "domains": sorted(namespace.get_error_domains(), key=lambda domain: domain.name.lower()),
2575        "enums": sorted(namespace.get_enumerations(), key=lambda enum: enum.name.lower()),
2576        "functions": sorted(namespace.get_functions(), key=lambda func: func.name.lower()),
2577        "function_macros": sorted(namespace.get_effective_function_macros(), key=lambda func: func.name.lower()),
2578        "interfaces": sorted(namespace.get_interfaces(), key=lambda interface: interface.name.lower()),
2579        "structs": sorted(namespace.get_effective_records(), key=lambda record: record.name.lower()),
2580        "unions": sorted(namespace.get_unions(), key=lambda union: union.name.lower()),
2581    }
2582
2583    all_indices = {
2584        "aliases": _gen_aliases,
2585        "bitfields": _gen_bitfields,
2586        "callbacks": _gen_callbacks,
2587        "classes": _gen_classes,
2588        "constants": _gen_constants,
2589        "domains": _gen_domains,
2590        "enums": _gen_enums,
2591        "functions": _gen_functions,
2592        "function_macros": _gen_function_macros,
2593        "interfaces": _gen_interfaces,
2594        "structs": _gen_records,
2595        "unions": _gen_unions,
2596    }
2597
2598    if options.no_namespace_dir:
2599        ns_dir = output_dir
2600    else:
2601        ns_dir = os.path.join(output_dir, f"{namespace.name}-{namespace.version}")
2602
2603    log.debug(f"Creating output path for the namespace: {ns_dir}")
2604    os.makedirs(ns_dir, exist_ok=True)
2605
2606    content_files = gen_content_files(config, theme_config, content_dir, ns_dir, jinja_env, namespace)
2607    content_images = gen_content_images(config, content_dir, ns_dir)
2608    content_files.append(gen_types_hierarchy(config, theme_config, ns_dir, jinja_env, repository))
2609
2610    if options.sections == [] or options.sections == ["all"]:
2611        gen_indices = list(all_indices.keys())
2612    elif options.sections == ["none"]:
2613        gen_indices = []
2614    else:
2615        gen_indices = options.sections
2616
2617    log.info(f"Generating references for: {gen_indices}")
2618
2619    template_symbols = {}
2620
2621    # Each section is isolated, so we run it into a thread pool
2622    with concurrent.futures.ThreadPoolExecutor() as executor:
2623        futures_to_section = {}
2624        for section in gen_indices:
2625            s = symbols.get(section, [])
2626            if s is None:
2627                log.debug(f"No symbols for section {section}")
2628                continue
2629
2630            generator = all_indices.get(section, None)
2631            if generator is None:
2632                log.debug(f"No generator for section {section}")
2633                continue
2634
2635            f = executor.submit(generator, config, theme_config, ns_dir, jinja_env, repository, s)
2636            futures_to_section[f] = section
2637
2638        for future in concurrent.futures.as_completed(futures_to_section):
2639            section = futures_to_section[future]
2640            try:
2641                res = future.result()
2642            except Exception as e:
2643                if log.log_fatal_warnings:
2644                    import traceback
2645                    traceback.print_exc()
2646                log.warning(f"Section {section} raised {e}")
2647            else:
2648                template_symbols[section] = res
2649
2650    # The concurrent processing introduces non-determinism. Ensure iteration order is reproducible
2651    # by sorting by key. This has virtually no overhead since the values are not copied.
2652    template_symbols = dict(sorted(template_symbols.items()))
2653
2654    ns_tmpl = jinja_env.get_template(theme_config.namespace_template)
2655    ns_file = os.path.join(ns_dir, "index.html")
2656    log.info(f"Creating namespace index file for {namespace.name}-{namespace.version}: {ns_file}")
2657    with open(ns_file, "w") as out:
2658        out.write(ns_tmpl.render({
2659            "CONFIG": config,
2660            "repository": repository,
2661            "namespace": namespace,
2662            "symbols": template_symbols,
2663            "content_files": content_files,
2664        }))
2665
2666    if config.devhelp:
2667        # Devhelp expects the book file to have the same basename as the directory it is in.
2668        devhelp_file = os.path.join(ns_dir, f"{os.path.basename(ns_dir)}.devhelp2")
2669        log.info(f"Creating DevHelp file for {namespace.name}-{namespace.version}: {devhelp_file}")
2670        res = gen_devhelp(config, repository, namespace, template_symbols, content_files)
2671        res.write(devhelp_file, encoding="UTF-8")
2672
2673    if config.search_index:
2674        gdgenindices.gen_indices(config, repository, content_dir, ns_dir)
2675
2676    copy_files = []
2677    if theme_config.css is not None:
2678        style_src = os.path.join(theme_dir, theme_config.css)
2679        style_dst = os.path.join(ns_dir, theme_config.css)
2680        copy_files.append((style_src, style_dst))
2681
2682    for extra_file in theme_config.extra_files:
2683        src = os.path.join(theme_dir, extra_file)
2684        dst = os.path.join(ns_dir, extra_file)
2685        copy_files.append((src, dst))
2686
2687    if config.urlmap_file is not None:
2688        src = os.path.join(content_dir, config.urlmap_file)
2689        dst = os.path.join(ns_dir, os.path.basename(config.urlmap_file))
2690        copy_files.append((src, dst))
2691
2692    copy_files.extend(content_images)
2693
2694    def copy_worker(src, dst):
2695        log.info(f"Copying file {src}: {dst}")
2696        dst_dir = os.path.dirname(dst)
2697        os.makedirs(dst_dir, exist_ok=True)
2698        shutil.copy(src, dst)
2699
2700    with concurrent.futures.ThreadPoolExecutor() as executor:
2701        for (src, dst) in copy_files:
2702            executor.submit(copy_worker, src, dst)
2703
2704
2705def add_args(parser):
2706    parser.add_argument("--add-include-path", action="append", dest="include_paths", default=[],
2707                        help="include paths for other GIR files")
2708    parser.add_argument("-C", "--config", metavar="FILE", help="the configuration file")
2709    parser.add_argument("--dry-run", action="store_true", help="parses the GIR file without generating files")
2710    parser.add_argument("--templates-dir", default=None, help="the base directory with the theme templates")
2711    parser.add_argument("--content-dir", default=None, help="the base directory with the extra content")
2712    parser.add_argument("--theme-name", default="basic", help="the theme to use")
2713    parser.add_argument("--output-dir", default=None, help="the output directory for the index files")
2714    parser.add_argument("--no-namespace-dir", action="store_true", help="do not create a namespace directory under the output directory")
2715    parser.add_argument("--section", action="append", dest="sections", default=[], help="the sections to generate, or 'all'")
2716    parser.add_argument("infile", metavar="GIRFILE", type=argparse.FileType('r', encoding='UTF-8'),
2717                        default=sys.stdin, help="the GIR file to parse")
2718
2719
2720def run(options):
2721    log.info(f"Loading config file: {options.config}")
2722
2723    conf = config.GIDocConfig(options.config)
2724
2725    output_dir = options.output_dir or os.getcwd()
2726    content_dir = options.content_dir or os.getcwd()
2727
2728    if options.templates_dir is not None:
2729        templates_dir = options.templates_dir
2730    else:
2731        templates_dir = conf.get_templates_dir()
2732        if templates_dir is None:
2733            templates_dir = os.path.join(os.path.dirname(__file__), 'templates')
2734
2735    theme_name = conf.get_theme_name(default=options.theme_name)
2736    theme_conf = config.GITemplateConfig(templates_dir, theme_name)
2737
2738    log.debug(f"Templates directory: {templates_dir}")
2739    log.info(f"Theme name: {theme_conf.name}")
2740    log.info(f"Output directory: {output_dir}")
2741
2742    paths = []
2743    paths.extend(options.include_paths)
2744    paths.extend(utils.default_search_paths())
2745    log.debug(f"Search paths: {paths}")
2746
2747    log.info("Parsing GIR file")
2748    parser = gir.GirParser(search_paths=paths)
2749    parser.parse(options.infile)
2750
2751    if not options.dry_run:
2752        log.checkpoint()
2753        gen_reference(conf, options, parser.get_repository(), templates_dir, theme_conf, content_dir, output_dir)
2754
2755    return 0
2756