diff options
Diffstat (limited to 'doc/manual/substitute.py')
-rwxr-xr-x | doc/manual/substitute.py | 101 |
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 |