Unverified Commit 45e44c56 authored by Naïm Favier's avatar Naïm Favier Committed by GitHub
Browse files

Merge pull request #217342 from pennae/nrd-html-manual

nixos-render-docs: add manual html renderer, use it for the nixos manual
parents e3d3bf7f 54f4992e
Loading
Loading
Loading
Loading
+61 −29
Original line number Diff line number Diff line
@@ -135,12 +135,7 @@ let
    }
  '';

  manual-combined = runCommand "nixos-manual-combined"
    { inputs = lib.sourceFilesBySuffices ./. [ ".xml" ".md" ];
      nativeBuildInputs = [ pkgs.nixos-render-docs pkgs.libxml2.bin pkgs.libxslt.bin ];
      meta.description = "The NixOS manual as plain docbook XML";
    }
    ''
  prepareManualFromMD = ''
    cp -r --no-preserve=all $inputs/* .

    substituteInPlace ./manual.md \
@@ -157,6 +152,15 @@ let
      --replace \
        '@NIXOS_TEST_OPTIONS_JSON@' \
        ${testOptionsDoc.optionsJSON}/share/doc/nixos/options.json
  '';

  manual-combined = runCommand "nixos-manual-combined"
    { inputs = lib.sourceFilesBySuffices ./. [ ".xml" ".md" ];
      nativeBuildInputs = [ pkgs.nixos-render-docs pkgs.libxml2.bin pkgs.libxslt.bin ];
      meta.description = "The NixOS manual as plain docbook XML";
    }
    ''
      ${prepareManualFromMD}

      nixos-render-docs -j $NIX_BUILD_CORES manual docbook \
        --manpage-urls ${manpageUrls} \
@@ -193,7 +197,14 @@ in rec {

  # Generate the NixOS manual.
  manualHTML = runCommand "nixos-manual-html"
    { nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ];
    { nativeBuildInputs =
        if allowDocBook then [
          buildPackages.libxml2.bin
          buildPackages.libxslt.bin
        ] else [
          buildPackages.nixos-render-docs
        ];
      inputs = lib.optionals (! allowDocBook) (lib.sourceFilesBySuffices ./. [ ".md" ]);
      meta.description = "The NixOS manual in HTML format";
      allowedReferences = ["out"];
    }
@@ -201,6 +212,12 @@ in rec {
      # Generate the HTML manual.
      dst=$out/share/doc/nixos
      mkdir -p $dst

      cp ${../../../doc/style.css} $dst/style.css
      cp ${../../../doc/overrides.css} $dst/overrides.css
      cp -r ${pkgs.documentation-highlighter} $dst/highlightjs

      ${if allowDocBook then ''
          xsltproc \
            ${manualXsltprocOptions} \
            --stringparam id.warnings "1" \
@@ -213,10 +230,25 @@ in rec {

          mkdir -p $dst/images/callouts
          cp ${docbook_xsl_ns}/xml/xsl/docbook/images/callouts/*.svg $dst/images/callouts/
        '' else ''
          ${prepareManualFromMD}

      cp ${../../../doc/style.css} $dst/style.css
      cp ${../../../doc/overrides.css} $dst/overrides.css
      cp -r ${pkgs.documentation-highlighter} $dst/highlightjs
          # TODO generator is set like this because the docbook/md manual compare workflow will
          # trigger if it's different
          nixos-render-docs -j $NIX_BUILD_CORES manual html \
            --manpage-urls ${manpageUrls} \
            --revision ${lib.escapeShellArg revision} \
            --generator "DocBook XSL Stylesheets V${docbook_xsl_ns.version}" \
            --stylesheet style.css \
            --stylesheet overrides.css \
            --stylesheet highlightjs/mono-blue.css \
            --script ./highlightjs/highlight.pack.js \
            --script ./highlightjs/loader.js \
            --toc-depth 1 \
            --chunk-toc-depth 1 \
            ./manual.md \
            $dst/index.html
        ''}

      mkdir -p $out/nix-support
      echo "nix-build out $out" >> $out/nix-support/hydra-build-products
+4 −1
Original line number Diff line number Diff line
@@ -47,7 +47,10 @@ development/development.md
contributing-to-this-manual.chapter.md
```

```{=include=} appendix
```{=include=} appendix html:into-file=//options.html
nixos-options.md
```

```{=include=} appendix html:into-file=//release-notes.html
release-notes/release-notes.md
```
+2 −2
Original line number Diff line number Diff line
@@ -318,8 +318,8 @@ to make packages available in the chroot.
{option}`services.systemd.akkoma.serviceConfig.BindPaths` and
{option}`services.systemd.akkoma.serviceConfig.BindReadOnlyPaths` permit access to outside paths
through bind mounts. Refer to
[{manpage}`systemd.exec(5)`](https://www.freedesktop.org/software/systemd/man/systemd.exec.html#BindPaths=)
for details.
[`BindPaths=`](https://www.freedesktop.org/software/systemd/man/systemd.exec.html#BindPaths=)
of {manpage}`systemd.exec(5)` for details.

### Distributed deployment {#modules-services-akkoma-distributed-deployment}

+1 −1
Original line number Diff line number Diff line
@@ -1948,7 +1948,7 @@ in
          Extra command-line arguments to pass to systemd-networkd-wait-online.
          These also affect per-interface `systemd-network-wait-online@` services.

          See [{manpage}`systemd-networkd-wait-online.service(8)`](https://www.freedesktop.org/software/systemd/man/systemd-networkd-wait-online.service.html) for all available options.
          See {manpage}`systemd-networkd-wait-online.service(8)` for all available options.
        '';
        type = with types; listOf str;
        default = [];
+49 −95
Original line number Diff line number Diff line
from collections.abc import Mapping, MutableMapping, Sequence
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from typing import Any, cast, Optional
from urllib.parse import quote

from .md import Renderer

import markdown_it
from markdown_it.token import Token
from markdown_it.utils import OptionsDict

_asciidoc_escapes = {
    # escape all dots, just in case one is pasted at SOL
@@ -59,8 +57,8 @@ class AsciiDocRenderer(Renderer):
    _list_stack: list[List]
    _attrspans: list[str]

    def __init__(self, manpage_urls: Mapping[str, str], parser: Optional[markdown_it.MarkdownIt] = None):
        super().__init__(manpage_urls, parser)
    def __init__(self, manpage_urls: Mapping[str, str]):
        super().__init__(manpage_urls)
        self._parstack = [ Par("\n\n", "====") ]
        self._list_stack = []
        self._attrspans = []
@@ -96,142 +94,103 @@ class AsciiDocRenderer(Renderer):
        self._list_stack.pop()
        return ""

    def text(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
             env: MutableMapping[str, Any]) -> str:
    def text(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        self._parstack[-1].continuing = True
        return asciidoc_escape(token.content)
    def paragraph_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                       env: MutableMapping[str, Any]) -> str:
    def paragraph_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return self._break()
    def paragraph_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                        env: MutableMapping[str, Any]) -> str:
    def paragraph_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return ""
    def hardbreak(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                  env: MutableMapping[str, Any]) -> str:
    def hardbreak(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return " +\n"
    def softbreak(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                  env: MutableMapping[str, Any]) -> str:
    def softbreak(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return f" "
    def code_inline(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                    env: MutableMapping[str, Any]) -> str:
    def code_inline(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        self._parstack[-1].continuing = True
        return f"``{asciidoc_escape(token.content)}``"
    def code_block(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                   env: MutableMapping[str, Any]) -> str:
        return self.fence(token, tokens, i, options, env)
    def link_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                  env: MutableMapping[str, Any]) -> str:
    def code_block(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return self.fence(token, tokens, i)
    def link_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        self._parstack[-1].continuing = True
        return f"link:{quote(cast(str, token.attrs['href']), safe='/:')}["
    def link_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                   env: MutableMapping[str, Any]) -> str:
    def link_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return "]"
    def list_item_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                       env: MutableMapping[str, Any]) -> str:
    def list_item_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        self._enter_block(True)
        # allow the next token to be a block or an inline.
        return f'\n{self._list_stack[-1].head} {{empty}}'
    def list_item_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                        env: MutableMapping[str, Any]) -> str:
    def list_item_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        self._leave_block()
        return "\n"
    def bullet_list_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                         env: MutableMapping[str, Any]) -> str:
    def bullet_list_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return self._list_open(token, '*')
    def bullet_list_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                          env: MutableMapping[str, Any]) -> str:
    def bullet_list_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return self._list_close()
    def em_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                env: MutableMapping[str, Any]) -> str:
    def em_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return "__"
    def em_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                 env: MutableMapping[str, Any]) -> str:
    def em_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return "__"
    def strong_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                    env: MutableMapping[str, Any]) -> str:
    def strong_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return "**"
    def strong_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                     env: MutableMapping[str, Any]) -> str:
    def strong_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return "**"
    def fence(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
              env: MutableMapping[str, Any]) -> str:
    def fence(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        attrs = f"[source,{token.info}]\n" if token.info else ""
        code = token.content
        if code.endswith('\n'):
            code = code[:-1]
        return f"{self._break(True)}{attrs}----\n{code}\n----"
    def blockquote_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                        env: MutableMapping[str, Any]) -> str:
    def blockquote_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        pbreak = self._break(True)
        self._enter_block(False)
        return f"{pbreak}[quote]\n{self._parstack[-2].block_delim}\n"
    def blockquote_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                         env: MutableMapping[str, Any]) -> str:
    def blockquote_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        self._leave_block()
        return f"\n{self._parstack[-1].block_delim}"
    def note_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                  env: MutableMapping[str, Any]) -> str:
    def note_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return self._admonition_open("NOTE")
    def note_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                   env: MutableMapping[str, Any]) -> str:
    def note_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return self._admonition_close()
    def caution_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                     env: MutableMapping[str, Any]) -> str:
    def caution_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return self._admonition_open("CAUTION")
    def caution_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                      env: MutableMapping[str, Any]) -> str:
    def caution_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return self._admonition_close()
    def important_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                       env: MutableMapping[str, Any]) -> str:
    def important_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return self._admonition_open("IMPORTANT")
    def important_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                        env: MutableMapping[str, Any]) -> str:
    def important_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return self._admonition_close()
    def tip_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                 env: MutableMapping[str, Any]) -> str:
    def tip_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return self._admonition_open("TIP")
    def tip_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                  env: MutableMapping[str, Any]) -> str:
    def tip_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return self._admonition_close()
    def warning_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                     env: MutableMapping[str, Any]) -> str:
    def warning_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return self._admonition_open("WARNING")
    def warning_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                      env: MutableMapping[str, Any]) -> str:
    def warning_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return self._admonition_close()
    def dl_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                env: MutableMapping[str, Any]) -> str:
    def dl_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return f"{self._break()}[]"
    def dl_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                 env: MutableMapping[str, Any]) -> str:
    def dl_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return ""
    def dt_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                env: MutableMapping[str, Any]) -> str:
    def dt_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return self._break()
    def dt_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                 env: MutableMapping[str, Any]) -> str:
    def dt_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        self._enter_block(True)
        return ":: {empty}"
    def dd_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                env: MutableMapping[str, Any]) -> str:
    def dd_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return ""
    def dd_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                 env: MutableMapping[str, Any]) -> str:
    def dd_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        self._leave_block()
        return "\n"
    def myst_role(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                  env: MutableMapping[str, Any]) -> str:
    def myst_role(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        self._parstack[-1].continuing = True
        content = asciidoc_escape(token.content)
        if token.meta['name'] == 'manpage' and (url := self._manpage_urls.get(token.content)):
            return f"link:{quote(url, safe='/:')}[{content}]"
        return f"[.{token.meta['name']}]``{asciidoc_escape(token.content)}``"
    def inline_anchor(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                      env: MutableMapping[str, Any]) -> str:
    def inline_anchor(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        self._parstack[-1].continuing = True
        return f"[[{token.attrs['id']}]]"
    def attr_span_begin(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                        env: MutableMapping[str, Any]) -> str:
    def attr_span_begin(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        self._parstack[-1].continuing = True
        (id_part, class_part) = ("", "")
        if id := token.attrs.get('id'):
@@ -241,22 +200,17 @@ class AsciiDocRenderer(Renderer):
                class_part = "kbd:["
                self._attrspans.append("]")
            else:
                return super().attr_span_begin(token, tokens, i, options, env)
                return super().attr_span_begin(token, tokens, i)
        else:
            self._attrspans.append("")
        return id_part + class_part
    def attr_span_end(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                      env: MutableMapping[str, Any]) -> str:
    def attr_span_end(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return self._attrspans.pop()
    def heading_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                     env: MutableMapping[str, Any]) -> str:
    def heading_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return token.markup.replace("#", "=") + " "
    def heading_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                      env: MutableMapping[str, Any]) -> str:
    def heading_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return "\n"
    def ordered_list_open(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                          env: MutableMapping[str, Any]) -> str:
    def ordered_list_open(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return self._list_open(token, '.')
    def ordered_list_close(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
                           env: MutableMapping[str, Any]) -> str:
    def ordered_list_close(self, token: Token, tokens: Sequence[Token], i: int) -> str:
        return self._list_close()
Loading