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