aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/manual/book.toml9
-rwxr-xr-xdoc/manual/docroot.py84
-rw-r--r--doc/manual/local.mk6
3 files changed, 94 insertions, 5 deletions
diff --git a/doc/manual/book.toml b/doc/manual/book.toml
index 73fb7e75e..1d14347a4 100644
--- a/doc/manual/book.toml
+++ b/doc/manual/book.toml
@@ -7,6 +7,15 @@ additional-js = ["redirects.js"]
edit-url-template = "https://github.com/NixOS/nix/tree/master/doc/manual/{path}"
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]
renderers = ["html"]
command = "jq --from-file doc/manual/anchors.jq"
diff --git a/doc/manual/docroot.py b/doc/manual/docroot.py
new file mode 100755
index 000000000..e95f8abbf
--- /dev/null
+++ b/doc/manual/docroot.py
@@ -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
diff --git a/doc/manual/local.mk b/doc/manual/local.mk
index 955357607..e7c029727 100644
--- a/doc/manual/local.mk
+++ b/doc/manual/local.mk
@@ -159,17 +159,13 @@ doc/manual/generated/man1/nix3-manpages: $(d)/src/command-ref/new-cli
done
@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) \
tmp="$$(mktemp -d)"; \
cp -r doc/manual "$$tmp"; \
find "$$tmp" -name '*.md' | while read -r file; do \
doc/manual/process-includes.sh $$file $$file; \
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; \
RUST_LOG=warn mdbook build "$$tmp/manual" -d $(DESTDIR)$(docdir)/manual.tmp 2>&1 \
| { grep -Fv "because fragment resolution isn't implemented" || :; }; \