diff options
author | pennae <github@quasiparticle.net> | 2023-06-21 16:58:23 +0200 |
---|---|---|
committer | pennae <github@quasiparticle.net> | 2023-07-01 20:27:28 +0200 |
commit | 8c2d14a6b850458020fd4ce079a90ee87e64c7a6 (patch) | |
tree | b6bfc3ea242ebf84d7a9dae2bfba0984494aafbe /pkgs/tools/nix | |
parent | b962ff92ff414b325c1b73f24f2ff3b0ae613dd7 (diff) |
nixos-render-docs: add image support
currently only supported for html. docbook could also support images, but it's on the way out for manual generation anyway so we won't add image support there. options docs can't use images because they also target manpages, which leaves no viable users.
Diffstat (limited to 'pkgs/tools/nix')
6 files changed, 70 insertions, 8 deletions
diff --git a/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/commonmark.py b/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/commonmark.py index 440cf35f0d387..6287b60f0a51d 100644 --- a/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/commonmark.py +++ b/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/commonmark.py @@ -184,3 +184,7 @@ class CommonMarkRenderer(Renderer): def ordered_list_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: self._list_stack.pop() return "" + def image(self, token: Token, tokens: Sequence[Token], i: int) -> str: + if title := cast(str, token.attrs.get('title', '')): + title = ' "' + title.replace('"', '\\"') + '"' + return f'![{token.content}]({token.attrs["src"]}{title})' diff --git a/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/html.py b/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/html.py index 1bffc601f998e..4a0504e61b0a4 100644 --- a/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/html.py +++ b/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/html.py @@ -44,6 +44,9 @@ class HTMLRenderer(Renderer): result += self._close_headings(None) return result + def _pull_image(self, path: str) -> str: + raise NotImplementedError() + def text(self, token: Token, tokens: Sequence[Token], i: int) -> str: return escape(token.content) def paragraph_open(self, token: Token, tokens: Sequence[Token], i: int) -> str: @@ -224,6 +227,16 @@ class HTMLRenderer(Renderer): return '<p class="title"><strong>' def example_title_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: return '</strong></p><div class="example-contents">' + def image(self, token: Token, tokens: Sequence[Token], i: int) -> str: + src = self._pull_image(cast(str, token.attrs['src'])) + alt = f'alt="{escape(token.content, True)}"' if token.content else "" + if title := cast(str, token.attrs.get('title', '')): + title = f'title="{escape(title, True)}"' + return ( + '<div class="mediaobject">' + f'<img src="{escape(src, True)}" {alt} {title} />' + '</div>' + ) def _make_hN(self, level: int) -> tuple[str, str]: return f"h{min(6, max(1, level + self._hlevel_offset))}", "" diff --git a/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual.py b/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual.py index 82e8f3e8a6d77..acf562b839e08 100644 --- a/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual.py +++ b/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/manual.py @@ -1,4 +1,5 @@ import argparse +import hashlib import html import json import re @@ -241,25 +242,42 @@ class HTMLParameters(NamedTuple): toc_depth: int chunk_toc_depth: int section_toc_depth: int + media_dir: Path class ManualHTMLRenderer(RendererMixin, HTMLRenderer): _base_path: Path + _in_dir: Path _html_params: HTMLParameters def __init__(self, toplevel_tag: str, revision: str, html_params: HTMLParameters, manpage_urls: Mapping[str, str], xref_targets: dict[str, XrefTarget], - base_path: Path): + in_dir: Path, base_path: Path): super().__init__(toplevel_tag, revision, manpage_urls, xref_targets) - self._base_path, self._html_params = base_path, html_params + self._in_dir = in_dir + self._base_path = base_path.absolute() + self._html_params = html_params + + def _pull_image(self, src: str) -> str: + src_path = Path(src) + content = (self._in_dir / src_path).read_bytes() + # images may be used more than once, but we want to store them only once and + # in an easily accessible (ie, not input-file-path-dependent) location without + # having to maintain a mapping structure. hashing the file and using the hash + # as both the path of the final image provides both. + content_hash = hashlib.sha3_256(content).hexdigest() + target_name = f"{content_hash}{src_path.suffix}" + target_path = self._base_path / self._html_params.media_dir / target_name + target_path.write_bytes(content) + return f"./{self._html_params.media_dir}/{target_name}" def _push(self, tag: str, hlevel_offset: int) -> Any: - result = (self._toplevel_tag, self._headings, self._attrspans, self._hlevel_offset) + result = (self._toplevel_tag, self._headings, self._attrspans, self._hlevel_offset, self._in_dir) self._hlevel_offset += hlevel_offset self._toplevel_tag, self._headings, self._attrspans = tag, [], [] return result def _pop(self, state: Any) -> None: - (self._toplevel_tag, self._headings, self._attrspans, self._hlevel_offset) = state + (self._toplevel_tag, self._headings, self._attrspans, self._hlevel_offset, self._in_dir) = state def _render_book(self, tokens: Sequence[Token]) -> str: assert tokens[4].children @@ -481,8 +499,10 @@ class ManualHTMLRenderer(RendererMixin, HTMLRenderer): # we do not set _hlevel_offset=0 because docbook doesn't either. else: inner = outer + in_dir = self._in_dir for included, path in fragments: try: + self._in_dir = (in_dir / path).parent inner.append(self.render(included)) except Exception as e: raise RuntimeError(f"rendering {path}") from e @@ -525,8 +545,9 @@ class HTMLConverter(BaseConverter[ManualHTMLRenderer]): # renderer not set on purpose since it has a dependency on the output path! def convert(self, infile: Path, outfile: Path) -> None: - self._renderer = ManualHTMLRenderer('book', self._revision, self._html_params, - self._manpage_urls, self._xref_targets, outfile.parent) + self._renderer = ManualHTMLRenderer( + 'book', self._revision, self._html_params, self._manpage_urls, self._xref_targets, + infile.parent, outfile.parent) super().convert(infile, outfile) def _parse(self, src: str) -> list[Token]: @@ -687,6 +708,7 @@ def _build_cli_html(p: argparse.ArgumentParser) -> None: p.add_argument('--toc-depth', default=1, type=int) p.add_argument('--chunk-toc-depth', default=1, type=int) p.add_argument('--section-toc-depth', default=0, type=int) + p.add_argument('--media-dir', default="media", type=Path) p.add_argument('infile', type=Path) p.add_argument('outfile', type=Path) @@ -700,7 +722,7 @@ def _run_cli_html(args: argparse.Namespace) -> None: md = HTMLConverter( args.revision, HTMLParameters(args.generator, args.stylesheet, args.script, args.toc_depth, - args.chunk_toc_depth, args.section_toc_depth), + args.chunk_toc_depth, args.section_toc_depth, args.media_dir), json.load(manpage_urls)) md.convert(args.infile, args.outfile) diff --git a/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/md.py b/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/md.py index 78e05642552b0..edd4238dd9f06 100644 --- a/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/md.py +++ b/pkgs/tools/nix/nixos-render-docs/src/nixos_render_docs/md.py @@ -90,6 +90,7 @@ class Renderer: "example_close": self.example_close, "example_title_open": self.example_title_open, "example_title_close": self.example_title_close, + "image": self.image, } self._admonitions = { @@ -225,6 +226,8 @@ class Renderer: raise RuntimeError("md token not supported", token) def example_title_close(self, token: Token, tokens: Sequence[Token], i: int) -> str: raise RuntimeError("md token not supported", token) + def image(self, token: Token, tokens: Sequence[Token], i: int) -> str: + raise RuntimeError("md token not supported", token) def _is_escaped(src: str, pos: int) -> bool: found = 0 diff --git a/pkgs/tools/nix/nixos-render-docs/src/tests/test_commonmark.py b/pkgs/tools/nix/nixos-render-docs/src/tests/test_commonmark.py index d808c5b50c345..4ff0bc3095c3d 100644 --- a/pkgs/tools/nix/nixos-render-docs/src/tests/test_commonmark.py +++ b/pkgs/tools/nix/nixos-render-docs/src/tests/test_commonmark.py @@ -91,3 +91,9 @@ some nested anchors - *more stuff in same deflist* foo""".replace(' ', ' ') + +def test_images() -> None: + c = Converter({}) + assert c._render("![*alt text*](foo \"title \\\"quoted\\\" text\")") == ( + "![*alt text*](foo \"title \\\"quoted\\\" text\")" + ) diff --git a/pkgs/tools/nix/nixos-render-docs/src/tests/test_html.py b/pkgs/tools/nix/nixos-render-docs/src/tests/test_html.py index df366a8babd7e..863aa68e3820c 100644 --- a/pkgs/tools/nix/nixos-render-docs/src/tests/test_html.py +++ b/pkgs/tools/nix/nixos-render-docs/src/tests/test_html.py @@ -3,10 +3,14 @@ import pytest from sample_md import sample1 +class Renderer(nrd.html.HTMLRenderer): + def _pull_image(self, src: str) -> str: + return src + class Converter(nrd.md.Converter[nrd.html.HTMLRenderer]): def __init__(self, manpage_urls: dict[str, str], xrefs: dict[str, nrd.manual_structure.XrefTarget]): super().__init__() - self._renderer = nrd.html.HTMLRenderer(manpage_urls, xrefs) + self._renderer = Renderer(manpage_urls, xrefs) def unpretty(s: str) -> str: return "".join(map(str.strip, s.splitlines())).replace('␣', ' ').replace('↵', '\n') @@ -69,6 +73,16 @@ def test_xrefs() -> None: c._render("[](#baz)") assert exc.value.args[0] == 'bad local reference, id #baz not known' +def test_images() -> None: + c = Converter({}, {}) + assert c._render("![*alt text*](foo \"title text\")") == unpretty(""" + <p> + <div class="mediaobject"> + <img src="foo" alt="*alt text*" title="title text" /> + </div> + </p> + """) + def test_full() -> None: c = Converter({ 'man(1)': 'http://example.org' }, {}) assert c._render(sample1) == unpretty(""" |