diff options
Diffstat (limited to 'doc/python_api/sphinx_doc_gen.py')
-rw-r--r-- | doc/python_api/sphinx_doc_gen.py | 254 |
1 files changed, 219 insertions, 35 deletions
diff --git a/doc/python_api/sphinx_doc_gen.py b/doc/python_api/sphinx_doc_gen.py index b29251b67cc..f5e0369cede 100644 --- a/doc/python_api/sphinx_doc_gen.py +++ b/doc/python_api/sphinx_doc_gen.py @@ -1,7 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-or-later -# <pep8 compliant> - """ API dump in RST files --------------------- @@ -78,6 +76,27 @@ SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__)) # See: D6261 for reference. USE_ONLY_BUILTIN_RNA_TYPES = True +# Write a page for each static enum defined in: +# `source/blender/makesrna/RNA_enum_items.h` so the enums can be linked to instead of being expanded everywhere. +USE_SHARED_RNA_ENUM_ITEMS_STATIC = True + +if USE_SHARED_RNA_ENUM_ITEMS_STATIC: + from _bpy import rna_enum_items_static + rna_enum_dict = rna_enum_items_static() + for key in ("DummyRNA_DEFAULT_items", "DummyRNA_NULL_items"): + del rna_enum_dict[key] + del key, rna_enum_items_static + + # Build enum `{pointer: identifier}` map, so any enum property pointer can + # lookup an identifier using `InfoPropertyRNA.enum_pointer` as the key. + rna_enum_pointer_to_id_map = { + enum_prop.as_pointer(): key + for key, enum_items in rna_enum_dict.items() + # It's possible the first item is a heading (which has no identifier). + # skip these as the `EnumProperty.enum_items` does not expose them. + if (enum_prop := next(iter(enum_prop for enum_prop in enum_items if enum_prop.identifier), None)) + } + def handle_args(): """ @@ -123,6 +142,26 @@ def handle_args(): ) parser.add_argument( + "--api-changelog-generate", + dest="changelog", + default=False, + action='store_true', + help="Generate the API changelog RST file " + "(default=False, requires `--api-dump-index-path` parameter)", + required=False, + ) + + parser.add_argument( + "--api-dump-index-path", + dest="api_dump_index_path", + metavar='FILE', + default=None, + help="Path to the API dump index JSON file " + "(required when `--api-changelog-generate` is True)", + required=False, + ) + + parser.add_argument( "-o", "--output", dest="output_dir", type=str, @@ -495,6 +534,42 @@ if ARGS.sphinx_build_pdf: sphinx_make_pdf_log = os.path.join(ARGS.output_dir, ".latex_make.log") SPHINX_MAKE_PDF_STDOUT = open(sphinx_make_pdf_log, "w", encoding="utf-8") + +# --------------------------------CHANGELOG GENERATION-------------------------------------- + +def generate_changelog(): + import importlib.util + spec = importlib.util.spec_from_file_location( + "sphinx_changelog_gen", + os.path.abspath(os.path.join(SCRIPT_DIR, "sphinx_changelog_gen.py")), + ) + sphinx_changelog_gen = importlib.util.module_from_spec(spec) + spec.loader.exec_module(sphinx_changelog_gen) + + API_DUMP_INDEX_FILEPATH = ARGS.api_dump_index_path + API_DUMP_ROOT = os.path.dirname(API_DUMP_INDEX_FILEPATH) + API_DUMP_FILEPATH = os.path.abspath(os.path.join(API_DUMP_ROOT, BLENDER_VERSION_DOTS, "api_dump.json")) + API_CHANGELOG_FILEPATH = os.path.abspath(os.path.join(SPHINX_IN_TMP, "change_log.rst")) + + sphinx_changelog_gen.main(( + "--", + "--indexpath", + API_DUMP_INDEX_FILEPATH, + "dump", + "--filepath-out", + API_DUMP_FILEPATH, + )) + + sphinx_changelog_gen.main(( + "--", + "--indexpath", + API_DUMP_INDEX_FILEPATH, + "changelog", + "--filepath-out", + API_CHANGELOG_FILEPATH, + )) + + # --------------------------------API DUMP-------------------------------------- # Lame, python won't give some access. @@ -531,7 +606,7 @@ def import_value_from_module(module_name, import_name): def execfile(filepath): global_namespace = {"__file__": filepath, "__name__": "__main__"} - with open(filepath) as file_handle: + with open(filepath, encoding="utf-8") as file_handle: exec(compile(file_handle.read(), filepath, 'exec'), global_namespace) @@ -693,26 +768,6 @@ def write_indented_lines(ident, fn, text, strip=True): fn(ident + l + "\n") -def pymethod2sphinx(ident, fw, identifier, py_func): - """ - class method to sphinx - """ - arg_str = inspect.formatargspec(*inspect.getargspec(py_func)) - if arg_str.startswith("(self, "): - arg_str = "(" + arg_str[7:] - func_type = "method" - elif arg_str.startswith("(cls, "): - arg_str = "(" + arg_str[6:] - func_type = "classmethod" - else: - func_type = "staticmethod" - - fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str)) - if py_func.__doc__: - write_indented_lines(ident + " ", fw, py_func.__doc__) - fw("\n") - - def pyfunc2sphinx(ident, fw, module_name, type_name, identifier, py_func, is_class=True): """ function or class method to sphinx @@ -1220,15 +1275,23 @@ def pycontext2sphinx(basepath): # No need to check if there are duplicates yet as it's known there wont be. unique.add(prop.identifier) + enum_descr_override = None + if USE_SHARED_RNA_ENUM_ITEMS_STATIC: + enum_descr_override = pyrna_enum2sphinx_shared_link(prop) + type_descr = prop.get_type_description( - class_fmt=":class:`bpy.types.%s`", collection_id=_BPY_PROP_COLLECTION_ID) + class_fmt=":class:`bpy.types.%s`", + collection_id=_BPY_PROP_COLLECTION_ID, + enum_descr_override=enum_descr_override, + ) fw(".. data:: %s\n\n" % prop.identifier) if prop.description: fw(" %s\n\n" % prop.description) # Special exception, can't use generic code here for enums. if prop.type == "enum": - enum_text = pyrna_enum2sphinx(prop) + # If the link has been written, no need to inline the enum items. + enum_text = "" if enum_descr_override else pyrna_enum2sphinx(prop) if enum_text: write_indented_lines(" ", fw, enum_text) fw("\n") @@ -1290,6 +1353,11 @@ def pyrna_enum2sphinx(prop, use_empty_descriptions=False): Write a bullet point list of enum + descriptions. """ + # Write a link to the enum if this is part of `rna_enum_pointer_map`. + if USE_SHARED_RNA_ENUM_ITEMS_STATIC: + if (result := pyrna_enum2sphinx_shared_link(prop)) is not None: + return result + if use_empty_descriptions: ok = True else: @@ -1368,10 +1436,15 @@ def pyrna2sphinx(basepath): kwargs["collection_id"] = _BPY_PROP_COLLECTION_ID - type_descr = prop.get_type_description(**kwargs) + enum_descr_override = None + if USE_SHARED_RNA_ENUM_ITEMS_STATIC: + enum_descr_override = pyrna_enum2sphinx_shared_link(prop) + kwargs["enum_descr_override"] = enum_descr_override - enum_text = pyrna_enum2sphinx(prop) + type_descr = prop.get_type_description(**kwargs) + # If the link has been written, no need to inline the enum items. + enum_text = "" if enum_descr_override else pyrna_enum2sphinx(prop) if prop.name or prop.description or enum_text: fw(ident + ":%s%s:\n\n" % (id_name, identifier)) @@ -1456,7 +1529,8 @@ def pyrna2sphinx(basepath): else: fw(".. class:: %s\n\n" % struct_id) - fw(" %s\n\n" % struct.description) + write_indented_lines(" ", fw, struct.description, False) + fw("\n") # Properties sorted in alphabetical order. sorted_struct_properties = struct.properties[:] @@ -1472,7 +1546,15 @@ def pyrna2sphinx(basepath): if identifier in struct_blacklist: continue - type_descr = prop.get_type_description(class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID) + enum_descr_override = None + if USE_SHARED_RNA_ENUM_ITEMS_STATIC: + enum_descr_override = pyrna_enum2sphinx_shared_link(prop) + + type_descr = prop.get_type_description( + class_fmt=":class:`%s`", + collection_id=_BPY_PROP_COLLECTION_ID, + enum_descr_override=enum_descr_override, + ) # Read-only properties use "data" directive, variables properties use "attribute" directive. if "readonly" in type_descr: fw(" .. data:: %s\n" % identifier) @@ -1489,7 +1571,8 @@ def pyrna2sphinx(basepath): # Special exception, can't use generic code here for enums. if prop.type == "enum": - enum_text = pyrna_enum2sphinx(prop) + # If the link has been written, no need to inline the enum items. + enum_text = "" if enum_descr_override else pyrna_enum2sphinx(prop) if enum_text: write_indented_lines(" ", fw, enum_text) fw("\n") @@ -1528,8 +1611,16 @@ def pyrna2sphinx(basepath): for prop in func.return_values: # TODO: pyrna_enum2sphinx for multiple return values... actually don't # think we even use this but still! + + enum_descr_override = None + if USE_SHARED_RNA_ENUM_ITEMS_STATIC: + enum_descr_override = pyrna_enum2sphinx_shared_link(prop) + type_descr = prop.get_type_description( - as_ret=True, class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID) + as_ret=True, class_fmt=":class:`%s`", + collection_id=_BPY_PROP_COLLECTION_ID, + enum_descr_override=enum_descr_override, + ) descr = prop.description if not descr: descr = prop.name @@ -1778,11 +1869,16 @@ def write_sphinx_conf_py(basepath): fw("extensions = ['sphinx.ext.intersphinx']\n\n") fw("intersphinx_mapping = {'blender_manual': ('https://docs.blender.org/manual/en/dev/', None)}\n\n") fw("project = 'Blender %s Python API'\n" % BLENDER_VERSION_STRING) - fw("master_doc = 'index'\n") - fw("copyright = u'Blender Foundation'\n") + fw("root_doc = 'index'\n") + fw("copyright = 'Blender Foundation'\n") fw("version = '%s'\n" % BLENDER_VERSION_DOTS) fw("release = '%s'\n" % BLENDER_VERSION_DOTS) + # Set this as the default is a super-set of Python3. + fw("highlight_language = 'python3'\n") + # No need to detect encoding. + fw("highlight_options = {'default': {'encoding': 'utf-8'}}\n\n") + # Quiet file not in table-of-contents warnings. fw("exclude_patterns = [\n") fw(" 'include__bmesh.rst',\n") @@ -1978,6 +2074,14 @@ def write_rst_types_index(basepath): fw(".. toctree::\n") fw(" :glob:\n\n") fw(" bpy.types.*\n\n") + + # This needs to be included somewhere, while it's hidden, list to avoid warnings. + if USE_SHARED_RNA_ENUM_ITEMS_STATIC: + fw(".. toctree::\n") + fw(" :hidden:\n") + fw(" :maxdepth: 1\n\n") + fw(" Shared Enum Types <bpy_types_enum_items/index>\n\n") + file.close() @@ -2048,6 +2152,81 @@ def write_rst_data(basepath): EXAMPLE_SET_USED.add("bpy.data") +def pyrna_enum2sphinx_shared_link(prop): + """ + Return a reference to the enum used by ``prop`` or None when not found. + """ + if ( + (prop.type == "enum") and + (pointer := prop.enum_pointer) and + (identifier := rna_enum_pointer_to_id_map.get(pointer)) + ): + return ":ref:`%s`" % identifier + return None + + +def write_rst_enum_items(basepath, key, key_no_prefix, enum_items): + """ + Write a single page for a static enum in RST. + + This helps avoiding very large lists being in-lined in many places which is an issue + especially with icons in ``bpy.types.UILayout``. See T87008. + """ + filepath = os.path.join(basepath, "%s.rst" % key_no_prefix) + with open(filepath, "w", encoding="utf-8") as fh: + fw = fh.write + # fw(".. noindex::\n\n") + fw(".. _%s:\n\n" % key) + + fw(title_string(key_no_prefix.replace("_", " ").title(), "#")) + # fw(".. rubric:: %s\n\n" % key_no_prefix.replace("_", " ").title()) + + for item in enum_items: + identifier = item.identifier + name = item.name + description = item.description + if identifier: + fw(":%s: %s\n" % (item.identifier, (escape_rst(name) + ".") if name else "")) + if description: + fw("\n") + write_indented_lines(" ", fw, escape_rst(description) + ".") + else: + fw("\n") + else: + if name: + fw("\n\n**%s**\n\n" % name) + else: + fw("\n\n----\n\n") + + if description: + fw(escape_rst(description) + ".") + fw("\n\n") + + +def write_rst_enum_items_and_index(basepath): + """ + Write shared enum items. + """ + subdir = "bpy_types_enum_items" + basepath_bpy_types_rna_enum = os.path.join(basepath, subdir) + os.makedirs(basepath_bpy_types_rna_enum, exist_ok=True) + with open(os.path.join(basepath_bpy_types_rna_enum, "index.rst"), "w", encoding="utf-8") as fh: + fw = fh.write + fw(title_string("Shared Enum Items", "#")) + fw(".. toctree::\n") + fw("\n") + for key, enum_items in rna_enum_dict.items(): + if not key.startswith("rna_enum_"): + raise Exception("Found RNA enum identifier that doesn't use the 'rna_enum_' prefix, found %r!" % key) + key_no_prefix = key.removeprefix("rna_enum_") + fw(" %s\n" % key_no_prefix) + + for key, enum_items in rna_enum_dict.items(): + key_no_prefix = key.removeprefix("rna_enum_") + write_rst_enum_items(basepath_bpy_types_rna_enum, key, key_no_prefix, enum_items) + fw("\n") + + def write_rst_importable_modules(basepath): """ Write the RST files of importable modules. @@ -2212,6 +2391,10 @@ def rna2sphinx(basepath): write_rst_data(basepath) # bpy.data write_rst_importable_modules(basepath) + # `bpy_types_enum_items/*` (referenced from `bpy.types`). + if USE_SHARED_RNA_ENUM_ITEMS_STATIC: + write_rst_enum_items_and_index(basepath) + # copy the other rsts copy_handwritten_rsts(basepath) @@ -2285,8 +2468,6 @@ def setup_monkey_patch(): # Avoid adding too many changes here. def setup_blender(): - import bpy - # Remove handlers since the functions get included # in the doc-string and don't have meaningful names. lists_to_restore = [] @@ -2349,6 +2530,9 @@ def main(): rna2sphinx(SPHINX_IN_TMP) + if ARGS.changelog: + generate_changelog() + if ARGS.full_rebuild: # Only for full updates. shutil.rmtree(SPHINX_IN, True) |