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