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