forked from lix-project/lix
docs: generalize replacement script
let's have a script that can't replaced only docroot, but also other
variables. this could be used to generate manual fragments elsewhere
and have mdbook include them from an absolute path, for example.
Change-Id: I81aadddfc79462bf057c2de683dd270056a99e90
This commit is contained in:
parent
c32ee6f69b
commit
c136475a0c
|
@ -9,8 +9,8 @@ git-repository-url = "https://github.com/NixOS/nix"
|
||||||
|
|
||||||
# Handles replacing @docroot@ with a path to ./src relative to that markdown file.
|
# Handles replacing @docroot@ with a path to ./src relative to that markdown file.
|
||||||
[preprocessor.docroot]
|
[preprocessor.docroot]
|
||||||
renderers = ["html", "linkcheck"]
|
command = "python3 doc/manual/substitute.py docroot"
|
||||||
command = "python3 doc/manual/docroot.py"
|
replace-kind = "relative-to-book-src"
|
||||||
# I would have thought that @docroot@ replacement had to be done *before*
|
# I would have thought that @docroot@ replacement had to be done *before*
|
||||||
# the link preprocessor gets its hands on this book, but nope it's actually
|
# the link preprocessor gets its hands on this book, but nope it's actually
|
||||||
# the opposite.
|
# the opposite.
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
#!/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
|
|
|
@ -147,7 +147,7 @@ doc/manual/generated/man1/nix3-manpages: $(d)/src/command-ref/new-cli
|
||||||
done
|
done
|
||||||
@touch $@
|
@touch $@
|
||||||
|
|
||||||
doc/manual/generated/out: $(MANUAL_SRCS) $(d)/book.toml $(d)/anchors.jq $(d)/custom.css $(d)/src/SUMMARY.md $(d)/src/command-ref/new-cli $(d)/src/contributing/experimental-feature-descriptions.md $(d)/src/command-ref/conf-file.md $(d)/src/language/builtins.md $(d)/src/language/builtin-constants.md $(d)/src/release-notes/rl-next-generated.md $(d)/docroot.py
|
doc/manual/generated/out: $(MANUAL_SRCS) $(d)/book.toml $(d)/anchors.jq $(d)/custom.css $(d)/src/SUMMARY.md $(d)/src/command-ref/new-cli $(d)/src/contributing/experimental-feature-descriptions.md $(d)/src/command-ref/conf-file.md $(d)/src/language/builtins.md $(d)/src/language/builtin-constants.md $(d)/src/release-notes/rl-next-generated.md $(d)/substitute.py
|
||||||
@rm -rf $@
|
@rm -rf $@
|
||||||
$(trace-gen) \
|
$(trace-gen) \
|
||||||
RUST_LOG=warn mdbook build doc/manual -d generated/out 2>&1 \
|
RUST_LOG=warn mdbook build doc/manual -d generated/out 2>&1 \
|
||||||
|
|
95
doc/manual/substitute.py
Executable file
95
doc/manual/substitute.py
Executable file
|
@ -0,0 +1,95 @@
|
||||||
|
#!/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 relative_to(var: str, source_root: Path):
|
||||||
|
def replace(relative_md_path: Path, content: str):
|
||||||
|
nonlocal source_root
|
||||||
|
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()})'
|
||||||
|
|
||||||
|
# @var@ wants to be replaced with the path relative to source_root.
|
||||||
|
var_rel = os.path.relpath(source_root, start=var_abs)
|
||||||
|
|
||||||
|
return content.replace(f'@{var}@', var_rel)
|
||||||
|
|
||||||
|
return replace
|
||||||
|
|
||||||
|
def recursive_replace(data, do_replace):
|
||||||
|
match data:
|
||||||
|
case {'sections': sections}:
|
||||||
|
return data | dict(
|
||||||
|
sections = [recursive_replace(section, do_replace) for section in sections],
|
||||||
|
)
|
||||||
|
case {'Chapter': chapter}:
|
||||||
|
path_to_chapter = Path(chapter['path'])
|
||||||
|
chapter_content = chapter['content']
|
||||||
|
|
||||||
|
return data | dict(
|
||||||
|
Chapter = chapter | dict(
|
||||||
|
content = do_replace(path_to_chapter, chapter_content),
|
||||||
|
sub_items = [
|
||||||
|
recursive_replace(sub_item, do_replace)
|
||||||
|
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():
|
||||||
|
|
||||||
|
var = sys.argv[1]
|
||||||
|
|
||||||
|
if len(sys.argv) > 2 and sys.argv[2] == '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)
|
||||||
|
config = context['config']['preprocessor'][var]
|
||||||
|
|
||||||
|
if config['replace-kind'] == "relative-to-book-src":
|
||||||
|
kind = "relative to book sources"
|
||||||
|
|
||||||
|
# book_root is the directory where book contents leave (ie, src/)
|
||||||
|
book_root = Path(context['root']) / context['config']['book']['src']
|
||||||
|
replace = relative_to(var, book_root)
|
||||||
|
else:
|
||||||
|
assert False, "need a replace-kind"
|
||||||
|
|
||||||
|
log(f'replacing all occurrences of @{var}@ with a path {kind}')
|
||||||
|
|
||||||
|
# Find @var@ in all parts of our recursive book structure.
|
||||||
|
replaced_content = recursive_replace(book, replace)
|
||||||
|
|
||||||
|
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
|
Loading…
Reference in a new issue