docs: replace sed invocation with an mdbook preprocessor for @docroot@
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
This commit is contained in:
parent
9166babbaf
commit
c355354772
3 changed files with 94 additions and 5 deletions
|
@ -7,6 +7,15 @@ additional-js = ["redirects.js"]
|
||||||
edit-url-template = "https://github.com/NixOS/nix/tree/master/doc/manual/{path}"
|
edit-url-template = "https://github.com/NixOS/nix/tree/master/doc/manual/{path}"
|
||||||
git-repository-url = "https://github.com/NixOS/nix"
|
git-repository-url = "https://github.com/NixOS/nix"
|
||||||
|
|
||||||
|
# Handles replacing @docroot@ with a path to ./src relative to that markdown file.
|
||||||
|
[preprocessor.docroot]
|
||||||
|
renderers = ["html", "linkcheck"]
|
||||||
|
command = "python3 doc/manual/docroot.py"
|
||||||
|
# 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 opposite.
|
||||||
|
after = ["links"]
|
||||||
|
|
||||||
[preprocessor.anchors]
|
[preprocessor.anchors]
|
||||||
renderers = ["html"]
|
renderers = ["html"]
|
||||||
command = "jq --from-file doc/manual/anchors.jq"
|
command = "jq --from-file doc/manual/anchors.jq"
|
||||||
|
|
84
doc/manual/docroot.py
Executable file
84
doc/manual/docroot.py
Executable file
|
@ -0,0 +1,84 @@
|
||||||
|
#!/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
|
|
@ -159,17 +159,13 @@ doc/manual/generated/man1/nix3-manpages: $(d)/src/command-ref/new-cli
|
||||||
done
|
done
|
||||||
@touch $@
|
@touch $@
|
||||||
|
|
||||||
$(docdir)/manual/index.html: $(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.md
|
$(docdir)/manual/index.html: $(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.md $(d)/docroot.py
|
||||||
$(trace-gen) \
|
$(trace-gen) \
|
||||||
tmp="$$(mktemp -d)"; \
|
tmp="$$(mktemp -d)"; \
|
||||||
cp -r doc/manual "$$tmp"; \
|
cp -r doc/manual "$$tmp"; \
|
||||||
find "$$tmp" -name '*.md' | while read -r file; do \
|
find "$$tmp" -name '*.md' | while read -r file; do \
|
||||||
doc/manual/process-includes.sh $$file $$file; \
|
doc/manual/process-includes.sh $$file $$file; \
|
||||||
done; \
|
done; \
|
||||||
find "$$tmp" -name '*.md' | while read -r file; do \
|
|
||||||
docroot="$$(realpath --relative-to="$$(dirname "$$file")" $$tmp/manual/src)"; \
|
|
||||||
sed -i "s,@docroot@,$$docroot,g" "$$file"; \
|
|
||||||
done; \
|
|
||||||
set -euo pipefail; \
|
set -euo pipefail; \
|
||||||
RUST_LOG=warn mdbook build "$$tmp/manual" -d $(DESTDIR)$(docdir)/manual.tmp 2>&1 \
|
RUST_LOG=warn mdbook build "$$tmp/manual" -d $(DESTDIR)$(docdir)/manual.tmp 2>&1 \
|
||||||
| { grep -Fv "because fragment resolution isn't implemented" || :; }; \
|
| { grep -Fv "because fragment resolution isn't implemented" || :; }; \
|
||||||
|
|
Loading…
Reference in a new issue