docs: build manual with mdbook
Switch the HTML manual renderer to mdBook while preserving existing docs package outputs and legacy redirect paths.
This commit is contained in:
parent
9b2afbdd47
commit
6aaf73d18d
11 changed files with 676 additions and 45 deletions
|
|
@ -253,9 +253,15 @@ let
|
|||
# Generate the HTML manual pages
|
||||
home-manager-manual = pkgs.callPackage ./home-manager-manual.nix {
|
||||
home-manager-options = {
|
||||
home-manager = hmOptionsDocs.optionsJSON;
|
||||
nixos = nixosOptionsDocs.optionsJSON;
|
||||
nix-darwin = nixDarwinOptionsDocs.optionsJSON;
|
||||
home-manager = {
|
||||
json = hmOptionsDocs.optionsJSON;
|
||||
};
|
||||
nixos = {
|
||||
json = nixosOptionsDocs.optionsJSON;
|
||||
};
|
||||
nix-darwin = {
|
||||
json = nixDarwinOptionsDocs.optionsJSON;
|
||||
};
|
||||
};
|
||||
inherit revision;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,63 +1,99 @@
|
|||
{
|
||||
stdenv,
|
||||
lib,
|
||||
documentation-highlighter,
|
||||
callPackage,
|
||||
mdbook,
|
||||
python3,
|
||||
revision,
|
||||
home-manager-options,
|
||||
nixos-render-docs,
|
||||
}:
|
||||
let
|
||||
outputPath = "share/doc/home-manager";
|
||||
mdbookOptions = callPackage ./mdbook/options.nix {
|
||||
manpageUrls = ./manual/manpage-urls.json;
|
||||
inherit revision;
|
||||
optionDocs = {
|
||||
home-manager = {
|
||||
title = "Home Manager Configuration Options";
|
||||
path = "home-manager";
|
||||
prefix = "opt-";
|
||||
json = "${home-manager-options.home-manager.json}";
|
||||
};
|
||||
nixos = {
|
||||
title = "NixOS Configuration Options";
|
||||
path = "nixos";
|
||||
prefix = "nixos-opt-";
|
||||
json = "${home-manager-options.nixos.json}";
|
||||
};
|
||||
nix-darwin = {
|
||||
title = "nix-darwin Configuration Options";
|
||||
path = "nix-darwin";
|
||||
prefix = "nix-darwin-opt-";
|
||||
json = "${home-manager-options.nix-darwin.json}";
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
stdenv.mkDerivation {
|
||||
name = "home-manager-manual";
|
||||
|
||||
nativeBuildInputs = [ nixos-render-docs ];
|
||||
nativeBuildInputs = [
|
||||
mdbook
|
||||
python3
|
||||
];
|
||||
|
||||
src = ./manual;
|
||||
src = ./.;
|
||||
|
||||
buildPhase = ''
|
||||
mkdir -p out/{highlightjs,media}
|
||||
runHook preBuild
|
||||
|
||||
cp -t out/highlightjs \
|
||||
${documentation-highlighter}/highlight.pack.js \
|
||||
${documentation-highlighter}/LICENSE \
|
||||
${documentation-highlighter}/mono-blue.css \
|
||||
${documentation-highlighter}/loader.js
|
||||
mkdir -p source
|
||||
python3 ${./mdbook/convert-markup.py} "$src/manual" source
|
||||
python3 ${./mdbook/convert-markup.py} \
|
||||
--base-depth 1 \
|
||||
"$src/release-notes" \
|
||||
source/release-notes
|
||||
|
||||
substituteInPlace ./options.md \
|
||||
--subst-var-by \
|
||||
OPTIONS_JSON \
|
||||
${home-manager-options.home-manager}/share/doc/nixos/options.json
|
||||
cp -r ${mdbookOptions}/options source/options
|
||||
|
||||
substituteInPlace ./nixos-options.md \
|
||||
--subst-var-by \
|
||||
OPTIONS_JSON \
|
||||
${home-manager-options.nixos}/share/doc/nixos/options.json
|
||||
python3 ${./mdbook/substitute-summary.py} \
|
||||
source/SUMMARY.md \
|
||||
${mdbookOptions}/summary/home-manager.md \
|
||||
${mdbookOptions}/summary/nixos.md \
|
||||
${mdbookOptions}/summary/nix-darwin.md
|
||||
|
||||
substituteInPlace ./nix-darwin-options.md \
|
||||
--subst-var-by \
|
||||
OPTIONS_JSON \
|
||||
${home-manager-options.nix-darwin}/share/doc/nixos/options.json
|
||||
mdbook build source --dest-dir book
|
||||
|
||||
cp ${./options.html} out/options.html
|
||||
mkdir -p out
|
||||
cp -r book/* out/
|
||||
|
||||
cp ${./static/style.css} out/style.css
|
||||
cp ${./static/anchor-links.js} out/anchor-links.js
|
||||
makeRedirect() {
|
||||
local target=$1
|
||||
local destination=$2
|
||||
printf '%s\n' \
|
||||
'<!doctype html>' \
|
||||
'<html lang="en">' \
|
||||
' <head>' \
|
||||
' <meta charset="utf-8">' \
|
||||
" <meta http-equiv=\"refresh\" content=\"0; url=$destination\">" \
|
||||
" <link rel=\"canonical\" href=\"$destination\">" \
|
||||
' <title>Redirecting...</title>' \
|
||||
' </head>' \
|
||||
' <body>' \
|
||||
" <p>Redirecting to <a href=\"$destination\">$destination</a>.</p>" \
|
||||
' </body>' \
|
||||
'</html>' \
|
||||
> "out/$target"
|
||||
}
|
||||
|
||||
cp -r ${./release-notes} release-notes
|
||||
makeRedirect index.xhtml index.html
|
||||
makeRedirect options.html options/home-manager/index.html
|
||||
makeRedirect options.xhtml options/home-manager/index.html
|
||||
makeRedirect nixos-options.xhtml options/nixos/index.html
|
||||
makeRedirect nix-darwin-options.xhtml options/nix-darwin/index.html
|
||||
makeRedirect release-notes.xhtml release-notes/release-notes.html
|
||||
|
||||
nixos-render-docs manual html \
|
||||
--manpage-urls ./manpage-urls.json \
|
||||
--revision ${lib.trivial.revisionWithDefault revision} \
|
||||
--stylesheet style.css \
|
||||
--script highlightjs/highlight.pack.js \
|
||||
--script highlightjs/loader.js \
|
||||
--script anchor-links.js \
|
||||
--toc-depth 1 \
|
||||
--section-toc-depth 1 \
|
||||
manual.md \
|
||||
out/index.xhtml
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
|
|
@ -69,7 +105,9 @@ stdenv.mkDerivation {
|
|||
echo "doc manual $dest index.html" >> $out/nix-support/hydra-build-products
|
||||
'';
|
||||
|
||||
passthru = { inherit home-manager-options; };
|
||||
passthru = {
|
||||
inherit home-manager-options mdbookOptions;
|
||||
};
|
||||
|
||||
meta = {
|
||||
maintainers = [ lib.maintainers.considerate ];
|
||||
|
|
|
|||
|
|
@ -25,9 +25,14 @@ let
|
|||
if [[ ! -v BROWSER || -z $BROWSER ]]; then
|
||||
echo "$0: unable to start a web browser; please set \$BROWSER"
|
||||
exit 1
|
||||
else
|
||||
exec "$BROWSER" "${html}/share/doc/${pathName}/index.xhtml"
|
||||
fi
|
||||
|
||||
manualPath="${html}/share/doc/${pathName}/index.html"
|
||||
if [[ ! -e $manualPath ]]; then
|
||||
manualPath="${html}/share/doc/${pathName}/index.xhtml"
|
||||
fi
|
||||
|
||||
exec "$BROWSER" "$manualPath"
|
||||
'';
|
||||
|
||||
desktopItem = makeDesktopItem {
|
||||
|
|
|
|||
64
docs/manual/SUMMARY.md
Normal file
64
docs/manual/SUMMARY.md
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# Summary
|
||||
|
||||
- [Preface](preface.md)
|
||||
- [Introduction to Home Manager](introduction.md)
|
||||
- [Installing Home Manager](installation.md)
|
||||
- [Standalone installation](installation/standalone.md)
|
||||
- [NixOS module](installation/nixos.md)
|
||||
- [nix-darwin module](installation/nix-darwin.md)
|
||||
- [Using Home Manager](usage.md)
|
||||
- [Configuration Example](usage/configuration.md)
|
||||
- [Rollbacks](usage/rollbacks.md)
|
||||
- [Keeping your ~ safe from harm](usage/dotfiles.md)
|
||||
- [Graphical services](usage/graphical.md)
|
||||
- [GPU on non-NixOS systems](usage/gpu-non-nixos.md)
|
||||
- [Modular Services](usage/modular-services.md)
|
||||
- [Updating](usage/updating.md)
|
||||
- [Upgrading to a new Home Manager release](usage/upgrading.md)
|
||||
- [Nix Flakes](nix-flakes.md)
|
||||
- [Prerequisites](nix-flakes/prerequisites.md)
|
||||
- [Standalone setup](nix-flakes/standalone.md)
|
||||
- [NixOS module](nix-flakes/nixos.md)
|
||||
- [nix-darwin module](nix-flakes/nix-darwin.md)
|
||||
- [flake-parts module](nix-flakes/flake-parts.md)
|
||||
- [Writing Home Manager Modules](writing-modules.md)
|
||||
- [Option Types](writing-modules/types.md)
|
||||
- [Contributing](contributing.md)
|
||||
- [Getting started](contributing/getting-started.md)
|
||||
- [Guidelines](contributing/guidelines.md)
|
||||
- [Release Notes](contributing/release-notes.md)
|
||||
- [News](contributing/news.md)
|
||||
- [Tests](contributing/tests.md)
|
||||
- [Home Manager Internals](internals.md)
|
||||
- [Activation](internals/activation.md)
|
||||
- [Third-Party Tools and Extensions](3rd-party.md)
|
||||
- [Module Collections](3rd-party/collections.md)
|
||||
- [Frequently Asked Questions (FAQ)](faq.md)
|
||||
- [Why is there a collision error when switching generation?](faq/collision.md)
|
||||
- [Why are the session variables not set?](faq/session-variables.md)
|
||||
- [How to set up a configuration for multiple users/machines?](faq/multiple-users-machines.md)
|
||||
- [Why do I get an error message about `ca.desrt.dconf` or `dconf.service`?](faq/ca-desrt-dconf.md)
|
||||
- [How do I install packages from Nixpkgs unstable?](faq/unstable.md)
|
||||
- [How do I change the package used by a module?](faq/change-package-module.md)
|
||||
- [Options](options.md)
|
||||
@HOME_MANAGER_OPTIONS@
|
||||
@NIXOS_OPTIONS@
|
||||
@NIX_DARWIN_OPTIONS@
|
||||
- [Release Notes](release-notes/release-notes.md)
|
||||
- [Release 26.11](release-notes/rl-2611.md)
|
||||
- [Release 26.05](release-notes/rl-2605.md)
|
||||
- [Release 25.11](release-notes/rl-2511.md)
|
||||
- [Release 25.05](release-notes/rl-2505.md)
|
||||
- [Release 24.11](release-notes/rl-2411.md)
|
||||
- [Release 24.05](release-notes/rl-2405.md)
|
||||
- [Release 23.11](release-notes/rl-2311.md)
|
||||
- [Release 23.05](release-notes/rl-2305.md)
|
||||
- [Release 22.11](release-notes/rl-2211.md)
|
||||
- [Release 22.05](release-notes/rl-2205.md)
|
||||
- [Release 21.11](release-notes/rl-2111.md)
|
||||
- [Release 21.05](release-notes/rl-2105.md)
|
||||
- [Release 20.09](release-notes/rl-2009.md)
|
||||
- [Release 20.03](release-notes/rl-2003.md)
|
||||
- [Release 19.09](release-notes/rl-1909.md)
|
||||
- [Release 19.03](release-notes/rl-1903.md)
|
||||
- [Release 18.09](release-notes/rl-1809.md)
|
||||
11
docs/manual/book.toml
Normal file
11
docs/manual/book.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[book]
|
||||
title = "Home Manager Manual"
|
||||
language = "en"
|
||||
src = "."
|
||||
|
||||
[output.html]
|
||||
git-repository-url = "https://github.com/nix-community/home-manager"
|
||||
|
||||
[output.html.fold]
|
||||
enable = true
|
||||
level = 0
|
||||
|
|
@ -108,7 +108,7 @@ Manager Git repository:
|
|||
|
||||
``` shell
|
||||
$ nix-build -A docs.html
|
||||
$ xdg-open ./result/share/doc/home-manager/index.xhtml
|
||||
$ xdg-open ./result/share/doc/home-manager/index.html
|
||||
```
|
||||
|
||||
When you have made changes to a module, it is a good idea to check that
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use Home Manager's `master` branch.
|
|||
|
||||
Home Manager follows NixOS release cycles and provides corresponding branches:
|
||||
|
||||
- **release-<version>**: Stable branch for the matching NixOS or
|
||||
- **release-\<version\>**: Stable branch for the matching NixOS or
|
||||
Nixpkgs release, such as `release-25.11`.
|
||||
|
||||
- **master**: Development branch (tracks nixpkgs-unstable)
|
||||
|
|
|
|||
231
docs/mdbook/convert-markup.py
Normal file
231
docs/mdbook/convert-markup.py
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import html
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
SIMPLE_ROLES = (
|
||||
"command",
|
||||
"component",
|
||||
"description",
|
||||
"file",
|
||||
"index",
|
||||
"system",
|
||||
"type",
|
||||
)
|
||||
|
||||
HEADING_ANCHOR = re.compile(r"^(#{1,6}\s+)(.*)\s+\{#([^}]+)\}\s*$")
|
||||
INLINE_ANCHOR = re.compile(r"\[\]\{#([^}]+)\}")
|
||||
OPTION_ROLE = re.compile(r"(?<![$`])\{option\}`([^`]*)`")
|
||||
SIMPLE_ROLE = re.compile(r"(?<![$`])\{(" + "|".join(SIMPLE_ROLES) + r")\}`([^`]*)`")
|
||||
OPTION_LINK = re.compile(
|
||||
r"\[(?P<label>[^\]]*)\]\(#(?P<anchor>(?:opt|nixos-opt|nix-darwin-opt)-[^)]+)\)"
|
||||
)
|
||||
LEFTOVER_ROLE = re.compile(
|
||||
r"(?<![$`])\{(" + "|".join(("option", *SIMPLE_ROLES)) + r")\}`[^`]*`"
|
||||
)
|
||||
FENCE = re.compile(r"^\s*(`{3,})(.*)$")
|
||||
FENCE_CLOSE = re.compile(r"^\s*`{3,}\s*$")
|
||||
ADMONITION_OPEN = re.compile(r"^\s*:::\s*\{\.(note|warning|example)\}\s*$")
|
||||
ADMONITION_CLOSE = re.compile(r"^\s*:::\s*$")
|
||||
DEEP_SPLIT_NAMESPACES = {"programs", "services"}
|
||||
|
||||
|
||||
def option_target(anchor: str, current_file: Path, base_depth: int) -> str:
|
||||
if anchor.startswith("nix-darwin-opt-"):
|
||||
option = anchor.removeprefix("nix-darwin-opt-")
|
||||
option = option.replace("<", "_").replace(">", "_")
|
||||
anchor = f"nix-darwin-opt-{option}"
|
||||
base = "options/nix-darwin"
|
||||
elif anchor.startswith("nixos-opt-"):
|
||||
option = anchor.removeprefix("nixos-opt-")
|
||||
option = option.replace("<", "_").replace(">", "_")
|
||||
anchor = f"nixos-opt-{option}"
|
||||
base = "options/nixos"
|
||||
else:
|
||||
option = anchor.removeprefix("opt-")
|
||||
option = option.replace("<", "_").replace(">", "_")
|
||||
anchor = f"opt-{option}"
|
||||
base = "options/home-manager"
|
||||
|
||||
page_parts = option_page_parts(option)
|
||||
prefix = "../" * (base_depth + len(current_file.parent.parts))
|
||||
return f"{prefix}{base}/{'/'.join(page_parts)}.md#{anchor}"
|
||||
|
||||
|
||||
def option_page_parts(option_name: str) -> list[str]:
|
||||
parts = option_name.split(".")
|
||||
namespace = parts[0]
|
||||
if namespace in DEEP_SPLIT_NAMESPACES and len(parts) > 1:
|
||||
return parts[:2]
|
||||
return [namespace]
|
||||
|
||||
|
||||
def option_label(anchor: str) -> str:
|
||||
if anchor.startswith("nix-darwin-opt-"):
|
||||
return anchor.removeprefix("nix-darwin-opt-")
|
||||
if anchor.startswith("nixos-opt-"):
|
||||
return anchor.removeprefix("nixos-opt-")
|
||||
return anchor.removeprefix("opt-")
|
||||
|
||||
|
||||
def markdown_label(value: str) -> str:
|
||||
return value.replace("<", "<").replace(">", ">")
|
||||
|
||||
|
||||
def convert_inline(line: str, current_file: Path, base_depth: int) -> str:
|
||||
line = line.replace("index.xhtml", "index.html")
|
||||
line = INLINE_ANCHOR.sub(
|
||||
lambda match: f'<a id="{html.escape(match.group(1), quote=True)}"></a>',
|
||||
line,
|
||||
)
|
||||
line = OPTION_ROLE.sub(
|
||||
lambda match: (
|
||||
f"[{markdown_label(match.group(1))}]"
|
||||
f"({option_target(f'opt-{match.group(1)}', current_file, base_depth)})"
|
||||
),
|
||||
line,
|
||||
)
|
||||
line = OPTION_LINK.sub(
|
||||
lambda match: (
|
||||
f"[{markdown_label(match.group('label') or option_label(match.group('anchor')))}]"
|
||||
f"({option_target(match.group('anchor'), current_file, base_depth)})"
|
||||
),
|
||||
line,
|
||||
)
|
||||
return SIMPLE_ROLE.sub(lambda match: f"`{match.group(2)}`", line)
|
||||
|
||||
|
||||
def convert_heading(line: str, current_file: Path, base_depth: int) -> str:
|
||||
match = HEADING_ANCHOR.match(line)
|
||||
if match is None:
|
||||
return convert_inline(line, current_file, base_depth)
|
||||
|
||||
prefix, title, anchor = match.groups()
|
||||
return (
|
||||
f'<a id="{html.escape(anchor, quote=True)}"></a>\n'
|
||||
f"{prefix}{convert_inline(title, current_file, base_depth)}"
|
||||
)
|
||||
|
||||
|
||||
def is_include_fence(line: str) -> tuple[bool, str]:
|
||||
match = FENCE.match(line)
|
||||
if match is None:
|
||||
return False, ""
|
||||
|
||||
info = match.group(2).strip()
|
||||
return info.startswith(("{=include=}", "include")), match.group(1)
|
||||
|
||||
|
||||
def convert_markdown(
|
||||
text: str,
|
||||
source: Path,
|
||||
current_file: Path,
|
||||
base_depth: int,
|
||||
) -> str:
|
||||
output: list[str] = []
|
||||
in_code_fence = False
|
||||
code_fence = ""
|
||||
in_include = False
|
||||
include_fence = ""
|
||||
in_admonition = False
|
||||
|
||||
for raw_line in text.splitlines(keepends=True):
|
||||
line = raw_line[:-1] if raw_line.endswith("\n") else raw_line
|
||||
newline = "\n" if raw_line.endswith("\n") else ""
|
||||
|
||||
if in_include:
|
||||
if FENCE_CLOSE.match(line) and len(line.strip()) >= len(include_fence):
|
||||
in_include = False
|
||||
include_fence = ""
|
||||
continue
|
||||
|
||||
if in_code_fence:
|
||||
output.append(raw_line)
|
||||
if FENCE_CLOSE.match(line) and len(line.strip()) >= len(code_fence):
|
||||
in_code_fence = False
|
||||
code_fence = ""
|
||||
continue
|
||||
|
||||
include, fence = is_include_fence(line)
|
||||
if include:
|
||||
in_include = True
|
||||
include_fence = fence
|
||||
continue
|
||||
|
||||
fence_match = FENCE.match(line)
|
||||
if fence_match is not None:
|
||||
in_code_fence = True
|
||||
code_fence = fence_match.group(1)
|
||||
output.append(raw_line)
|
||||
continue
|
||||
|
||||
if in_admonition:
|
||||
if ADMONITION_CLOSE.match(line):
|
||||
in_admonition = False
|
||||
continue
|
||||
converted = convert_inline(line, current_file, base_depth)
|
||||
output.append(f"> {converted}{newline}" if converted else ">\n")
|
||||
continue
|
||||
|
||||
admonition = ADMONITION_OPEN.match(line)
|
||||
if admonition is not None:
|
||||
in_admonition = True
|
||||
output.append(f"> **{admonition.group(1).title()}**\n")
|
||||
continue
|
||||
|
||||
output.append(convert_heading(line, current_file, base_depth) + newline)
|
||||
|
||||
if in_include:
|
||||
raise ValueError(f"{source}: unterminated include block")
|
||||
|
||||
converted = "".join(output)
|
||||
if LEFTOVER_ROLE.search(converted):
|
||||
raise ValueError(f"{source}: unconverted NixOS-render-docs role remains")
|
||||
if "```{=include=}" in converted:
|
||||
raise ValueError(f"{source}: unconverted include block remains")
|
||||
|
||||
return converted
|
||||
|
||||
|
||||
def convert_tree(source: Path, destination: Path, base_depth: int) -> None:
|
||||
for path in source.rglob("*"):
|
||||
if not path.is_file():
|
||||
continue
|
||||
|
||||
target = destination / path.relative_to(source)
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if path.suffix == ".md":
|
||||
text = path.read_text(encoding="utf-8")
|
||||
target.write_text(
|
||||
convert_markdown(text, path, path.relative_to(source), base_depth),
|
||||
encoding="utf-8",
|
||||
)
|
||||
else:
|
||||
shutil.copy2(path, target)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--base-depth", type=int, default=0)
|
||||
parser.add_argument("source", type=Path)
|
||||
parser.add_argument("destination", type=Path)
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.source.is_dir():
|
||||
print(f"missing source directory: {args.source}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
args.destination.mkdir(parents=True, exist_ok=True)
|
||||
convert_tree(args.source, args.destination, args.base_depth)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
23
docs/mdbook/options.nix
Normal file
23
docs/mdbook/options.nix
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
pkgs,
|
||||
optionDocs,
|
||||
manpageUrls,
|
||||
revision,
|
||||
}:
|
||||
|
||||
pkgs.runCommand "home-manager-mdbook-options"
|
||||
{
|
||||
nativeBuildInputs = [
|
||||
pkgs.buildPackages.nixos-render-docs
|
||||
pkgs.buildPackages.python3
|
||||
];
|
||||
optionDocsJson = builtins.toJSON optionDocs;
|
||||
passAsFile = [ "optionDocsJson" ];
|
||||
}
|
||||
''
|
||||
python3 ${./render-options.py} \
|
||||
"$optionDocsJsonPath" \
|
||||
${manpageUrls} \
|
||||
${revision} \
|
||||
"$out"
|
||||
''
|
||||
221
docs/mdbook/render-options.py
Normal file
221
docs/mdbook/render-options.py
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
OPTION_LINK = re.compile(
|
||||
r"\[(?P<label>[^\]]*)\]\(#(?P<anchor>(?:opt|nixos-opt|nix-darwin-opt)-[^)]+)\)"
|
||||
)
|
||||
OPTION_HREF = re.compile(r'href="#(?P<anchor>(?:opt|nixos-opt|nix-darwin-opt)-[^"]+)"')
|
||||
DEEP_SPLIT_NAMESPACES = {"programs", "services"}
|
||||
|
||||
|
||||
def option_label(anchor: str) -> str:
|
||||
if anchor.startswith("nix-darwin-opt-"):
|
||||
return anchor.removeprefix("nix-darwin-opt-")
|
||||
if anchor.startswith("nixos-opt-"):
|
||||
return anchor.removeprefix("nixos-opt-")
|
||||
return anchor.removeprefix("opt-")
|
||||
|
||||
|
||||
def option_target(anchor: str, current_file: Path) -> str:
|
||||
if anchor.startswith("nix-darwin-opt-"):
|
||||
option = anchor.removeprefix("nix-darwin-opt-")
|
||||
base = "options/nix-darwin"
|
||||
elif anchor.startswith("nixos-opt-"):
|
||||
option = anchor.removeprefix("nixos-opt-")
|
||||
base = "options/nixos"
|
||||
else:
|
||||
option = anchor.removeprefix("opt-")
|
||||
base = "options/home-manager"
|
||||
|
||||
page_parts = option_page_parts(option)
|
||||
prefix = "../" * len(current_file.parent.parts)
|
||||
return f"{prefix}{base}/{'/'.join(page_parts)}.md#{anchor}"
|
||||
|
||||
|
||||
def rewrite_option_links(text: str, current_file: Path) -> str:
|
||||
text = OPTION_LINK.sub(
|
||||
lambda match: (
|
||||
f"[{match.group('label') or option_label(match.group('anchor'))}]"
|
||||
f"({option_target(match.group('anchor'), current_file)})"
|
||||
),
|
||||
text,
|
||||
)
|
||||
return OPTION_HREF.sub(
|
||||
lambda match: f'href="{option_target(match.group("anchor"), current_file)}"',
|
||||
text,
|
||||
)
|
||||
|
||||
|
||||
def namespace_for(option_name: str) -> str:
|
||||
return option_name.split(".", 1)[0]
|
||||
|
||||
|
||||
def option_page_parts(option_name: str) -> list[str]:
|
||||
parts = option_name.split(".")
|
||||
namespace = parts[0]
|
||||
if namespace in DEEP_SPLIT_NAMESPACES and len(parts) > 1:
|
||||
return parts[:2]
|
||||
return [namespace]
|
||||
|
||||
|
||||
def option_group_for(option_name: str) -> str:
|
||||
return "/".join(option_page_parts(option_name))
|
||||
|
||||
|
||||
def render_namespace(
|
||||
options: dict[str, object],
|
||||
destination: Path,
|
||||
manpage_urls: Path,
|
||||
revision: str,
|
||||
anchor_prefix: str,
|
||||
current_file: Path,
|
||||
) -> None:
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
source = destination.with_suffix(".json")
|
||||
source.write_text(json.dumps(options, sort_keys=True), encoding="utf-8")
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
"nixos-render-docs",
|
||||
"options",
|
||||
"commonmark",
|
||||
"--manpage-urls",
|
||||
str(manpage_urls),
|
||||
"--revision",
|
||||
revision,
|
||||
"--anchor-style",
|
||||
"legacy",
|
||||
"--anchor-prefix",
|
||||
anchor_prefix,
|
||||
str(source),
|
||||
str(destination),
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
source.unlink()
|
||||
destination.write_text(
|
||||
rewrite_option_links(destination.read_text(encoding="utf-8"), current_file),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def write_doc(
|
||||
name: str,
|
||||
doc: dict[str, str],
|
||||
manpage_urls: Path,
|
||||
revision: str,
|
||||
output: Path,
|
||||
) -> None:
|
||||
options_json = Path(doc["json"]) / "share/doc/nixos/options.json"
|
||||
options = json.loads(options_json.read_text(encoding="utf-8"))
|
||||
|
||||
grouped: dict[str, dict[str, object]] = {}
|
||||
for option_name, option in options.items():
|
||||
group = option_group_for(option_name)
|
||||
grouped.setdefault(group, {})[option_name] = option
|
||||
|
||||
doc_dir = output / "options" / doc["path"]
|
||||
doc_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
groups = sorted(grouped)
|
||||
namespaces = sorted({group.split("/", 1)[0] for group in groups})
|
||||
nested = {
|
||||
namespace: sorted(
|
||||
group for group in groups if group.startswith(f"{namespace}/")
|
||||
)
|
||||
for namespace in namespaces
|
||||
}
|
||||
index = [
|
||||
f"# {doc['title']}\n",
|
||||
"\n",
|
||||
"Generated from Home Manager option definitions.\n",
|
||||
"\n",
|
||||
"## Namespaces\n",
|
||||
"\n",
|
||||
]
|
||||
for namespace in namespaces:
|
||||
if nested[namespace]:
|
||||
index.append(f"- [{namespace}]({namespace}/index.md)\n")
|
||||
else:
|
||||
index.append(f"- [{namespace}]({namespace}.md)\n")
|
||||
(doc_dir / "index.md").write_text("".join(index), encoding="utf-8")
|
||||
|
||||
summary_dir = output / "summary"
|
||||
summary_dir.mkdir(parents=True, exist_ok=True)
|
||||
summary = [f" - [{doc['title']}](options/{doc['path']}/index.md)\n"]
|
||||
|
||||
for namespace in namespaces:
|
||||
if nested[namespace]:
|
||||
namespace_index = doc_dir / namespace / "index.md"
|
||||
namespace_index.parent.mkdir(parents=True, exist_ok=True)
|
||||
namespace_index.write_text(
|
||||
"".join(
|
||||
[
|
||||
f"# {namespace}\n",
|
||||
"\n",
|
||||
"## Modules\n",
|
||||
"\n",
|
||||
*(
|
||||
f"- [{group.split('/', 1)[1]}]({group.split('/', 1)[1]}.md)\n"
|
||||
for group in nested[namespace]
|
||||
),
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
summary.append(
|
||||
f" - [{namespace}](options/{doc['path']}/{namespace}/index.md)\n"
|
||||
)
|
||||
for group in nested[namespace]:
|
||||
module_name = group.split("/", 1)[1]
|
||||
summary.append(
|
||||
f" - [{module_name}](options/{doc['path']}/{namespace}/{module_name}.md)\n"
|
||||
)
|
||||
else:
|
||||
summary.append(
|
||||
f" - [{namespace}](options/{doc['path']}/{namespace}.md)\n"
|
||||
)
|
||||
|
||||
for group in groups:
|
||||
page = doc_dir / f"{group}.md"
|
||||
current_file = Path("options") / doc["path"] / f"{group}.md"
|
||||
render_namespace(
|
||||
grouped[group],
|
||||
page,
|
||||
manpage_urls,
|
||||
revision,
|
||||
doc["prefix"],
|
||||
current_file,
|
||||
)
|
||||
|
||||
(summary_dir / f"{name}.md").write_text("".join(summary), encoding="utf-8")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 5:
|
||||
print(
|
||||
"usage: render-options.py OPTION_DOCS MANPAGE_URLS REVISION OUT",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
option_docs = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
||||
manpage_urls = Path(sys.argv[2])
|
||||
revision = sys.argv[3]
|
||||
output = Path(sys.argv[4])
|
||||
|
||||
for name, doc in option_docs.items():
|
||||
write_doc(name, doc, manpage_urls, revision, output)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
32
docs/mdbook/substitute-summary.py
Normal file
32
docs/mdbook/substitute-summary.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 5:
|
||||
print(
|
||||
"usage: substitute-summary.py SUMMARY HM_SUMMARY NIXOS_SUMMARY DARWIN_SUMMARY",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
summary = Path(sys.argv[1])
|
||||
replacements = {
|
||||
"@HOME_MANAGER_OPTIONS@": Path(sys.argv[2]).read_text(encoding="utf-8"),
|
||||
"@NIXOS_OPTIONS@": Path(sys.argv[3]).read_text(encoding="utf-8"),
|
||||
"@NIX_DARWIN_OPTIONS@": Path(sys.argv[4]).read_text(encoding="utf-8"),
|
||||
}
|
||||
|
||||
text = summary.read_text(encoding="utf-8")
|
||||
for needle, replacement in replacements.items():
|
||||
text = text.replace(needle, replacement.rstrip())
|
||||
|
||||
summary.write_text(text, encoding="utf-8")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue