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