mkdocs-entangled-plugin 0.3.0__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mkdocs_entangled_plugin-0.5.0/.gitignore +6 -0
- {mkdocs_entangled_plugin-0.3.0 → mkdocs_entangled_plugin-0.5.0}/PKG-INFO +14 -12
- {mkdocs_entangled_plugin-0.3.0 → mkdocs_entangled_plugin-0.5.0}/README.md +5 -0
- mkdocs_entangled_plugin-0.5.0/mkdocs_entangled/on_page_markdown.py +137 -0
- {mkdocs_entangled_plugin-0.3.0 → mkdocs_entangled_plugin-0.5.0}/mkdocs_entangled/plugin.py +5 -11
- mkdocs_entangled_plugin-0.5.0/pyproject.toml +44 -0
- mkdocs_entangled_plugin-0.3.0/mkdocs_entangled/mawk.py +0 -100
- mkdocs_entangled_plugin-0.3.0/mkdocs_entangled/on_page_markdown.py +0 -64
- mkdocs_entangled_plugin-0.3.0/mkdocs_entangled/properties.py +0 -124
- mkdocs_entangled_plugin-0.3.0/pyproject.toml +0 -39
- {mkdocs_entangled_plugin-0.3.0 → mkdocs_entangled_plugin-0.5.0}/LICENSE +0 -0
- {mkdocs_entangled_plugin-0.3.0 → mkdocs_entangled_plugin-0.5.0}/mkdocs_entangled/__init__.py +0 -0
- {mkdocs_entangled_plugin-0.3.0 → mkdocs_entangled_plugin-0.5.0}/mkdocs_entangled/config.py +0 -0
- {mkdocs_entangled_plugin-0.3.0 → mkdocs_entangled_plugin-0.5.0}/mkdocs_entangled/py.typed +0 -0
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: mkdocs-entangled-plugin
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Plugin for MkDocs helping with rendering Entangled (entangled.github.io) projects.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Requires-Python:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
Requires-Dist: entangled-cli[rich] (>=2.0.0a3,<3.0.0)
|
|
13
|
-
Requires-Dist: mkdocs (>=1.4.2,<2.0.0)
|
|
5
|
+
Author-email: Johan Hidding <j.hidding@esciencecenter.nl>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: <4,>=3.12
|
|
9
|
+
Requires-Dist: entangled-cli<3,>=2.2
|
|
10
|
+
Requires-Dist: mawk<0.2,>=0.1.7
|
|
11
|
+
Requires-Dist: mkdocs<2,>=1.4.2
|
|
14
12
|
Description-Content-Type: text/markdown
|
|
15
13
|
|
|
16
14
|
# Welcome to MkDocs Entangled Plugin
|
|
17
15
|
Using this plugin, you can make your Entangled documents look better.
|
|
18
16
|
|
|
17
|
+
## Examples
|
|
18
|
+
Detailed examples are found in the [entangled/mkdocs-examples repository](https://entangled.github.io/mkdocs-examples).
|
|
19
|
+
|
|
19
20
|
## Install
|
|
20
21
|
|
|
21
22
|
Install this with `pip install mkdocs-entangled-plugin`. To use the entangled plugin, add the following lines to your `mkdocs.yml`:
|
|
@@ -30,6 +31,8 @@ markdown_extensions:
|
|
|
30
31
|
alternate_style: true
|
|
31
32
|
```
|
|
32
33
|
|
|
34
|
+
For more information, see [the documentation](https://entangled.github.io/mkdocs-plugin).
|
|
35
|
+
|
|
33
36
|
## Components
|
|
34
37
|
This plugin bundles functionality for literate programming with Entangled.
|
|
35
38
|
|
|
@@ -119,4 +122,3 @@ Build artifacts by specifying a Makefile.
|
|
|
119
122
|
|
|
120
123
|
## License
|
|
121
124
|
Licensed under the Apache-2 license agreement: see LICENSE
|
|
122
|
-
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# Welcome to MkDocs Entangled Plugin
|
|
2
2
|
Using this plugin, you can make your Entangled documents look better.
|
|
3
3
|
|
|
4
|
+
## Examples
|
|
5
|
+
Detailed examples are found in the [entangled/mkdocs-examples repository](https://entangled.github.io/mkdocs-examples).
|
|
6
|
+
|
|
4
7
|
## Install
|
|
5
8
|
|
|
6
9
|
Install this with `pip install mkdocs-entangled-plugin`. To use the entangled plugin, add the following lines to your `mkdocs.yml`:
|
|
@@ -15,6 +18,8 @@ markdown_extensions:
|
|
|
15
18
|
alternate_style: true
|
|
16
19
|
```
|
|
17
20
|
|
|
21
|
+
For more information, see [the documentation](https://entangled.github.io/mkdocs-plugin).
|
|
22
|
+
|
|
18
23
|
## Components
|
|
19
24
|
This plugin bundles functionality for literate programming with Entangled.
|
|
20
25
|
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from collections.abc import Iterable, Callable
|
|
2
|
+
from functools import partial, reduce
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from textwrap import indent
|
|
5
|
+
from itertools import chain
|
|
6
|
+
|
|
7
|
+
from mkdocs.config.defaults import MkDocsConfig
|
|
8
|
+
from mkdocs.structure.pages import Page
|
|
9
|
+
from mkdocs.structure.files import Files
|
|
10
|
+
|
|
11
|
+
from entangled.document import ReferenceMap, Content, PlainText, ReferenceId, CodeBlock, document_to_text
|
|
12
|
+
from entangled.properties import get_attribute, Id, Attribute, Class
|
|
13
|
+
from entangled.hooks import get_hooks
|
|
14
|
+
from entangled.config import config
|
|
15
|
+
from repl_session import read_session
|
|
16
|
+
|
|
17
|
+
config.read()
|
|
18
|
+
|
|
19
|
+
# The Entangled config needs to be read before importing the Markdown reader
|
|
20
|
+
# This is very bad I know, and should be fixed in some future version.
|
|
21
|
+
from entangled.markdown_reader import read_markdown_string
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
type ContentFilter = Callable[[ReferenceMap, Content], Iterable[Content]]
|
|
25
|
+
type CodeBlockFilter = Callable[[ReferenceMap, ReferenceId], Iterable[Content]]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def codeblock_filter(f: CodeBlockFilter) -> ContentFilter:
|
|
29
|
+
def _foo(rm: ReferenceMap, c: Content) -> Iterable[Content]:
|
|
30
|
+
match c:
|
|
31
|
+
case PlainText():
|
|
32
|
+
return [c]
|
|
33
|
+
case ReferenceId():
|
|
34
|
+
return f(rm, c)
|
|
35
|
+
return _foo
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def iter_bind[T, U](lst: Iterable[T], f: Callable[[T], Iterable[U]]) -> Iterable[U]:
|
|
39
|
+
return chain(*map(f, lst))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def compose_filter(a: ContentFilter, b: ContentFilter) -> ContentFilter:
|
|
43
|
+
def _joined(rm: ReferenceMap, c: Content) -> Iterable[Content]:
|
|
44
|
+
return iter_bind(partial(a, rm)(c), partial(b, rm))
|
|
45
|
+
return _joined
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def compose_filters(*args: ContentFilter) -> ContentFilter:
|
|
49
|
+
return reduce(compose_filter, args, lambda _, c: [c])
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def read_markdown(text: str) -> tuple[ReferenceMap, list[Content]]:
|
|
53
|
+
hooks = get_hooks()
|
|
54
|
+
return read_markdown_string(text, hooks=hooks)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@codeblock_filter
|
|
58
|
+
def add_title(reference_map: ReferenceMap, r: ReferenceId) -> list[Content]:
|
|
59
|
+
"""
|
|
60
|
+
Changes the `open_line` member of a `CodeBlock` to reflect accepted
|
|
61
|
+
MkDocs syntax, adding a `title` attribute.
|
|
62
|
+
"""
|
|
63
|
+
codeblock: CodeBlock = reference_map.get_codeblock(r)
|
|
64
|
+
|
|
65
|
+
ids = [p.value for p in codeblock.properties if isinstance(p, Id)]
|
|
66
|
+
classes = [p.value for p in codeblock.properties if isinstance(p, Class)]
|
|
67
|
+
filenames = [p.value for p in codeblock.properties if isinstance(p, Attribute) and p.key == "file"]
|
|
68
|
+
|
|
69
|
+
title = None
|
|
70
|
+
if len(ids) == 1 and len(filenames) == 1:
|
|
71
|
+
title = f"#{ids[0]} / file: {filenames[0]}"
|
|
72
|
+
elif len(ids) == 1:
|
|
73
|
+
title = f"#{ids[0]}"
|
|
74
|
+
elif len(filenames) == 1:
|
|
75
|
+
title = f"file: {filenames[0]}"
|
|
76
|
+
elif len(ids) > 1 or len(filenames) > 1:
|
|
77
|
+
title = "error: ambiguous code block title"
|
|
78
|
+
|
|
79
|
+
open_line = "```"
|
|
80
|
+
if classes:
|
|
81
|
+
language_id = classes[0]
|
|
82
|
+
open_line += language_id
|
|
83
|
+
|
|
84
|
+
if ids or classes or title:
|
|
85
|
+
open_line += " {"
|
|
86
|
+
|
|
87
|
+
if ids:
|
|
88
|
+
open_line += str(Id(ids[0]))
|
|
89
|
+
|
|
90
|
+
for c in classes[1:]:
|
|
91
|
+
open_line += " " + str(Class(c))
|
|
92
|
+
|
|
93
|
+
if title:
|
|
94
|
+
open_line += " " + str(Attribute("title", title))
|
|
95
|
+
|
|
96
|
+
open_line += "}"
|
|
97
|
+
|
|
98
|
+
codeblock.open_line = open_line
|
|
99
|
+
return [r]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@codeblock_filter
|
|
103
|
+
def include_repl_output(reference_map: ReferenceMap, r: ReferenceId) -> list[Content]:
|
|
104
|
+
"""
|
|
105
|
+
Takes any codeblock that has the `repl` class and append its pre-computed output.
|
|
106
|
+
"""
|
|
107
|
+
codeblock: CodeBlock = reference_map.get_codeblock(r)
|
|
108
|
+
if Class("repl") not in codeblock.properties:
|
|
109
|
+
return [r]
|
|
110
|
+
|
|
111
|
+
first: CodeBlock = next(iter(reference_map.by_name(r.name)))
|
|
112
|
+
session_filename = get_attribute(first.properties, "session")
|
|
113
|
+
assert session_filename is not None
|
|
114
|
+
session_path: Path = Path(session_filename)
|
|
115
|
+
assert session_path.exists()
|
|
116
|
+
session_output_path: Path = session_path.with_suffix(".out.json")
|
|
117
|
+
assert session_output_path.exists()
|
|
118
|
+
|
|
119
|
+
session = read_session(session_output_path.open("r"))
|
|
120
|
+
command = session.commands[r.ref_count]
|
|
121
|
+
|
|
122
|
+
if not command.output:
|
|
123
|
+
return [r]
|
|
124
|
+
|
|
125
|
+
output: str
|
|
126
|
+
if command.output_type == "text/plain":
|
|
127
|
+
output = indent(f"\n``` {{.text .output}}\n{command.output}\n```", codeblock.indent)
|
|
128
|
+
else:
|
|
129
|
+
output = indent(f"\n**unknown MIME type: {command.output_type}**", codeblock.indent)
|
|
130
|
+
|
|
131
|
+
return [r, PlainText(output)]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def on_page_markdown(markdown: str, *, page: Page, config: MkDocsConfig, files: Files) -> str:
|
|
135
|
+
reference_map, content = read_markdown(markdown)
|
|
136
|
+
filtered_content = iter_bind(content, partial(compose_filters(add_title, include_repl_output), reference_map))
|
|
137
|
+
return document_to_text(reference_map, filtered_content)
|
|
@@ -1,20 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
from typing import override
|
|
2
|
+
from mkdocs.plugins import BasePlugin
|
|
3
3
|
from mkdocs.config.defaults import MkDocsConfig
|
|
4
4
|
from mkdocs.structure.pages import Page
|
|
5
5
|
from mkdocs.structure.files import Files
|
|
6
6
|
|
|
7
|
-
from entangled.commands import sync
|
|
8
|
-
from entangled.main import configure
|
|
9
|
-
|
|
10
7
|
from .on_page_markdown import on_page_markdown
|
|
11
8
|
from .config import EntangledConfig
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
logging.info("Running `entangled sync`")
|
|
17
|
-
sync()
|
|
18
|
-
|
|
10
|
+
|
|
11
|
+
class EntangledPlugin(BasePlugin[EntangledConfig]):
|
|
12
|
+
@override
|
|
19
13
|
def on_page_markdown(self, markdown: str, *, page: Page, config: MkDocsConfig, files: Files):
|
|
20
14
|
return on_page_markdown(markdown, page=page, config=config, files=files)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "mkdocs-entangled-plugin"
|
|
3
|
+
version = "0.5.0"
|
|
4
|
+
description = "Plugin for MkDocs helping with rendering Entangled (entangled.github.io) projects."
|
|
5
|
+
authors = [{ name = "Johan Hidding", email = "j.hidding@esciencecenter.nl" }]
|
|
6
|
+
requires-python = ">=3.12,<4"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
license = "Apache-2.0"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"mkdocs>=1.4.2,<2",
|
|
11
|
+
"entangled-cli>=2.2,<3",
|
|
12
|
+
"mawk>=0.1.7,<0.2",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.entry-points."mkdocs.plugins"]
|
|
16
|
+
entangled = "mkdocs_entangled:EntangledPlugin"
|
|
17
|
+
|
|
18
|
+
[dependency-groups]
|
|
19
|
+
dev = [
|
|
20
|
+
"mypy>=1.2.0,<2",
|
|
21
|
+
"pytest>=7.3.0,<8",
|
|
22
|
+
"pytest-mypy>=0.10.3,<0.11",
|
|
23
|
+
"mkdocs-material>=9.1.6,<10",
|
|
24
|
+
"pyright>=1.1.406",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[tool.hatch.build.targets.sdist]
|
|
28
|
+
include = ["mkdocs_entangled"]
|
|
29
|
+
|
|
30
|
+
[tool.hatch.build.targets.wheel]
|
|
31
|
+
include = ["mkdocs_entangled"]
|
|
32
|
+
|
|
33
|
+
[build-system]
|
|
34
|
+
requires = ["hatchling"]
|
|
35
|
+
build-backend = "hatchling.build"
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
addopts = ["--mypy"]
|
|
39
|
+
testpaths = [
|
|
40
|
+
"test"
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[tool.mypy]
|
|
44
|
+
packages = ["mkdocs_entangled"]
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
μAwk, a tiny awk-like interface in Python
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from typing import Callable, Optional
|
|
6
|
-
from functools import wraps, partial
|
|
7
|
-
import re
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"""Global counter to sort filters by the order in which they appear
|
|
11
|
-
in a class."""
|
|
12
|
-
_filter_creation_order = 0
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def match_filter(expr: str, handler: Callable[[re.Match], Optional[list[str]]], inp: str) -> Optional[list[str]]:
|
|
16
|
-
if m := re.match(expr, inp):
|
|
17
|
-
return handler(m)
|
|
18
|
-
return None
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def on_match(expr: str):
|
|
22
|
-
"""Decorator for doing search-and-replace based on a given regex.
|
|
23
|
-
|
|
24
|
-
The inner function should take as argument a `re.Match` object
|
|
25
|
-
and return a list of strings. The resulting decorated function
|
|
26
|
-
then becomes a function from string to list of strings.
|
|
27
|
-
|
|
28
|
-
If the input doesn't match the given regex, the original string
|
|
29
|
-
is returned.
|
|
30
|
-
|
|
31
|
-
The inner function may still decide to do nothing by returning None.
|
|
32
|
-
|
|
33
|
-
To erase the matched input return the empty list.
|
|
34
|
-
|
|
35
|
-
This decorator also works on class methods. It is then assumed that
|
|
36
|
-
the method has signature `(self, m: re.Match)`.
|
|
37
|
-
"""
|
|
38
|
-
def _decorator(f):
|
|
39
|
-
global _filter_creation_order
|
|
40
|
-
method = len(f.__qualname__.split('.')) > 1
|
|
41
|
-
if method:
|
|
42
|
-
@wraps(f)
|
|
43
|
-
def _impl(self, inp):
|
|
44
|
-
return match_filter(expr, partial(f, self), inp)
|
|
45
|
-
_impl._is_rule = _filter_creation_order # add tag to method
|
|
46
|
-
_filter_creation_order += 1
|
|
47
|
-
return _impl
|
|
48
|
-
else:
|
|
49
|
-
return wraps(f)(partial(match_filter, expr, f))
|
|
50
|
-
return _decorator
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def always(f):
|
|
54
|
-
"""Suppose you have a rule with `@on_match(r".*")`, then it is better
|
|
55
|
-
not to run the regex machinery at all and just pass on the string.
|
|
56
|
-
In that case it is better to use `@always`."""
|
|
57
|
-
global _filter_creation_order
|
|
58
|
-
method = len(f.__qualname__.split('.')) > 1
|
|
59
|
-
if method:
|
|
60
|
-
f._is_rule = _filter_creation_order # add tag to method
|
|
61
|
-
_filter_creation_order += 1
|
|
62
|
-
return f
|
|
63
|
-
else:
|
|
64
|
-
return f
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Rule = Callable[[str], list[str]]
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def run(rules: list[Rule], inp: str, exclusive=True) -> str:
|
|
71
|
-
"""Takes a list of rules, being function from `str` to `list[str]`.
|
|
72
|
-
The input string is split into lines, after which each line is fed
|
|
73
|
-
through the list of rules. All the results are colected into a list
|
|
74
|
-
of strings and then joined by newline.
|
|
75
|
-
"""
|
|
76
|
-
lines = inp.splitlines()
|
|
77
|
-
result = []
|
|
78
|
-
for l in lines:
|
|
79
|
-
for r in rules:
|
|
80
|
-
v = r(l)
|
|
81
|
-
if v is not None:
|
|
82
|
-
result.extend(v)
|
|
83
|
-
if exclusive:
|
|
84
|
-
break
|
|
85
|
-
else:
|
|
86
|
-
result.append(l)
|
|
87
|
-
return "\n".join(result)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
class RuleSet:
|
|
91
|
-
"""To be used as a base class for classes that contain `on_match`
|
|
92
|
-
decorated methods."""
|
|
93
|
-
def list_rules(self):
|
|
94
|
-
members = (getattr(self, m) for m in dir(self) if m[0] != '_')
|
|
95
|
-
rules = [m for m in members if hasattr(m, '_is_rule')]
|
|
96
|
-
return sorted(rules, key=lambda r: r._is_rule)
|
|
97
|
-
|
|
98
|
-
def run(self, inp: str, exclusive=True) -> str:
|
|
99
|
-
"""Runs all rules in the class on input."""
|
|
100
|
-
return run(self.list_rules(), inp, exclusive)
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
import re
|
|
4
|
-
|
|
5
|
-
from mkdocs.config.defaults import MkDocsConfig
|
|
6
|
-
from mkdocs.structure.pages import Page
|
|
7
|
-
from mkdocs.structure.files import Files
|
|
8
|
-
|
|
9
|
-
from .properties import read_properties, Id, Attribute
|
|
10
|
-
from . import mawk
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@dataclass
|
|
14
|
-
class EntangledFilter(mawk.RuleSet):
|
|
15
|
-
add_title: bool = True
|
|
16
|
-
_ignore: bool = False
|
|
17
|
-
|
|
18
|
-
@mawk.on_match(r"~~~markdown")
|
|
19
|
-
def start_ignore(self, m):
|
|
20
|
-
self._ignore = True
|
|
21
|
-
return [m[0]]
|
|
22
|
-
|
|
23
|
-
@mawk.on_match(r"~~~")
|
|
24
|
-
def stop_ignore(self, m):
|
|
25
|
-
self._ignore = False
|
|
26
|
-
return [m[0]]
|
|
27
|
-
|
|
28
|
-
@mawk.on_match(r"(\s*)``` *\{([^}]*)\}")
|
|
29
|
-
def open_code_block(self, m: re.Match) -> Optional[list[str]]:
|
|
30
|
-
if self._ignore:
|
|
31
|
-
return None
|
|
32
|
-
|
|
33
|
-
indent = m[1]
|
|
34
|
-
props = list(read_properties(m[2]))
|
|
35
|
-
|
|
36
|
-
ids = [p.value for p in props if isinstance(p, Id)]
|
|
37
|
-
filenames = [p.value for p in props if isinstance(p, Attribute) and p.key == "file"]
|
|
38
|
-
|
|
39
|
-
if self.add_title:
|
|
40
|
-
title = None
|
|
41
|
-
if len(ids) == 1 and len(filenames) == 1:
|
|
42
|
-
title = f"#{ids[0]} / file: {filenames[0]}"
|
|
43
|
-
elif len(ids) == 1:
|
|
44
|
-
title = f"#{ids[0]}"
|
|
45
|
-
elif len(filenames) == 1:
|
|
46
|
-
title = f"file: {filenames[0]}"
|
|
47
|
-
elif len(ids) > 1 or len(filenames) > 1:
|
|
48
|
-
title = f"error: ambiguous code block title"
|
|
49
|
-
|
|
50
|
-
if title is not None:
|
|
51
|
-
props.append(Attribute("title", title))
|
|
52
|
-
|
|
53
|
-
prop_str = " ".join(str(p) for p in props)
|
|
54
|
-
return [f"{indent}``` {{{prop_str}}}"]
|
|
55
|
-
|
|
56
|
-
@mawk.on_match(r"\s*```\s*$")
|
|
57
|
-
def close_code_block(self, m: re.Match) -> Optional[list[str]]:
|
|
58
|
-
return None
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def on_page_markdown(markdown: str, *, page: Page, config: MkDocsConfig, files: Files) -> str:
|
|
62
|
-
result = EntangledFilter().run(markdown)
|
|
63
|
-
return result
|
|
64
|
-
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
from typing import Optional, Protocol, TypeVar, Generic, Any, Sequence, Union
|
|
5
|
-
import re
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
T = TypeVar("T")
|
|
9
|
-
T_co = TypeVar("T_co", covariant=True)
|
|
10
|
-
|
|
11
|
-
class Parser(Protocol[T_co]):
|
|
12
|
-
def read(self, inp: str) -> Optional[tuple[T_co, str]]:
|
|
13
|
-
...
|
|
14
|
-
|
|
15
|
-
@dataclass
|
|
16
|
-
class Id:
|
|
17
|
-
value: str
|
|
18
|
-
|
|
19
|
-
def __str__(self):
|
|
20
|
-
return f"#{self.value}"
|
|
21
|
-
|
|
22
|
-
@staticmethod
|
|
23
|
-
def read(inp: str) -> Optional[tuple[Id, str]]:
|
|
24
|
-
if m := re.match("#([^ {}]+)", inp):
|
|
25
|
-
return Id(m[1]), inp[m.end():]
|
|
26
|
-
return None
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@dataclass
|
|
30
|
-
class Class:
|
|
31
|
-
value: str
|
|
32
|
-
|
|
33
|
-
def __str__(self):
|
|
34
|
-
return f".{self.value}"
|
|
35
|
-
|
|
36
|
-
@staticmethod
|
|
37
|
-
def read(inp: str) -> Optional[tuple[Class, str]]:
|
|
38
|
-
if m := re.match("\\.([^ {}]+)", inp):
|
|
39
|
-
return Class(m[1]), inp[m.end():]
|
|
40
|
-
return None
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@dataclass
|
|
44
|
-
class Attribute:
|
|
45
|
-
key: str
|
|
46
|
-
value: str
|
|
47
|
-
|
|
48
|
-
def __str__(self):
|
|
49
|
-
return f"{self.key}=\"{self.value}\""
|
|
50
|
-
|
|
51
|
-
@staticmethod
|
|
52
|
-
def read(inp: str) -> Optional[tuple[Attribute, str]]:
|
|
53
|
-
if m := re.match("([^ {}]+) *= *([^ {}\"]+)", inp):
|
|
54
|
-
return Attribute(m[1], m[2]), inp[m.end():]
|
|
55
|
-
if m := re.match("([^ {}]+) *= *\"([^\"]*)\"", inp):
|
|
56
|
-
return Attribute(m[1], m[2]), inp[m.end():]
|
|
57
|
-
return None
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
Property = Union[Attribute, Class, Id]
|
|
61
|
-
|
|
62
|
-
@dataclass
|
|
63
|
-
class Choice(Generic[T]):
|
|
64
|
-
options: Sequence[Parser[T]]
|
|
65
|
-
|
|
66
|
-
def read(self, inp: str) -> Optional[tuple[T, str]]:
|
|
67
|
-
for o in self.options:
|
|
68
|
-
rv = o.read(inp)
|
|
69
|
-
if rv is None:
|
|
70
|
-
continue
|
|
71
|
-
else:
|
|
72
|
-
return rv
|
|
73
|
-
return None
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
@dataclass
|
|
77
|
-
class Many(Generic[T]):
|
|
78
|
-
parser: Parser[T]
|
|
79
|
-
|
|
80
|
-
def read(self, inp: str) -> tuple[list[T], str]:
|
|
81
|
-
result: list[T] = []
|
|
82
|
-
while True:
|
|
83
|
-
rv = self.parser.read(inp)
|
|
84
|
-
if rv is None:
|
|
85
|
-
return result, inp
|
|
86
|
-
else:
|
|
87
|
-
value, rest = rv
|
|
88
|
-
result.append(value)
|
|
89
|
-
inp = rest
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
@dataclass
|
|
93
|
-
class Skip:
|
|
94
|
-
what: str
|
|
95
|
-
|
|
96
|
-
def read(self, inp: str) -> Optional[tuple[None, str]]:
|
|
97
|
-
if m := re.match(self.what, inp):
|
|
98
|
-
return None, inp[m.end():]
|
|
99
|
-
return None
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
@dataclass
|
|
103
|
-
class Last(Generic[T]):
|
|
104
|
-
first: Parser[Any]
|
|
105
|
-
second: Parser[T]
|
|
106
|
-
|
|
107
|
-
def read(self, inp: str) -> Optional[tuple[T, str]]:
|
|
108
|
-
rv = self.first.read(inp)
|
|
109
|
-
if rv is None:
|
|
110
|
-
return None
|
|
111
|
-
else:
|
|
112
|
-
_, rest = rv
|
|
113
|
-
return self.second.read(rest)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def tokenize(p: Parser[T]) -> Parser[T]:
|
|
117
|
-
return Last(Skip(" *"), p)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def read_properties(inp: str) -> list[Property]:
|
|
121
|
-
# Explicit typing is needed to convince MyPy of correctness
|
|
122
|
-
parsers: list[Parser[Property]] = [Id, Class, Attribute]
|
|
123
|
-
result, _ = Many(tokenize(Choice(parsers))).read(inp)
|
|
124
|
-
return result
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
[tool.poetry]
|
|
2
|
-
name = "mkdocs-entangled-plugin"
|
|
3
|
-
version = "0.3.0"
|
|
4
|
-
description = "Plugin for MkDocs helping with rendering Entangled (entangled.github.io) projects."
|
|
5
|
-
readme = "README.md"
|
|
6
|
-
authors = ["Johan Hidding <j.hidding@esciencecenter.nl>"]
|
|
7
|
-
license = "Apache 2.0"
|
|
8
|
-
packages = [
|
|
9
|
-
{ include = "mkdocs_entangled" }
|
|
10
|
-
]
|
|
11
|
-
|
|
12
|
-
[tool.poetry.plugins."mkdocs.plugins"]
|
|
13
|
-
entangled = "mkdocs_entangled:EntangledPlugin"
|
|
14
|
-
|
|
15
|
-
[tool.poetry.dependencies]
|
|
16
|
-
python = "^3.11"
|
|
17
|
-
mkdocs = "^1.4.2"
|
|
18
|
-
# entangled-cli = {path = "../entangled.py", extras = ["rich"], develop = true}
|
|
19
|
-
entangled-cli = {extras = ["rich"], version = "^2.0.0a3"}
|
|
20
|
-
|
|
21
|
-
[tool.poetry.dev-dependencies]
|
|
22
|
-
mypy = "^1.2.0"
|
|
23
|
-
pytest = "^7.3.0"
|
|
24
|
-
pytest-mypy = "^0.10.3"
|
|
25
|
-
mkdocs-material = "^9.1.6"
|
|
26
|
-
twine = "^4.0.2"
|
|
27
|
-
|
|
28
|
-
[build-system]
|
|
29
|
-
requires = ["poetry-core>=1.0.0"]
|
|
30
|
-
build-backend = "poetry.core.masonry.api"
|
|
31
|
-
|
|
32
|
-
[tool.pytest.ini_options]
|
|
33
|
-
addopts = ["--mypy"]
|
|
34
|
-
testpaths = [
|
|
35
|
-
"test"
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
[tool.mypy]
|
|
39
|
-
packages = ["mkdocs_entangled"]
|
|
File without changes
|
{mkdocs_entangled_plugin-0.3.0 → mkdocs_entangled_plugin-0.5.0}/mkdocs_entangled/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|