forked from lix-project/lix
We're not entirely clear on why the links preprocessor has to be done
*before* rather than after, but we assume it is probably that as a
builtin preprocessor it does some processing on the raw book source,
and not just the JSON data.
Also a real use for Python pattern matching? I know I was surprised too.
Change-Id: Ibe8b59e7b5bd5f357a655d8b4c5f0b0f58a67d6b
85 lines
3 KiB
Python
Executable file
85 lines
3 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
|
|
from pathlib import Path
|
|
import json
|
|
import os, os.path
|
|
import sys
|
|
|
|
name = 'process-docroot.py'
|
|
|
|
def log(*args, **kwargs):
|
|
kwargs['file'] = sys.stderr
|
|
return print(f'{name}:', *args, **kwargs)
|
|
|
|
def replace_docroot(relative_md_path: Path, content: str, book_root: Path):
|
|
assert not relative_md_path.is_absolute(), f'{relative_md_path=} from mdbook should be relative'
|
|
|
|
md_path_abs = book_root / relative_md_path
|
|
docroot_abs = md_path_abs.parent
|
|
assert docroot_abs.is_dir(), f'supposed docroot {docroot_abs} is not a directory (cwd={os.getcwd()})'
|
|
|
|
# The paths mdbook gives us are relative to the directory with book.toml.
|
|
# @docroot@ wants to be replaced with the path relative to `src/`.
|
|
docroot_rel = os.path.relpath(book_root / 'src', start=docroot_abs)
|
|
|
|
return content.replace('@docroot@', docroot_rel)
|
|
|
|
def recursive_replace(data, book_root):
|
|
match data:
|
|
case {'sections': sections}:
|
|
return data | dict(
|
|
sections = [recursive_replace(section, book_root) for section in sections],
|
|
)
|
|
case {'Chapter': chapter}:
|
|
# Path to the .md file for this chapter, relative to book_root.
|
|
path_to_chapter = Path('src') / chapter['path']
|
|
chapter_content = chapter['content']
|
|
|
|
return data | dict(
|
|
Chapter = chapter | dict(
|
|
content = replace_docroot(path_to_chapter, chapter_content, book_root),
|
|
sub_items = [recursive_replace(sub_item, book_root) 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':
|
|
log('confirming to mdbook that we support their stuff')
|
|
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 *not* @docroot@. @docroot@ gets replaced with a relative path to `./src/`.
|
|
# book_root is the directory where book.toml, aka `src`'s parent.
|
|
book_root = Path(context['root'])
|
|
assert book_root.exists(), f'{book_root=} does not exist'
|
|
assert book_root.joinpath('book.toml').is_file(), f'{book_root / "book.toml"} is not a file'
|
|
|
|
log('replacing all occurrences of @docroot@ with a relative path')
|
|
|
|
# Find @docroot@ in all parts of our recursive book structure.
|
|
replaced_content = recursive_replace(book, book_root)
|
|
|
|
replaced_content_str = json.dumps(replaced_content)
|
|
|
|
# Give mdbook our changes.
|
|
print(replaced_content_str)
|
|
|
|
log('done!')
|
|
|
|
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}')
|
|
raise
|