aboutsummaryrefslogtreecommitdiff
path: root/doc/manual/substitute.py
diff options
context:
space:
mode:
Diffstat (limited to 'doc/manual/substitute.py')
-rwxr-xr-xdoc/manual/substitute.py101
1 files changed, 101 insertions, 0 deletions
diff --git a/doc/manual/substitute.py b/doc/manual/substitute.py
new file mode 100755
index 000000000..18b7bed68
--- /dev/null
+++ b/doc/manual/substitute.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+
+from pathlib import Path
+import json
+import os, os.path
+import sys
+
+name = 'substitute.py'
+
+def log(*args, **kwargs):
+ kwargs['file'] = sys.stderr
+ return print(f'{name}:', *args, **kwargs)
+
+def do_include(content: str, relative_md_path: Path, source_root: Path, search_path: Path):
+ assert not relative_md_path.is_absolute(), f'{relative_md_path=} from mdbook should be relative'
+
+ md_path_abs = source_root / relative_md_path
+ var_abs = md_path_abs.parent
+ assert var_abs.is_dir(), f'supposed directory {var_abs} is not a directory (cwd={os.getcwd()})'
+
+ lines = []
+ for l in content.splitlines(keepends=True):
+ if l.strip().startswith("{{#include "):
+ requested = l.strip()[11:][:-2]
+ if requested.startswith("@generated@/"):
+ included = search_path / Path(requested[12:])
+ requested = included.relative_to(search_path)
+ else:
+ included = source_root / relative_md_path.parent / requested
+ requested = included.resolve().relative_to(source_root)
+ assert included.exists(), f"{requested} not found at {included}"
+ lines.append(do_include(included.read_text(), requested, source_root, search_path) + "\n")
+ else:
+ lines.append(l)
+ return "".join(lines)
+
+def recursive_replace(data, book_root, search_path):
+ match data:
+ case {'sections': sections}:
+ return data | dict(
+ sections = [recursive_replace(section, book_root, search_path) for section in sections],
+ )
+ case {'Chapter': chapter}:
+ path_to_chapter = Path(chapter['path'])
+ chapter_content = chapter['content']
+
+ return data | dict(
+ Chapter = chapter | dict(
+ # first process includes. this must happen before docroot processing since
+ # mdbook does not see these included files, only the final agglomeration.
+ content = do_include(
+ chapter_content,
+ path_to_chapter,
+ book_root,
+ search_path
+ ).replace(
+ '@docroot@',
+ ("../" * len(path_to_chapter.parent.parts) or "./")[:-1]
+ ),
+ sub_items = [
+ recursive_replace(sub_item, book_root, search_path)
+ for sub_item in chapter['sub_items']
+ ],
+ ),
+ )
+
+ case rest:
+ assert False, f'should have been called on a dict, not {type(rest)=}\n\t{rest=}'
+
+def main():
+
+ if len(sys.argv) > 1 and sys.argv[1] == 'supports':
+ return 0
+
+ # mdbook communicates with us over stdin and stdout.
+ # It splorks us a JSON array, the first element describing the context,
+ # the second element describing the book itself,
+ # and then expects us to send it the modified book JSON over stdout.
+
+ context, book = json.load(sys.stdin)
+
+ # book_root is the directory where book contents leave (ie, src/)
+ book_root = Path(context['root']) / context['config']['book']['src']
+
+ # includes pointing into @generated@ will look here
+ search_path = Path(os.environ['MDBOOK_SUBSTITUTE_SEARCH'])
+
+ # Find @var@ in all parts of our recursive book structure.
+ replaced_content = recursive_replace(book, book_root, search_path)
+
+ replaced_content_str = json.dumps(replaced_content)
+
+ # Give mdbook our changes.
+ print(replaced_content_str)
+
+try:
+ sys.exit(main())
+except AssertionError as e:
+ print(f'{name}: INTERNAL ERROR in mdbook preprocessor', file=sys.stderr)
+ print(f'this is a bug in {name}', file=sys.stderr)
+ raise