From 925dc2fe30dd76458a0d7a74e4eb5cb3509842bd Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Thu, 2 Jan 2025 18:56:42 +0100 Subject: [PATCH 1/3] nixosOptionsDoc/optionsCommonMark: Add extraFlags attr --- nixos/lib/make-options-doc/default.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nixos/lib/make-options-doc/default.nix b/nixos/lib/make-options-doc/default.nix index a6ff4be92c80..25af32773a74 100644 --- a/nixos/lib/make-options-doc/default.nix +++ b/nixos/lib/make-options-doc/default.nix @@ -193,12 +193,16 @@ rec { optionsCommonMark = pkgs.runCommand "options.md" { + __structuredAttrs = true; nativeBuildInputs = [ pkgs.nixos-render-docs ]; + # For overriding + extraArgs = [ ]; } '' nixos-render-docs -j $NIX_BUILD_CORES options commonmark \ --manpage-urls ${pkgs.path + "/doc/manpage-urls.json"} \ --revision ${lib.escapeShellArg revision} \ + ''${extraArgs[@]} \ ${optionsJSON}/share/doc/nixos/options.json \ $out ''; From 9e0b42e0f7891b692e1338a5f868922697c95add Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Thu, 2 Jan 2025 20:56:34 +0100 Subject: [PATCH 2/3] nixos-render-docs: Add anchor support to CommonMark options output --- .../src/nixos_render_docs/options.py | 47 +++++++++++++++++-- .../src/nixos_render_docs/types.py | 5 ++ .../src/tests/sample_options_simple.json | 17 +++++++ .../tests/sample_options_simple_default.md | 13 +++++ .../src/tests/sample_options_simple_legacy.md | 13 +++++ .../src/tests/test_options.py | 27 +++++++++++ 6 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 pkgs/by-name/ni/nixos-render-docs/src/tests/sample_options_simple.json create mode 100644 pkgs/by-name/ni/nixos-render-docs/src/tests/sample_options_simple_default.md create mode 100644 pkgs/by-name/ni/nixos-render-docs/src/tests/sample_options_simple_legacy.md diff --git a/pkgs/by-name/ni/nixos-render-docs/src/nixos_render_docs/options.py b/pkgs/by-name/ni/nixos-render-docs/src/nixos_render_docs/options.py index fcd5af4ffe31..75fbeadce1d0 100644 --- a/pkgs/by-name/ni/nixos-render-docs/src/nixos_render_docs/options.py +++ b/pkgs/by-name/ni/nixos-render-docs/src/nixos_render_docs/options.py @@ -21,7 +21,7 @@ from .html import HTMLRenderer from .manpage import ManpageRenderer, man_escape from .manual_structure import make_xml_id, XrefTarget from .md import Converter, md_escape, md_make_code -from .types import OptionLoc, Option, RenderedOption +from .types import OptionLoc, Option, RenderedOption, AnchorStyle def option_is(option: Option, key: str, typ: str) -> Optional[dict[str, str]]: if key not in option: @@ -317,10 +317,15 @@ class OptionsCommonMarkRenderer(OptionDocsRestrictions, CommonMarkRenderer): class CommonMarkConverter(BaseConverter[OptionsCommonMarkRenderer]): __option_block_separator__ = "" + _anchor_style: AnchorStyle + _anchor_prefix: str - def __init__(self, manpage_urls: Mapping[str, str], revision: str): + + def __init__(self, manpage_urls: Mapping[str, str], revision: str, anchor_style: AnchorStyle = AnchorStyle.NONE, anchor_prefix: str = ""): super().__init__(revision) self._renderer = OptionsCommonMarkRenderer(manpage_urls) + self._anchor_style = anchor_style + self._anchor_prefix = anchor_prefix def _parallel_render_prepare(self) -> Any: return (self._renderer._manpage_urls, self._revision) @@ -342,11 +347,21 @@ class CommonMarkConverter(BaseConverter[OptionsCommonMarkRenderer]): def _decl_def_footer(self) -> list[str]: return [] + def _make_anchor_suffix(self, loc: list[str]) -> str: + if self._anchor_style == AnchorStyle.NONE: + return "" + elif self._anchor_style == AnchorStyle.LEGACY: + sanitized = ".".join(map(make_xml_id, loc)) + return f" {{#{self._anchor_prefix}{sanitized}}}" + else: + raise RuntimeError("unhandled anchor style", self._anchor_style) + def finalize(self) -> str: result = [] for (name, opt) in self._sorted_options(): - result.append(f"## {md_escape(name)}\n") + anchor_suffix = self._make_anchor_suffix(opt.loc) + result.append(f"## {md_escape(name)}{anchor_suffix}\n") result += opt.lines result.append("\n\n") @@ -490,9 +505,30 @@ def _build_cli_manpage(p: argparse.ArgumentParser) -> None: p.add_argument("infile") p.add_argument("outfile") +def parse_anchor_style(value: str|AnchorStyle) -> AnchorStyle: + if isinstance(value, AnchorStyle): + # Used by `argparse.add_argument`'s `default` + return value + try: + return AnchorStyle(value.lower()) + except ValueError: + raise argparse.ArgumentTypeError(f"Invalid value {value}\nExpected one of {', '.join(style.value for style in AnchorStyle)}") + def _build_cli_commonmark(p: argparse.ArgumentParser) -> None: p.add_argument('--manpage-urls', required=True) p.add_argument('--revision', required=True) + p.add_argument( + '--anchor-style', + required=False, + default=AnchorStyle.NONE.value, + choices = [style.value for style in AnchorStyle], + help = "(default: %(default)s) Anchor style to use for links to options. \nOnly none is standard CommonMark." + ) + p.add_argument('--anchor-prefix', + required=False, + default="", + help="(default: no prefix) String to prepend to anchor ids. Not used when anchor style is none." + ) p.add_argument("infile") p.add_argument("outfile") @@ -527,7 +563,10 @@ def _run_cli_manpage(args: argparse.Namespace) -> None: def _run_cli_commonmark(args: argparse.Namespace) -> None: with open(args.manpage_urls, 'r') as manpage_urls: - md = CommonMarkConverter(json.load(manpage_urls), revision = args.revision) + md = CommonMarkConverter(json.load(manpage_urls), + revision = args.revision, + anchor_style = parse_anchor_style(args.anchor_style), + anchor_prefix = args.anchor_prefix) with open(args.infile, 'r') as f: md.add_options(json.load(f)) diff --git a/pkgs/by-name/ni/nixos-render-docs/src/nixos_render_docs/types.py b/pkgs/by-name/ni/nixos-render-docs/src/nixos_render_docs/types.py index 1d586ca240f7..b5c6e91a9b03 100644 --- a/pkgs/by-name/ni/nixos-render-docs/src/nixos_render_docs/types.py +++ b/pkgs/by-name/ni/nixos-render-docs/src/nixos_render_docs/types.py @@ -1,4 +1,5 @@ from collections.abc import Sequence +from enum import Enum from typing import Callable, Optional, NamedTuple from markdown_it.token import Token @@ -12,3 +13,7 @@ class RenderedOption(NamedTuple): links: Optional[list[str]] = None RenderFn = Callable[[Token, Sequence[Token], int], str] + +class AnchorStyle(Enum): + NONE = "none" + LEGACY = "legacy" diff --git a/pkgs/by-name/ni/nixos-render-docs/src/tests/sample_options_simple.json b/pkgs/by-name/ni/nixos-render-docs/src/tests/sample_options_simple.json new file mode 100644 index 000000000000..9b5f5d9ba1be --- /dev/null +++ b/pkgs/by-name/ni/nixos-render-docs/src/tests/sample_options_simple.json @@ -0,0 +1,17 @@ +{ + "services.frobnicator.types..enable": { + "declarations": [ + "nixos/modules/services/frobnicator.nix" + ], + "description": "Whether to enable the frobnication of this (``) type.", + "loc": [ + "services", + "frobnicator", + "types", + "", + "enable" + ], + "readOnly": false, + "type": "boolean" + } +} diff --git a/pkgs/by-name/ni/nixos-render-docs/src/tests/sample_options_simple_default.md b/pkgs/by-name/ni/nixos-render-docs/src/tests/sample_options_simple_default.md new file mode 100644 index 000000000000..3256b2acaf01 --- /dev/null +++ b/pkgs/by-name/ni/nixos-render-docs/src/tests/sample_options_simple_default.md @@ -0,0 +1,13 @@ +## services\.frobnicator\.types\.\\.enable + +Whether to enable the frobnication of this (` `) type\. + + + +*Type:* +boolean + +*Declared by:* + - [\](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/frobnicator.nix) + + diff --git a/pkgs/by-name/ni/nixos-render-docs/src/tests/sample_options_simple_legacy.md b/pkgs/by-name/ni/nixos-render-docs/src/tests/sample_options_simple_legacy.md new file mode 100644 index 000000000000..3ae36bf2b762 --- /dev/null +++ b/pkgs/by-name/ni/nixos-render-docs/src/tests/sample_options_simple_legacy.md @@ -0,0 +1,13 @@ +## services\.frobnicator\.types\.\\.enable {#opt-services.frobnicator.types._name_.enable} + +Whether to enable the frobnication of this (` `) type\. + + + +*Type:* +boolean + +*Declared by:* + - [\](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/frobnicator.nix) + + diff --git a/pkgs/by-name/ni/nixos-render-docs/src/tests/test_options.py b/pkgs/by-name/ni/nixos-render-docs/src/tests/test_options.py index 9499f83df525..5793b9c912b4 100644 --- a/pkgs/by-name/ni/nixos-render-docs/src/tests/test_options.py +++ b/pkgs/by-name/ni/nixos-render-docs/src/tests/test_options.py @@ -1,6 +1,9 @@ import nixos_render_docs +from nixos_render_docs.options import AnchorStyle +import json from markdown_it.token import Token +from pathlib import Path import pytest def test_option_headings() -> None: @@ -12,3 +15,27 @@ def test_option_headings() -> None: type='heading_open', tag='h1', nesting=1, attrs={}, map=[0, 1], level=0, children=None, content='', markup='#', info='', meta={}, block=True, hidden=False ) + +def test_options_commonmark() -> None: + c = nixos_render_docs.options.CommonMarkConverter({}, 'local') + with Path('tests/sample_options_simple.json').open() as f: + opts = json.load(f) + assert opts is not None + with Path('tests/sample_options_simple_default.md').open() as f: + expected = f.read() + + c.add_options(opts) + s = c.finalize() + assert s == expected + +def test_options_commonmark_legacy_anchors() -> None: + c = nixos_render_docs.options.CommonMarkConverter({}, 'local', anchor_style = AnchorStyle.LEGACY, anchor_prefix = 'opt-') + with Path('tests/sample_options_simple.json').open() as f: + opts = json.load(f) + assert opts is not None + with Path('tests/sample_options_simple_legacy.md').open() as f: + expected = f.read() + + c.add_options(opts) + s = c.finalize() + assert s == expected From e2078ef31e1a02e23fe261a9a07fe419fd4a2192 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Thu, 2 Jan 2025 22:38:08 +0100 Subject: [PATCH 3/3] tests.nixosOptionsDoc: init --- nixos/lib/make-options-doc/default.nix | 2 + nixos/lib/make-options-doc/tests.nix | 59 ++++++++++++++++++++++++++ pkgs/test/default.nix | 2 + 3 files changed, 63 insertions(+) create mode 100644 nixos/lib/make-options-doc/tests.nix diff --git a/nixos/lib/make-options-doc/default.nix b/nixos/lib/make-options-doc/default.nix index 25af32773a74..ea24c4004e55 100644 --- a/nixos/lib/make-options-doc/default.nix +++ b/nixos/lib/make-options-doc/default.nix @@ -1,3 +1,5 @@ +# Tests: ./tests.nix + /** Generates documentation for [nix modules](https://nix.dev/tutorials/module-system/index.html). diff --git a/nixos/lib/make-options-doc/tests.nix b/nixos/lib/make-options-doc/tests.nix new file mode 100644 index 000000000000..795c0ff4fe00 --- /dev/null +++ b/nixos/lib/make-options-doc/tests.nix @@ -0,0 +1,59 @@ +# Run tests: nix-build -A tests.nixosOptionsDoc + +{ + lib, + nixosOptionsDoc, + runCommand, +}: +let + inherit (lib) mkOption types; + + eval = lib.evalModules { + modules = [ + { + options.foo.bar.enable = mkOption { + type = types.bool; + default = false; + description = '' + Enable the foo bar feature. + ''; + }; + } + ]; + }; + + doc = nixosOptionsDoc { + inherit (eval) options; + }; +in +{ + /** + Test that + - the `nixosOptionsDoc` function can be invoked + - integration of the module system and `nixosOptionsDoc` (limited coverage) + + The more interesting tests happen in the `nixos-render-docs` package. + */ + commonMark = + runCommand "test-nixosOptionsDoc-commonMark" + { + commonMarkDefault = doc.optionsCommonMark; + commonMarkAnchors = doc.optionsCommonMark.overrideAttrs { + extraArgs = [ + "--anchor-prefix" + "my-opt-" + "--anchor-style" + "legacy" + ]; + }; + } + '' + env | grep ^commonMark | sed -e 's/=/ = /' + ( + set -x + grep -F 'foo\.bar\.enable' $commonMarkDefault >/dev/null + grep -F '{#my-opt-foo.bar.enable}' $commonMarkAnchors >/dev/null + ) + touch $out + ''; +} diff --git a/pkgs/test/default.nix b/pkgs/test/default.nix index 5f5e1bc07ba5..78b1ad61ea8e 100644 --- a/pkgs/test/default.nix +++ b/pkgs/test/default.nix @@ -155,6 +155,8 @@ with pkgs; nixos-functions = callPackage ./nixos-functions { }; + nixosOptionsDoc = callPackage ../../nixos/lib/make-options-doc/tests.nix { }; + overriding = callPackage ./overriding.nix { }; texlive = callPackage ./texlive { };