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