diff --git a/docs/default.nix b/docs/default.nix index 0e7958a6..31a50a54 100644 --- a/docs/default.nix +++ b/docs/default.nix @@ -55,6 +55,8 @@ in { inherit options-json; + gfm-alerts-to-admonitions = pkgs.python3.pkgs.callPackage ./gfm-alerts-to-admonitions { }; + man-docs = pkgs.callPackage ./man { inherit options-json; }; } // lib.optionalAttrs (!pkgs.stdenv.isDarwin) ( diff --git a/docs/gfm-alerts-to-admonitions/default.nix b/docs/gfm-alerts-to-admonitions/default.nix new file mode 100644 index 00000000..5965c92c --- /dev/null +++ b/docs/gfm-alerts-to-admonitions/default.nix @@ -0,0 +1,30 @@ +{ + buildPythonPackage, + pytestCheckHook, + markdown-it-py, + lib, + setuptools, + pytest-regressions, +}: +buildPythonPackage { + pname = "gfm-alerts-to-admonitions"; + version = "0.0"; + format = "pyproject"; + + src = ./.; + + build-system = [ setuptools ]; + dependencies = [ markdown-it-py ]; + + nativeCheckInputs = [ + pytestCheckHook + pytest-regressions + ]; + pythonImportsCheck = [ "gfm_alerts_to_admonitions" ]; + + meta = { + description = "Transform GFM alerts to nixos-render-docs admonitions"; + license = lib.licenses.mit; + maintainers = [ lib.maintainers.traxys ]; + }; +} diff --git a/docs/gfm-alerts-to-admonitions/gfm_alerts_to_admonitions/__init__.py b/docs/gfm-alerts-to-admonitions/gfm_alerts_to_admonitions/__init__.py new file mode 100644 index 00000000..de758178 --- /dev/null +++ b/docs/gfm-alerts-to-admonitions/gfm_alerts_to_admonitions/__init__.py @@ -0,0 +1 @@ +from .main import gfm_alert_to_admonition as gfm_alert_to_admonition diff --git a/docs/gfm-alerts-to-admonitions/gfm_alerts_to_admonitions/main.py b/docs/gfm-alerts-to-admonitions/gfm_alerts_to_admonitions/main.py new file mode 100644 index 00000000..20db2824 --- /dev/null +++ b/docs/gfm-alerts-to-admonitions/gfm_alerts_to_admonitions/main.py @@ -0,0 +1,55 @@ +import re +from re import Match + +from markdown_it import MarkdownIt +from markdown_it.rules_core import StateCore +from markdown_it.token import Token + +_ALERT_PATTERN = re.compile( + r"^\[\!(TIP|NOTE|IMPORTANT|WARNING|CAUTION)\]\s*", re.IGNORECASE +) + + +def gfm_alert_to_admonition(md: MarkdownIt): + def gfm_alert_to_adm(state: StateCore): + # Scan all tokens, looking for blockquotes, + # convert any alert-style blockquotes to admonition tokens + tokens: list[Token] = state.tokens + for i, token in enumerate(tokens): + if token.type == "blockquote_open": + # Store the blockquote's opening token + open = tokens[i] + start_index = i + + # Find the blockquote's closing token + while tokens[i].type != "blockquote_close" and i < len(tokens): + i += 1 + close = tokens[i] + end_index = i + + # Find the first `inline` token in the blockquote + first_content = next( + ( + t + for t in tokens[start_index : end_index + 1] + if t.type == "inline" + ), + None, + ) + if first_content is None: + continue + + # Check if the blockquote is actually an alert + m: Match = _ALERT_PATTERN.match(first_content.content) + if m is None: + continue + + # Remove ' [!TIP]' from the token's content + first_content.content = first_content.content[m.end(0) :] + + # Convert the opening & closing tokens from "blockquote" to "admonition" + open.type = "admonition_open" + open.meta["kind"] = m.group(1).lower() + close.type = "admonition_close" + + md.core.ruler.after("block", "github-alerts", gfm_alert_to_adm) diff --git a/docs/gfm-alerts-to-admonitions/pyproject.toml b/docs/gfm-alerts-to-admonitions/pyproject.toml new file mode 100644 index 00000000..a63ca42d --- /dev/null +++ b/docs/gfm-alerts-to-admonitions/pyproject.toml @@ -0,0 +1,15 @@ +[project] +name = "gfm-alerts-to-admonitions" +version = "0.0" +description = "MarkdownIt plugin to convert gfm alerts to nixos-render-docs admonitions" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] + +[build-system] +requires = ["setuptools"] + +[tool.pytest.ini_options] +pythonpath = ["."] diff --git a/docs/gfm-alerts-to-admonitions/tests/test_plugin.py b/docs/gfm-alerts-to-admonitions/tests/test_plugin.py new file mode 100644 index 00000000..d956deb7 --- /dev/null +++ b/docs/gfm-alerts-to-admonitions/tests/test_plugin.py @@ -0,0 +1,25 @@ +import pytest +from gfm_alerts_to_admonitions import gfm_alert_to_admonition +from markdown_it import MarkdownIt + + +@pytest.mark.parametrize("kind", ["tip", "note", "important", "warning", "caution"]) +def test_parse(data_regression, kind): + input = f"> [!{kind.upper()}]\n> This is an *alert*" + md = MarkdownIt("commonmark").use(gfm_alert_to_admonition) + tokens = md.parse(input) + data_regression.check([t.as_dict() for t in tokens]) + + +def test_case(): + def make_input(kind): + return f"> [!{kind}]\n> tip" + + md = MarkdownIt("commonmark").use(gfm_alert_to_admonition) + + reference = [t.as_dict() for t in md.parse(make_input("TIP"))] + lower = [t.as_dict() for t in md.parse(make_input("tip"))] + mixed = [t.as_dict() for t in md.parse(make_input("tIp"))] + + assert lower == reference + assert mixed == reference diff --git a/docs/gfm-alerts-to-admonitions/tests/test_plugin/test_parse_caution_.yml b/docs/gfm-alerts-to-admonitions/tests/test_plugin/test_parse_caution_.yml new file mode 100644 index 00000000..3d5d7430 --- /dev/null +++ b/docs/gfm-alerts-to-admonitions/tests/test_plugin/test_parse_caution_.yml @@ -0,0 +1,124 @@ +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 0 + map: + - 0 + - 2 + markup: '>' + meta: + kind: caution + nesting: 1 + tag: blockquote + type: admonition_open +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 1 + map: + - 0 + - 2 + markup: '' + meta: {} + nesting: 1 + tag: p + type: paragraph_open +- attrs: null + block: true + children: + - attrs: null + block: false + children: null + content: 'This is an ' + hidden: false + info: '' + level: 0 + map: null + markup: '' + meta: {} + nesting: 0 + tag: '' + type: text + - attrs: null + block: false + children: null + content: '' + hidden: false + info: '' + level: 0 + map: null + markup: '*' + meta: {} + nesting: 1 + tag: em + type: em_open + - attrs: null + block: false + children: null + content: alert + hidden: false + info: '' + level: 1 + map: null + markup: '' + meta: {} + nesting: 0 + tag: '' + type: text + - attrs: null + block: false + children: null + content: '' + hidden: false + info: '' + level: 0 + map: null + markup: '*' + meta: {} + nesting: -1 + tag: em + type: em_close + content: This is an *alert* + hidden: false + info: '' + level: 2 + map: + - 0 + - 2 + markup: '' + meta: {} + nesting: 0 + tag: '' + type: inline +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 1 + map: null + markup: '' + meta: {} + nesting: -1 + tag: p + type: paragraph_close +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 0 + map: null + markup: '>' + meta: {} + nesting: -1 + tag: blockquote + type: admonition_close diff --git a/docs/gfm-alerts-to-admonitions/tests/test_plugin/test_parse_important_.yml b/docs/gfm-alerts-to-admonitions/tests/test_plugin/test_parse_important_.yml new file mode 100644 index 00000000..c3efab89 --- /dev/null +++ b/docs/gfm-alerts-to-admonitions/tests/test_plugin/test_parse_important_.yml @@ -0,0 +1,124 @@ +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 0 + map: + - 0 + - 2 + markup: '>' + meta: + kind: important + nesting: 1 + tag: blockquote + type: admonition_open +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 1 + map: + - 0 + - 2 + markup: '' + meta: {} + nesting: 1 + tag: p + type: paragraph_open +- attrs: null + block: true + children: + - attrs: null + block: false + children: null + content: 'This is an ' + hidden: false + info: '' + level: 0 + map: null + markup: '' + meta: {} + nesting: 0 + tag: '' + type: text + - attrs: null + block: false + children: null + content: '' + hidden: false + info: '' + level: 0 + map: null + markup: '*' + meta: {} + nesting: 1 + tag: em + type: em_open + - attrs: null + block: false + children: null + content: alert + hidden: false + info: '' + level: 1 + map: null + markup: '' + meta: {} + nesting: 0 + tag: '' + type: text + - attrs: null + block: false + children: null + content: '' + hidden: false + info: '' + level: 0 + map: null + markup: '*' + meta: {} + nesting: -1 + tag: em + type: em_close + content: This is an *alert* + hidden: false + info: '' + level: 2 + map: + - 0 + - 2 + markup: '' + meta: {} + nesting: 0 + tag: '' + type: inline +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 1 + map: null + markup: '' + meta: {} + nesting: -1 + tag: p + type: paragraph_close +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 0 + map: null + markup: '>' + meta: {} + nesting: -1 + tag: blockquote + type: admonition_close diff --git a/docs/gfm-alerts-to-admonitions/tests/test_plugin/test_parse_note_.yml b/docs/gfm-alerts-to-admonitions/tests/test_plugin/test_parse_note_.yml new file mode 100644 index 00000000..ffdccfef --- /dev/null +++ b/docs/gfm-alerts-to-admonitions/tests/test_plugin/test_parse_note_.yml @@ -0,0 +1,124 @@ +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 0 + map: + - 0 + - 2 + markup: '>' + meta: + kind: note + nesting: 1 + tag: blockquote + type: admonition_open +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 1 + map: + - 0 + - 2 + markup: '' + meta: {} + nesting: 1 + tag: p + type: paragraph_open +- attrs: null + block: true + children: + - attrs: null + block: false + children: null + content: 'This is an ' + hidden: false + info: '' + level: 0 + map: null + markup: '' + meta: {} + nesting: 0 + tag: '' + type: text + - attrs: null + block: false + children: null + content: '' + hidden: false + info: '' + level: 0 + map: null + markup: '*' + meta: {} + nesting: 1 + tag: em + type: em_open + - attrs: null + block: false + children: null + content: alert + hidden: false + info: '' + level: 1 + map: null + markup: '' + meta: {} + nesting: 0 + tag: '' + type: text + - attrs: null + block: false + children: null + content: '' + hidden: false + info: '' + level: 0 + map: null + markup: '*' + meta: {} + nesting: -1 + tag: em + type: em_close + content: This is an *alert* + hidden: false + info: '' + level: 2 + map: + - 0 + - 2 + markup: '' + meta: {} + nesting: 0 + tag: '' + type: inline +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 1 + map: null + markup: '' + meta: {} + nesting: -1 + tag: p + type: paragraph_close +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 0 + map: null + markup: '>' + meta: {} + nesting: -1 + tag: blockquote + type: admonition_close diff --git a/docs/gfm-alerts-to-admonitions/tests/test_plugin/test_parse_tip_.yml b/docs/gfm-alerts-to-admonitions/tests/test_plugin/test_parse_tip_.yml new file mode 100644 index 00000000..0613e11e --- /dev/null +++ b/docs/gfm-alerts-to-admonitions/tests/test_plugin/test_parse_tip_.yml @@ -0,0 +1,124 @@ +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 0 + map: + - 0 + - 2 + markup: '>' + meta: + kind: tip + nesting: 1 + tag: blockquote + type: admonition_open +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 1 + map: + - 0 + - 2 + markup: '' + meta: {} + nesting: 1 + tag: p + type: paragraph_open +- attrs: null + block: true + children: + - attrs: null + block: false + children: null + content: 'This is an ' + hidden: false + info: '' + level: 0 + map: null + markup: '' + meta: {} + nesting: 0 + tag: '' + type: text + - attrs: null + block: false + children: null + content: '' + hidden: false + info: '' + level: 0 + map: null + markup: '*' + meta: {} + nesting: 1 + tag: em + type: em_open + - attrs: null + block: false + children: null + content: alert + hidden: false + info: '' + level: 1 + map: null + markup: '' + meta: {} + nesting: 0 + tag: '' + type: text + - attrs: null + block: false + children: null + content: '' + hidden: false + info: '' + level: 0 + map: null + markup: '*' + meta: {} + nesting: -1 + tag: em + type: em_close + content: This is an *alert* + hidden: false + info: '' + level: 2 + map: + - 0 + - 2 + markup: '' + meta: {} + nesting: 0 + tag: '' + type: inline +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 1 + map: null + markup: '' + meta: {} + nesting: -1 + tag: p + type: paragraph_close +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 0 + map: null + markup: '>' + meta: {} + nesting: -1 + tag: blockquote + type: admonition_close diff --git a/docs/gfm-alerts-to-admonitions/tests/test_plugin/test_parse_warning_.yml b/docs/gfm-alerts-to-admonitions/tests/test_plugin/test_parse_warning_.yml new file mode 100644 index 00000000..03e17181 --- /dev/null +++ b/docs/gfm-alerts-to-admonitions/tests/test_plugin/test_parse_warning_.yml @@ -0,0 +1,124 @@ +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 0 + map: + - 0 + - 2 + markup: '>' + meta: + kind: warning + nesting: 1 + tag: blockquote + type: admonition_open +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 1 + map: + - 0 + - 2 + markup: '' + meta: {} + nesting: 1 + tag: p + type: paragraph_open +- attrs: null + block: true + children: + - attrs: null + block: false + children: null + content: 'This is an ' + hidden: false + info: '' + level: 0 + map: null + markup: '' + meta: {} + nesting: 0 + tag: '' + type: text + - attrs: null + block: false + children: null + content: '' + hidden: false + info: '' + level: 0 + map: null + markup: '*' + meta: {} + nesting: 1 + tag: em + type: em_open + - attrs: null + block: false + children: null + content: alert + hidden: false + info: '' + level: 1 + map: null + markup: '' + meta: {} + nesting: 0 + tag: '' + type: text + - attrs: null + block: false + children: null + content: '' + hidden: false + info: '' + level: 0 + map: null + markup: '*' + meta: {} + nesting: -1 + tag: em + type: em_close + content: This is an *alert* + hidden: false + info: '' + level: 2 + map: + - 0 + - 2 + markup: '' + meta: {} + nesting: 0 + tag: '' + type: inline +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 1 + map: null + markup: '' + meta: {} + nesting: -1 + tag: p + type: paragraph_close +- attrs: null + block: true + children: null + content: '' + hidden: false + info: '' + level: 0 + map: null + markup: '>' + meta: {} + nesting: -1 + tag: blockquote + type: admonition_close diff --git a/flake-modules/dev/default.nix b/flake-modules/dev/default.nix index 1bd4419b..64d53ae7 100644 --- a/flake-modules/dev/default.nix +++ b/flake-modules/dev/default.nix @@ -52,6 +52,9 @@ "**.scm" "**.svg" "**/man/*.5" + # Those files are generated by pytest-regression, which then `diff`s them. + # Formatting them will make the tests fail. + "docs/gfm-alerts-to-admonitions/tests/**/*.yml" ]; formatter.ruff-format.options = [ "--isolated" ]; };