From 067b6359bb6309bad4d727124db508cee68187cc Mon Sep 17 00:00:00 2001 From: nesi-mkdocs-bot Date: Mon, 17 Aug 2026 13:02:19 +1200 Subject: [PATCH] Revert safe-include hooks accidentally bundled into GPU update macro_hooks.py and mkdocs_hooks.py picked up unrelated safe-include changes in the GPU update (#1375) that broke the template. Reverting just those two files; the GPU content changes from that PR are untouched. Co-Authored-By: Claude Sonnet 5 --- macro_hooks.py | 27 ------------------- mkdocs_hooks.py | 72 ------------------------------------------------- 2 files changed, 99 deletions(-) diff --git a/macro_hooks.py b/macro_hooks.py index 5c231ad01..364ceefd2 100644 --- a/macro_hooks.py +++ b/macro_hooks.py @@ -5,7 +5,6 @@ If this is confusing, ask Cal to explain. """ -import importlib.util import os import json @@ -13,25 +12,6 @@ tag_index_path = os.getenv("TAG_INDEX_PATH", "docs/assets/tag-index.json") -def _load_mkdocs_hooks(): - """Load mkdocs_hooks.py by file path rather than `import mkdocs_hooks`. - - mkdocs and mkdocs-macros load their own hook module through different - mechanisms (mkdocs briefly patches sys.path and restores it; mkdocs-macros - execs this file directly by path), so a plain cross-file import can't - reliably resolve `mkdocs_hooks` regardless of how *this* file was loaded. - Resolving relative to our own (always-correct) __file__ sidesteps that. - """ - path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "mkdocs_hooks.py") - spec = importlib.util.spec_from_file_location("mkdocs_hooks", path) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -configure_safe_includes = _load_mkdocs_hooks().configure_safe_includes - - class CaseInsensitiveDict(dict): """Dict wrapper allowing `applications[app_name]` lookups regardless of case.""" @@ -79,10 +59,3 @@ def pages_with_tag(tag): {"title": e["title"], "path": os.path.relpath(e["path"], current_dir)} for e in entries ] - - -def on_pre_page_macros(env): - # env.env (the actual Jinja Environment) doesn't exist yet at define_env() - # time - on_config() creates it afterwards - so patch it here instead. - # configure_safe_includes() is idempotent, so re-running per page is cheap. - configure_safe_includes(env.env) diff --git a/mkdocs_hooks.py b/mkdocs_hooks.py index 55cc3c425..dd04856f0 100644 --- a/mkdocs_hooks.py +++ b/mkdocs_hooks.py @@ -13,80 +13,9 @@ import re import yaml -from jinja2.compiler import CodeGenerator -from jinja2.exceptions import TemplateNotFound module_list_path = os.getenv("MODULE_LIST_PATH", "docs/assets/module-list.json") - -class SafeIncludeCodeGenerator(CodeGenerator): - """Makes every `{% include %}` in this environment render as nothing - instead of raising, if the included template errors while rendering. - - Partials driven by module-list.json data (versions, network licences, - ...) can fail on one app's unexpected/missing data. Without this, that - error propagates and takes out the *whole* page (mkdocs-macros' error - block) or the *whole* build (the supported-apps index renders every app - inline via the theme's own Jinja env, outside mkdocs-macros' handling). - - `{% extends %}` and `{% import %}` are untouched (separate visit_* - methods) - only `{% include %}` gets this treatment. - - Set as `environment.code_generator_class` (an official Jinja extension - point - see Environment.code_generator_class), it replaces the include's - normal streaming codegen with a call to `environment.safe_include()`, - which renders the target as one buffered string (so a mid-render failure - can't leak partial output) and swallows/logs any exception. - """ - - def visit_Include(self, node, frame): - self.writeline("yield environment.safe_include(", node) - self.visit(node.template, frame) - if node.with_context: - self.write(f", {{**context.get_all(), **{self.dump_local_context(frame)}}}") - else: - self.write(", {}") - self.write(f", {node.ignore_missing!r})") - - -def make_safe_include(jinja_env): - """Build the `environment.safe_include()` called by the codegen above. - - A closure (not a method) bound to one specific environment via - `env.safe_include = make_safe_include(env)`, since generated template - code looks it up as a plain attribute (`environment.safe_include(...)`) - rather than through the descriptor protocol. - """ - def safe_include(name, context_dict, ignore_missing=False): - try: - template = (jinja_env.select_template(name) - if isinstance(name, (list, tuple)) - else jinja_env.get_template(name)) - except TemplateNotFound: - if ignore_missing: - return "" - raise - try: - return template.render(context_dict) - except Exception as e: - print(f"::WARNING file={name},title=include_failed,col=0,endColumn=0,line=0::{e}") - return "" - return safe_include - - -def configure_safe_includes(jinja_env): - """Make every `{% include %}` rendered by `jinja_env` fail soft. - - Idempotent - safe to call more than once on the same environment (e.g. - macro_hooks.py's on_pre_page_macros(), which fires per page). - """ - if getattr(jinja_env, "_safe_includes_configured", False): - return - jinja_env.code_generator_class = SafeIncludeCodeGenerator - jinja_env.safe_include = make_safe_include(jinja_env) - jinja_env._safe_includes_configured = True - - _FRONT_MATTER = re.compile(r"\A---\s*\n(.*?)\n---\s*\n", re.DOTALL) @@ -161,7 +90,6 @@ def on_env(env, config, files, **kwargs): for app in applications.values() for domain in app.get("domains", []) }) - configure_safe_includes(env) def lint(*args, **kwargs):