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