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