mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-11-16 23:03:40 +01:00
nixos-render-docs: add inline anchor plugin
supports the […]{#id} inline anchor syntax. other features of bracketed spans are intentionally not supported.
This commit is contained in:
parent
41a5c3a93d
commit
6829c6c335
4 changed files with 226 additions and 0 deletions
|
@ -44,6 +44,7 @@ python.pkgs.buildPythonApplication {
|
|||
|
||||
nativeBuildInputs = [
|
||||
python.pkgs.setuptools
|
||||
python.pkgs.pytestCheckHook
|
||||
];
|
||||
|
||||
propagatedBuildInputs = [
|
||||
|
@ -52,6 +53,8 @@ python.pkgs.buildPythonApplication {
|
|||
python.pkgs.frozendict
|
||||
];
|
||||
|
||||
pytestFlagsArray = [ "-vvrP" "tests/" ];
|
||||
|
||||
meta = with lib; {
|
||||
description = "Renderer for NixOS manual and option docs";
|
||||
license = licenses.mit;
|
||||
|
|
|
@ -200,3 +200,6 @@ class DocBookRenderer(Renderer):
|
|||
else:
|
||||
return ref
|
||||
raise NotImplementedError("md node not supported yet", token)
|
||||
def inline_anchor(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
||||
env: MutableMapping[str, Any]) -> str:
|
||||
return f'<anchor xml:id={quoteattr(cast(str, token.attrs["id"]))} />'
|
||||
|
|
|
@ -3,6 +3,8 @@ from collections.abc import Mapping, MutableMapping, Sequence
|
|||
from frozendict import frozendict # type: ignore[attr-defined]
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
import re
|
||||
|
||||
from .types import RenderFn
|
||||
|
||||
import markdown_it
|
||||
|
@ -61,6 +63,7 @@ class Renderer(markdown_it.renderer.RendererProtocol):
|
|||
'myst_role': self.myst_role,
|
||||
"container_admonition_open": self.admonition_open,
|
||||
"container_admonition_close": self.admonition_close,
|
||||
"inline_anchor": self.inline_anchor,
|
||||
}
|
||||
|
||||
self._admonitions = {
|
||||
|
@ -212,6 +215,51 @@ class Renderer(markdown_it.renderer.RendererProtocol):
|
|||
def myst_role(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
||||
env: MutableMapping[str, Any]) -> str:
|
||||
raise RuntimeError("md token not supported", token)
|
||||
def inline_anchor(self, token: Token, tokens: Sequence[Token], i: int, options: OptionsDict,
|
||||
env: MutableMapping[str, Any]) -> str:
|
||||
raise RuntimeError("md token not supported", token)
|
||||
|
||||
def _is_escaped(src: str, pos: int) -> bool:
|
||||
found = 0
|
||||
while pos >= 0 and src[pos] == '\\':
|
||||
found += 1
|
||||
pos -= 1
|
||||
return found % 2 == 1
|
||||
|
||||
_INLINE_ANCHOR_PATTERN = re.compile(r"\{\s*#([\w-]+)\s*\}")
|
||||
|
||||
def _inline_anchor_plugin(md: markdown_it.MarkdownIt) -> None:
|
||||
def inline_anchor(state: markdown_it.rules_inline.StateInline, silent: bool) -> bool:
|
||||
if state.src[state.pos] != '[':
|
||||
return False
|
||||
if _is_escaped(state.src, state.pos - 1):
|
||||
return False
|
||||
|
||||
# treat the inline span like a link label for simplicity.
|
||||
label_begin = state.pos + 1
|
||||
label_end = markdown_it.helpers.parseLinkLabel(state, state.pos)
|
||||
input_end = state.posMax
|
||||
if label_end < 0:
|
||||
return False
|
||||
|
||||
# match id
|
||||
match = _INLINE_ANCHOR_PATTERN.match(state.src[label_end + 1 : ])
|
||||
if not match:
|
||||
return False
|
||||
|
||||
if not silent:
|
||||
token = state.push("inline_anchor", "", 0) # type: ignore[no-untyped-call]
|
||||
token.attrs['id'] = match[1]
|
||||
|
||||
state.pos = label_begin
|
||||
state.posMax = label_end
|
||||
state.md.inline.tokenize(state)
|
||||
|
||||
state.pos = label_end + match.end() + 1
|
||||
state.posMax = input_end
|
||||
return True
|
||||
|
||||
md.inline.ruler.before("link", "inline_anchor", inline_anchor)
|
||||
|
||||
class Converter(ABC):
|
||||
__renderer__: Callable[[Mapping[str, str], markdown_it.MarkdownIt], Renderer]
|
||||
|
@ -237,6 +285,7 @@ class Converter(ABC):
|
|||
)
|
||||
self._md.use(deflist_plugin)
|
||||
self._md.use(myst_role_plugin)
|
||||
self._md.use(_inline_anchor_plugin)
|
||||
self._md.enable(["smartquotes", "replacements"])
|
||||
|
||||
def _post_parse(self, tokens: list[Token]) -> list[Token]:
|
||||
|
|
171
pkgs/tools/nix/nixos-render-docs/src/tests/test_plugins.py
Normal file
171
pkgs/tools/nix/nixos-render-docs/src/tests/test_plugins.py
Normal file
|
@ -0,0 +1,171 @@
|
|||
import nixos_render_docs
|
||||
|
||||
from markdown_it.token import Token
|
||||
|
||||
class Converter(nixos_render_docs.md.Converter):
|
||||
# actual renderer doesn't matter, we're just parsing.
|
||||
__renderer__ = nixos_render_docs.docbook.DocBookRenderer
|
||||
|
||||
def test_inline_anchor_simple() -> None:
|
||||
c = Converter({})
|
||||
assert c._parse("[]{#test}") == [
|
||||
Token(type='paragraph_open', tag='p', nesting=1, attrs={}, map=[0, 1], level=0, children=None,
|
||||
content='', markup='', info='', meta={}, block=True, hidden=False),
|
||||
Token(type='inline', tag='', nesting=0, attrs={}, map=[0, 1], level=1, content='[]{#test}',
|
||||
markup='', info='', meta={}, block=True, hidden=False,
|
||||
children=[
|
||||
Token(type='inline_anchor', tag='', nesting=0, attrs={'id': 'test'}, map=None, level=0,
|
||||
children=None, content='', markup='', info='', meta={}, block=False, hidden=False)
|
||||
]),
|
||||
Token(type='paragraph_close', tag='p', nesting=-1, attrs={}, map=None, level=0,
|
||||
children=None, content='', markup='', info='', meta={}, block=True, hidden=False)
|
||||
]
|
||||
|
||||
def test_inline_anchor_formatted() -> None:
|
||||
c = Converter({})
|
||||
assert c._parse("a[b c `d` ***e***]{#test}f") == [
|
||||
Token(type='paragraph_open', tag='p', nesting=1, attrs={}, map=[0, 1], level=0,
|
||||
children=None, content='', markup='', info='', meta={}, block=True, hidden=False),
|
||||
Token(type='inline', tag='', nesting=0, attrs={}, map=[0, 1], level=1,
|
||||
content='a[b c `d` ***e***]{#test}f', markup='', info='', meta={}, block=True, hidden=False,
|
||||
children=[
|
||||
Token(type='text', tag='', nesting=0, attrs={}, map=None, level=0,
|
||||
children=None, content='a', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='inline_anchor', tag='', nesting=0, attrs={'id': 'test'}, map=None, level=0,
|
||||
children=None, content='', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='text', tag='', nesting=0, attrs={}, map=None, level=0, children=None,
|
||||
content='b c ', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='code_inline', tag='code', nesting=0, attrs={}, map=None, level=0,
|
||||
children=None, content='d', markup='`', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='text', tag='', nesting=0, attrs={}, map=None, level=0, children=None,
|
||||
content=' ', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='em_open', tag='em', nesting=1, attrs={}, map=None, level=0, children=None,
|
||||
content='', markup='*', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='text', tag='', nesting=0, attrs={}, map=None, level=1, children=None,
|
||||
content='', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='strong_open', tag='strong', nesting=1, attrs={}, map=None, level=1,
|
||||
children=None, content='', markup='**', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='text', tag='', nesting=0, attrs={}, map=None, level=2, children=None,
|
||||
content='e', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='strong_close', tag='strong', nesting=-1, attrs={}, map=None, level=1,
|
||||
children=None, content='', markup='**', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='text', tag='', nesting=0, attrs={}, map=None, level=1, children=None,
|
||||
content='', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='em_close', tag='em', nesting=-1, attrs={}, map=None, level=0, children=None,
|
||||
content='', markup='*', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='text', tag='', nesting=0, attrs={}, map=None, level=0, children=None,
|
||||
content='f', markup='', info='', meta={}, block=False, hidden=False)
|
||||
]),
|
||||
Token(type='paragraph_close', tag='p', nesting=-1, attrs={}, map=None, level=0, children=None,
|
||||
content='', markup='', info='', meta={}, block=True, hidden=False)
|
||||
]
|
||||
|
||||
def test_inline_anchor_in_heading() -> None:
|
||||
c = Converter({})
|
||||
# inline anchors in headers are allowed, but header attributes should be preferred
|
||||
assert c._parse("# foo []{#bar} baz") == [
|
||||
Token(type='heading_open', tag='h1', nesting=1, attrs={}, map=[0, 1], level=0, children=None,
|
||||
content='', markup='#', info='', meta={}, block=True, hidden=False),
|
||||
Token(type='inline', tag='', nesting=0, attrs={}, map=[0, 1], level=1,
|
||||
content='foo []{#bar} baz', markup='', info='', meta={}, block=True, hidden=False,
|
||||
children=[
|
||||
Token(type='text', tag='', nesting=0, attrs={}, map=None, level=0, children=None,
|
||||
content='foo ', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='inline_anchor', tag='', nesting=0, attrs={'id': 'bar'}, map=None, level=0,
|
||||
children=None, content='', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='text', tag='', nesting=0, attrs={}, map=None, level=0, children=None,
|
||||
content=' baz', markup='', info='', meta={}, block=False, hidden=False)
|
||||
]),
|
||||
Token(type='heading_close', tag='h1', nesting=-1, attrs={}, map=None, level=0, children=None,
|
||||
content='', markup='#', info='', meta={}, block=True, hidden=False)
|
||||
]
|
||||
|
||||
def test_inline_anchor_on_links() -> None:
|
||||
c = Converter({})
|
||||
assert c._parse("[ [a](#bar) ]{#foo}") == [
|
||||
Token(type='paragraph_open', tag='p', nesting=1, attrs={}, map=[0, 1], level=0, children=None,
|
||||
content='', markup='', info='', meta={}, block=True, hidden=False),
|
||||
Token(type='inline', tag='', nesting=0, attrs={}, map=[0, 1], level=1, content='[ [a](#bar) ]{#foo}',
|
||||
markup='', info='', meta={}, block=True, hidden=False,
|
||||
children=[
|
||||
Token(type='inline_anchor', tag='', nesting=0, attrs={'id': 'foo'}, map=None, level=0,
|
||||
children=None, content='', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='text', tag='', nesting=0, attrs={}, map=None, level=0, children=None,
|
||||
content=' ', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='link_open', tag='a', nesting=1, attrs={'href': '#bar'}, map=None, level=0,
|
||||
children=None, content='', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='text', tag='', nesting=0, attrs={}, map=None, level=1, children=None,
|
||||
content='a', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='link_close', tag='a', nesting=-1, attrs={}, map=None, level=0, children=None,
|
||||
content='', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='text', tag='', nesting=0, attrs={}, map=None, level=0, children=None,
|
||||
content=' ', markup='', info='', meta={}, block=False, hidden=False)
|
||||
]),
|
||||
Token(type='paragraph_close', tag='p', nesting=-1, attrs={}, map=None, level=0, children=None,
|
||||
content='', markup='', info='', meta={}, block=True, hidden=False)
|
||||
]
|
||||
|
||||
def test_inline_anchor_nested() -> None:
|
||||
# inline anchors may contain more anchors (even though this is a bit pointless)
|
||||
c = Converter({})
|
||||
assert c._parse("[ [a]{#bar} ]{#foo}") == [
|
||||
Token(type='paragraph_open', tag='p', nesting=1, attrs={}, map=[0, 1], level=0, children=None,
|
||||
content='', markup='', info='', meta={}, block=True, hidden=False),
|
||||
Token(type='inline', tag='', nesting=0, attrs={}, map=[0, 1], level=1,
|
||||
content='[ [a]{#bar} ]{#foo}', markup='', info='', meta={}, block=True, hidden=False,
|
||||
children=[
|
||||
Token(type='inline_anchor', tag='', nesting=0, attrs={'id': 'foo'}, map=None, level=0,
|
||||
children=None, content='', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='text', tag='', nesting=0, attrs={}, map=None, level=0, children=None,
|
||||
content=' ', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='inline_anchor', tag='', nesting=0, attrs={'id': 'bar'}, map=None, level=0,
|
||||
children=None, content='', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='text', tag='', nesting=0, attrs={}, map=None, level=0, children=None,
|
||||
content='a ', markup='', info='', meta={}, block=False, hidden=False)
|
||||
]),
|
||||
Token(type='paragraph_close', tag='p', nesting=-1, attrs={}, map=None, level=0, children=None,
|
||||
content='', markup='', info='', meta={}, block=True, hidden=False)
|
||||
]
|
||||
|
||||
def test_inline_anchor_escaping() -> None:
|
||||
c = Converter({})
|
||||
assert c._parse("\\[a]{#bar}") == [
|
||||
Token(type='paragraph_open', tag='p', nesting=1, attrs={}, map=[0, 1], level=0, children=None,
|
||||
content='', markup='', info='', meta={}, block=True, hidden=False),
|
||||
Token(type='inline', tag='', nesting=0, attrs={}, map=[0, 1], level=1,
|
||||
content='\\[a]{#bar}', markup='', info='', meta={}, block=True, hidden=False,
|
||||
children=[
|
||||
Token(type='text', tag='', nesting=0, attrs={}, map=None, level=0, children=None,
|
||||
content='[a]{#bar}', markup='', info='', meta={}, block=False, hidden=False)
|
||||
]),
|
||||
Token(type='paragraph_close', tag='p', nesting=-1, attrs={}, map=None, level=0, children=None,
|
||||
content='', markup='', info='', meta={}, block=True, hidden=False)
|
||||
]
|
||||
assert c._parse("\\\\[a]{#bar}") == [
|
||||
Token(type='paragraph_open', tag='p', nesting=1, attrs={}, map=[0, 1], level=0, children=None,
|
||||
content='', markup='', info='', meta={}, block=True, hidden=False),
|
||||
Token(type='inline', tag='', nesting=0, attrs={}, map=[0, 1], level=1,
|
||||
content='\\\\[a]{#bar}', markup='', info='', meta={}, block=True, hidden=False,
|
||||
children=[
|
||||
Token(type='text', tag='', nesting=0, attrs={}, map=None, level=0, children=None,
|
||||
content='\\', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='inline_anchor', tag='', nesting=0, attrs={'id': 'bar'}, map=None, level=0,
|
||||
children=None, content='', markup='', info='', meta={}, block=False, hidden=False),
|
||||
Token(type='text', tag='', nesting=0, attrs={}, map=None, level=0, children=None,
|
||||
content='a', markup='', info='', meta={}, block=False, hidden=False)
|
||||
]),
|
||||
Token(type='paragraph_close', tag='p', nesting=-1, attrs={}, map=None, level=0, children=None,
|
||||
content='', markup='', info='', meta={}, block=True, hidden=False)
|
||||
]
|
||||
assert c._parse("\\\\\\[a]{#bar}") == [
|
||||
Token(type='paragraph_open', tag='p', nesting=1, attrs={}, map=[0, 1], level=0, children=None,
|
||||
content='', markup='', info='', meta={}, block=True, hidden=False),
|
||||
Token(type='inline', tag='', nesting=0, attrs={}, map=[0, 1], level=1,
|
||||
children=[
|
||||
Token(type='text', tag='', nesting=0, attrs={}, map=None, level=0, children=None,
|
||||
content='\\[a]{#bar}', markup='', info='', meta={}, block=False, hidden=False)
|
||||
],
|
||||
content='\\\\\\[a]{#bar}', markup='', info='', meta={}, block=True, hidden=False),
|
||||
Token(type='paragraph_close', tag='p', nesting=-1, attrs={}, map=None, level=0, children=None,
|
||||
content='', markup='', info='', meta={}, block=True, hidden=False)
|
||||
]
|
Loading…
Reference in a new issue