MDit 0.0.0.dev0__tar.gz → 0.0.0.dev1__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.
- mdit-0.0.0.dev1/PKG-INFO +24 -0
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev1}/pyproject.toml +17 -3
- mdit-0.0.0.dev1/src/MDit.egg-info/PKG-INFO +24 -0
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev1}/src/MDit.egg-info/SOURCES.txt +9 -1
- mdit-0.0.0.dev1/src/MDit.egg-info/requires.txt +20 -0
- mdit-0.0.0.dev1/src/mdit/__init__.py +133 -0
- mdit-0.0.0.dev1/src/mdit/container.py +195 -0
- mdit-0.0.0.dev1/src/mdit/data/__init__.py +3 -0
- mdit-0.0.0.dev1/src/mdit/data/file.py +75 -0
- mdit-0.0.0.dev1/src/mdit/data/schema.py +45 -0
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev1}/src/mdit/display.py +6 -12
- mdit-0.0.0.dev1/src/mdit/document.py +309 -0
- mdit-0.0.0.dev1/src/mdit/element.py +2790 -0
- mdit-0.0.0.dev1/src/mdit/generate.py +149 -0
- mdit-0.0.0.dev1/src/mdit/protocol.py +90 -0
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev1}/src/mdit/render.py +59 -8
- mdit-0.0.0.dev1/src/mdit/target.py +202 -0
- mdit-0.0.0.dev1/src/mdit/template.py +8 -0
- mdit-0.0.0.dev0/PKG-INFO +0 -11
- mdit-0.0.0.dev0/src/MDit.egg-info/PKG-INFO +0 -11
- mdit-0.0.0.dev0/src/MDit.egg-info/requires.txt +0 -7
- mdit-0.0.0.dev0/src/mdit/__init__.py +0 -6
- mdit-0.0.0.dev0/src/mdit/container.py +0 -71
- mdit-0.0.0.dev0/src/mdit/element.py +0 -558
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev1}/README.md +0 -0
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev1}/setup.cfg +0 -0
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev1}/src/MDit.egg-info/dependency_links.txt +0 -0
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev1}/src/MDit.egg-info/not-zip-safe +0 -0
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev1}/src/MDit.egg-info/top_level.txt +0 -0
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev1}/src/mdit/parse.py +0 -0
mdit-0.0.0.dev1/PKG-INFO
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: MDit
|
|
3
|
+
Version: 0.0.0.dev1
|
|
4
|
+
Requires-Python: >=3.10
|
|
5
|
+
Requires-Dist: sphinx<8
|
|
6
|
+
Requires-Dist: myst-parser<4,>3
|
|
7
|
+
Requires-Dist: pydata-sphinx-theme
|
|
8
|
+
Requires-Dist: sphinx-togglebutton
|
|
9
|
+
Requires-Dist: sphinx-design
|
|
10
|
+
Requires-Dist: markdown-it-py
|
|
11
|
+
Requires-Dist: mdit-py-plugins
|
|
12
|
+
Requires-Dist: linkify-it-py
|
|
13
|
+
Requires-Dist: readme-renderer
|
|
14
|
+
Requires-Dist: cmarkgfm
|
|
15
|
+
Requires-Dist: zundler
|
|
16
|
+
Requires-Dist: PkgData
|
|
17
|
+
Requires-Dist: PySerials
|
|
18
|
+
Requires-Dist: PyLinks
|
|
19
|
+
Requires-Dist: PyBadger
|
|
20
|
+
Requires-Dist: PyColorIt
|
|
21
|
+
Requires-Dist: IPython
|
|
22
|
+
Requires-Dist: PyProtocol
|
|
23
|
+
Requires-Dist: HTMP
|
|
24
|
+
Requires-Dist: ansi-sgr
|
|
@@ -17,15 +17,29 @@ namespaces = true
|
|
|
17
17
|
# ----------------------------------------- Project Metadata -------------------------------------
|
|
18
18
|
#
|
|
19
19
|
[project]
|
|
20
|
-
version = "0.0.0.
|
|
20
|
+
version = "0.0.0.dev1"
|
|
21
21
|
name = "MDit"
|
|
22
22
|
requires-python = ">=3.10"
|
|
23
23
|
dependencies = [
|
|
24
|
-
"
|
|
25
|
-
"
|
|
24
|
+
"sphinx < 8",
|
|
25
|
+
"myst-parser > 3, < 4",
|
|
26
|
+
"pydata-sphinx-theme",
|
|
27
|
+
"sphinx-togglebutton",
|
|
28
|
+
"sphinx-design",
|
|
26
29
|
"markdown-it-py",
|
|
27
30
|
"mdit-py-plugins",
|
|
28
31
|
"linkify-it-py",
|
|
29
32
|
"readme-renderer",
|
|
30
33
|
"cmarkgfm",
|
|
34
|
+
"zundler",
|
|
35
|
+
"PkgData",
|
|
36
|
+
"PySerials",
|
|
37
|
+
"PyLinks",
|
|
38
|
+
"PyBadger",
|
|
39
|
+
"PyColorIt",
|
|
40
|
+
"IPython",
|
|
41
|
+
"PyProtocol",
|
|
42
|
+
"HTMP",
|
|
43
|
+
"ansi-sgr",
|
|
44
|
+
|
|
31
45
|
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: MDit
|
|
3
|
+
Version: 0.0.0.dev1
|
|
4
|
+
Requires-Python: >=3.10
|
|
5
|
+
Requires-Dist: sphinx<8
|
|
6
|
+
Requires-Dist: myst-parser<4,>3
|
|
7
|
+
Requires-Dist: pydata-sphinx-theme
|
|
8
|
+
Requires-Dist: sphinx-togglebutton
|
|
9
|
+
Requires-Dist: sphinx-design
|
|
10
|
+
Requires-Dist: markdown-it-py
|
|
11
|
+
Requires-Dist: mdit-py-plugins
|
|
12
|
+
Requires-Dist: linkify-it-py
|
|
13
|
+
Requires-Dist: readme-renderer
|
|
14
|
+
Requires-Dist: cmarkgfm
|
|
15
|
+
Requires-Dist: zundler
|
|
16
|
+
Requires-Dist: PkgData
|
|
17
|
+
Requires-Dist: PySerials
|
|
18
|
+
Requires-Dist: PyLinks
|
|
19
|
+
Requires-Dist: PyBadger
|
|
20
|
+
Requires-Dist: PyColorIt
|
|
21
|
+
Requires-Dist: IPython
|
|
22
|
+
Requires-Dist: PyProtocol
|
|
23
|
+
Requires-Dist: HTMP
|
|
24
|
+
Requires-Dist: ansi-sgr
|
|
@@ -9,6 +9,14 @@ src/MDit.egg-info/top_level.txt
|
|
|
9
9
|
src/mdit/__init__.py
|
|
10
10
|
src/mdit/container.py
|
|
11
11
|
src/mdit/display.py
|
|
12
|
+
src/mdit/document.py
|
|
12
13
|
src/mdit/element.py
|
|
14
|
+
src/mdit/generate.py
|
|
13
15
|
src/mdit/parse.py
|
|
14
|
-
src/mdit/
|
|
16
|
+
src/mdit/protocol.py
|
|
17
|
+
src/mdit/render.py
|
|
18
|
+
src/mdit/target.py
|
|
19
|
+
src/mdit/template.py
|
|
20
|
+
src/mdit/data/__init__.py
|
|
21
|
+
src/mdit/data/file.py
|
|
22
|
+
src/mdit/data/schema.py
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
sphinx<8
|
|
2
|
+
myst-parser<4,>3
|
|
3
|
+
pydata-sphinx-theme
|
|
4
|
+
sphinx-togglebutton
|
|
5
|
+
sphinx-design
|
|
6
|
+
markdown-it-py
|
|
7
|
+
mdit-py-plugins
|
|
8
|
+
linkify-it-py
|
|
9
|
+
readme-renderer
|
|
10
|
+
cmarkgfm
|
|
11
|
+
zundler
|
|
12
|
+
PkgData
|
|
13
|
+
PySerials
|
|
14
|
+
PyLinks
|
|
15
|
+
PyBadger
|
|
16
|
+
PyColorIt
|
|
17
|
+
IPython
|
|
18
|
+
PyProtocol
|
|
19
|
+
HTMP
|
|
20
|
+
ansi-sgr
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Generate and process Markdown content.
|
|
2
|
+
|
|
3
|
+
References
|
|
4
|
+
----------
|
|
5
|
+
- [GitHub Flavored Markdown Spec](https://github.github.com/gfm/)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING as _TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
from mdit.container import Container, MDContainer, BlockMDContainer, InlineMDContainer
|
|
13
|
+
from mdit.document import Document
|
|
14
|
+
from mdit.generate import DocumentGenerator
|
|
15
|
+
from mdit import render, target, element, display, parse, protocol, template
|
|
16
|
+
__version__ = "XXX"
|
|
17
|
+
|
|
18
|
+
if _TYPE_CHECKING:
|
|
19
|
+
from typing import Callable
|
|
20
|
+
from mdit.protocol import ContainerContentType, ContainerInputType, Stringable, TargetConfig, ANSITargetConfig
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def generate(config: dict):
|
|
24
|
+
return DocumentGenerator().generate(config)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def document(
|
|
28
|
+
heading: element.Heading | ContainerInputType | None = None,
|
|
29
|
+
body: ContainerInputType = None,
|
|
30
|
+
section: Container | None = None,
|
|
31
|
+
footer: ContainerInputType = None,
|
|
32
|
+
frontmatter: dict | element.FrontMatter | None = None,
|
|
33
|
+
frontmatter_conditions: list[str] | None = None,
|
|
34
|
+
separate_sections: bool = False,
|
|
35
|
+
toctree_args: dict[str, str] | None = None,
|
|
36
|
+
toctree_dirhtml: bool = True,
|
|
37
|
+
target_config_md: dict[str, TargetConfig | dict] | None = None,
|
|
38
|
+
target_config_ansi: dict[str, ANSITargetConfig | dict] | None = None,
|
|
39
|
+
default_output_target: str = "sphinx",
|
|
40
|
+
deep_section_generator: Callable[[Document], str] | None = None,
|
|
41
|
+
):
|
|
42
|
+
if heading and not isinstance(heading, element.Heading):
|
|
43
|
+
heading = element.heading(content=heading, level=1)
|
|
44
|
+
body = container(body, "\n\n")
|
|
45
|
+
if isinstance(section, Container):
|
|
46
|
+
pass
|
|
47
|
+
elif not section:
|
|
48
|
+
section = section_container()
|
|
49
|
+
elif isinstance(section, dict):
|
|
50
|
+
section = section_container(**section)
|
|
51
|
+
elif isinstance(section, (list, tuple)):
|
|
52
|
+
section = section_container(*section)
|
|
53
|
+
else:
|
|
54
|
+
section = section_container(section)
|
|
55
|
+
footer = container(footer, "\n\n")
|
|
56
|
+
if isinstance(frontmatter, dict):
|
|
57
|
+
frontmatter = element.frontmatter(frontmatter)
|
|
58
|
+
target_config = {}
|
|
59
|
+
for key, config in (target_config_md or {}).items():
|
|
60
|
+
config_obj = config if isinstance(config, protocol.TargetConfig) else target.custom(**config)
|
|
61
|
+
target_config[key] = config_obj
|
|
62
|
+
for key, config in (target_config_ansi or {}).items():
|
|
63
|
+
config_obj = config if isinstance(config, protocol.ANSITargetConfig) else target.ansi(**config)
|
|
64
|
+
if key in target_config:
|
|
65
|
+
raise ValueError(f"Target config key '{key}' already exists.")
|
|
66
|
+
target_config[key] = config_obj
|
|
67
|
+
return Document(
|
|
68
|
+
heading=heading,
|
|
69
|
+
body=body,
|
|
70
|
+
section=section,
|
|
71
|
+
footer=footer,
|
|
72
|
+
frontmatter=frontmatter,
|
|
73
|
+
frontmatter_conditions=frontmatter_conditions,
|
|
74
|
+
separate_sections=separate_sections,
|
|
75
|
+
toctree_args=toctree_args,
|
|
76
|
+
toctree_dirhtml=toctree_dirhtml,
|
|
77
|
+
target_config=target_config,
|
|
78
|
+
default_output_target=default_output_target,
|
|
79
|
+
deep_section_generator=deep_section_generator,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def container(
|
|
84
|
+
content,
|
|
85
|
+
content_seperator: str,
|
|
86
|
+
html_container: Stringable | None = None,
|
|
87
|
+
html_container_attrs: dict | None = None,
|
|
88
|
+
html_container_conditions: list[str] | None = None,
|
|
89
|
+
) -> MDContainer:
|
|
90
|
+
if isinstance(content, MDContainer):
|
|
91
|
+
return content
|
|
92
|
+
cont = MDContainer(
|
|
93
|
+
content_separator=content_seperator,
|
|
94
|
+
html_container=html_container,
|
|
95
|
+
html_container_attrs=html_container_attrs,
|
|
96
|
+
html_container_conditions=html_container_conditions,
|
|
97
|
+
)
|
|
98
|
+
if not content:
|
|
99
|
+
return cont
|
|
100
|
+
if isinstance(content, dict):
|
|
101
|
+
cont.extend(**content)
|
|
102
|
+
elif isinstance(content, (list, tuple)):
|
|
103
|
+
cont.extend(*content)
|
|
104
|
+
else:
|
|
105
|
+
cont.append(content)
|
|
106
|
+
return cont
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def block_container(
|
|
110
|
+
*unlabeled_contents: ContainerContentType,
|
|
111
|
+
**labeled_contents: ContainerContentType,
|
|
112
|
+
) -> BlockMDContainer:
|
|
113
|
+
container_ = BlockMDContainer()
|
|
114
|
+
container_.extend(*unlabeled_contents, **labeled_contents)
|
|
115
|
+
return container_
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def inline_container(
|
|
119
|
+
*unlabeled_contents: ContainerContentType,
|
|
120
|
+
**labeled_contents: ContainerContentType,
|
|
121
|
+
) -> InlineMDContainer:
|
|
122
|
+
container_ = InlineMDContainer()
|
|
123
|
+
container_.extend(*unlabeled_contents, **labeled_contents)
|
|
124
|
+
return container_
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def section_container(
|
|
128
|
+
*unlabeled_contents: ContainerContentType,
|
|
129
|
+
**labeled_contents: ContainerContentType,
|
|
130
|
+
) -> Container:
|
|
131
|
+
container_ = Container()
|
|
132
|
+
container_.extend(*unlabeled_contents, **labeled_contents)
|
|
133
|
+
return container_
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING as _TYPE_CHECKING, NamedTuple as _NamedTuple
|
|
4
|
+
import re as _re
|
|
5
|
+
|
|
6
|
+
import htmp as _htmp
|
|
7
|
+
|
|
8
|
+
from mdit.protocol import MDCode as _MDCode
|
|
9
|
+
from mdit import display as _display
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
if _TYPE_CHECKING:
|
|
13
|
+
from typing import Literal
|
|
14
|
+
from mdit.protocol import ContainerInputType, ContainerContentType, ContainerContentType, TargetConfigType, Stringable
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ContainerContent(_NamedTuple):
|
|
18
|
+
content: ContainerContentType
|
|
19
|
+
conditions: list[str]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Container:
|
|
23
|
+
|
|
24
|
+
def __init__(self, data: dict[str | int, ContainerContent] | None = None):
|
|
25
|
+
self._data = data or {}
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
def append(
|
|
29
|
+
self,
|
|
30
|
+
content,
|
|
31
|
+
conditions: str | list[str] | None = None,
|
|
32
|
+
key: str | int | None = None
|
|
33
|
+
) -> str | int:
|
|
34
|
+
if not key:
|
|
35
|
+
key = max((key for key in self._data.keys() if isinstance(key, int)), default=-1) + 1
|
|
36
|
+
if key in self._data:
|
|
37
|
+
raise ValueError("Key already exists in content.")
|
|
38
|
+
if not conditions:
|
|
39
|
+
conditions = []
|
|
40
|
+
elif isinstance(conditions, str):
|
|
41
|
+
conditions = [conditions]
|
|
42
|
+
else:
|
|
43
|
+
conditions = list(conditions)
|
|
44
|
+
self._data[key] = ContainerContent(content=content, conditions=conditions)
|
|
45
|
+
return key
|
|
46
|
+
|
|
47
|
+
def extend(self, *unlabeled_contents, **labeled_contents) -> list[str | int]:
|
|
48
|
+
|
|
49
|
+
def resolve_value(v):
|
|
50
|
+
if isinstance(v, (list, tuple)):
|
|
51
|
+
val = v[0]
|
|
52
|
+
cond = v[1] if len(v) > 1 else None
|
|
53
|
+
return val, cond
|
|
54
|
+
return v, None
|
|
55
|
+
|
|
56
|
+
added_keys = []
|
|
57
|
+
if unlabeled_contents:
|
|
58
|
+
first_available_key = max(
|
|
59
|
+
(key for key in self._data.keys() if isinstance(key, int)), default=-1
|
|
60
|
+
) + 1
|
|
61
|
+
for idx, value in enumerate(unlabeled_contents):
|
|
62
|
+
content, conditions = resolve_value(value)
|
|
63
|
+
added_keys.append(self.append(content, conditions, first_available_key + idx))
|
|
64
|
+
if labeled_contents:
|
|
65
|
+
for key, value in labeled_contents.items():
|
|
66
|
+
content, conditions = resolve_value(value)
|
|
67
|
+
added_keys.append(self.append(content, conditions, key))
|
|
68
|
+
return added_keys
|
|
69
|
+
|
|
70
|
+
def elements(
|
|
71
|
+
self,
|
|
72
|
+
target: TargetConfigType | None = None,
|
|
73
|
+
filters: str | list[str] | None = None,
|
|
74
|
+
string: bool = False,
|
|
75
|
+
) -> list:
|
|
76
|
+
elements = []
|
|
77
|
+
if isinstance(filters, str):
|
|
78
|
+
filters = [filters]
|
|
79
|
+
for content, conditions in self.values():
|
|
80
|
+
if not filters or not conditions or any(filter in conditions for filter in filters):
|
|
81
|
+
if not string:
|
|
82
|
+
elements.append(content)
|
|
83
|
+
elif isinstance(content, _MDCode):
|
|
84
|
+
elements.append(content.source(target=target, filters=filters))
|
|
85
|
+
else:
|
|
86
|
+
elements.append(str(content))
|
|
87
|
+
return elements
|
|
88
|
+
|
|
89
|
+
def get(self, key: str | int, default=None):
|
|
90
|
+
return self._data.get(key, default)
|
|
91
|
+
|
|
92
|
+
def keys(self):
|
|
93
|
+
return self._data.keys()
|
|
94
|
+
|
|
95
|
+
def values(self):
|
|
96
|
+
return self._data.values()
|
|
97
|
+
|
|
98
|
+
def items(self):
|
|
99
|
+
return self._data.items()
|
|
100
|
+
|
|
101
|
+
def __getitem__(self, item):
|
|
102
|
+
return self._data[item]
|
|
103
|
+
|
|
104
|
+
def __setitem__(self, key, value):
|
|
105
|
+
self._data[key] = value
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
def __contains__(self, item):
|
|
109
|
+
return item in self._data
|
|
110
|
+
|
|
111
|
+
def __bool__(self):
|
|
112
|
+
return bool(self._data)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class MDContainer(Container):
|
|
116
|
+
|
|
117
|
+
_IS_MD_CODE = True
|
|
118
|
+
|
|
119
|
+
def __init__(
|
|
120
|
+
self,
|
|
121
|
+
content: dict[str | int, ContainerContent] | None = None,
|
|
122
|
+
content_separator: str = "\n\n",
|
|
123
|
+
html_container: Stringable | None = None,
|
|
124
|
+
html_container_attrs: dict | None = None,
|
|
125
|
+
html_container_conditions: list[str] | None = None,
|
|
126
|
+
):
|
|
127
|
+
super().__init__(content)
|
|
128
|
+
self.content_separator = content_separator
|
|
129
|
+
self.html_container = html_container
|
|
130
|
+
self.html_container_attrs = html_container_attrs or {}
|
|
131
|
+
self.html_container_conditions = html_container_conditions or []
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
def source(self, target: TargetConfigType | None = None, filters: str | list[str] | None = None) -> str:
|
|
135
|
+
elements = self.elements(target=target, filters=filters, string=True)
|
|
136
|
+
elements_str = self.content_separator.join(elements)
|
|
137
|
+
if self.html_container and self.html_container_attrs and (
|
|
138
|
+
not filters
|
|
139
|
+
or not self.html_container_conditions
|
|
140
|
+
or any(filter in self.html_container_conditions for filter in filters)
|
|
141
|
+
):
|
|
142
|
+
container_func = getattr(_htmp.element, str(self.html_container))
|
|
143
|
+
return container_func(_htmp.elementor.markdown(elements_str), self.html_container_attrs).source(indent=-1)
|
|
144
|
+
return elements_str
|
|
145
|
+
|
|
146
|
+
def display(self, target: TargetConfigType | None = None, filters: str | list[str] | None = None) -> None:
|
|
147
|
+
"""Display the element in an IPython notebook."""
|
|
148
|
+
_display.ipython(self.source(target=target, filters=filters))
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def code_fence_count(self) -> int:
|
|
153
|
+
pattern = _re.compile(r'^\s{0, 3}(`{3,}|~{3,}|:{3,})', _re.MULTILINE)
|
|
154
|
+
counts = []
|
|
155
|
+
for content, _ in self._data.values():
|
|
156
|
+
if isinstance(content, _MDCode):
|
|
157
|
+
counts.append(content.code_fence_count)
|
|
158
|
+
else:
|
|
159
|
+
matches = pattern.findall(str(content))
|
|
160
|
+
if matches:
|
|
161
|
+
counts.append(max(len(match) for match in matches))
|
|
162
|
+
return max(counts, default=0)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# def __str__(self):
|
|
166
|
+
# if not self._block:
|
|
167
|
+
# if any(not isinstance(elem, str) for elem in self._content.values()):
|
|
168
|
+
# raise ValueError("Inline elements must have string content.")
|
|
169
|
+
# elif self._leaf:
|
|
170
|
+
# if any(isinstance(elem, Element) and elem.is_block for elem in self._content.values()):
|
|
171
|
+
# raise ValueError("Leaf block elements cannot contain block content.")
|
|
172
|
+
# content = "".join(str(elem) for elem in self._content.values())
|
|
173
|
+
# md = self._md.replace("${{content}}", content)
|
|
174
|
+
# newlines_before, newlines_after = [
|
|
175
|
+
# newlines_count if isinstance(newlines_count, int) else (1 if self._block else 0)
|
|
176
|
+
# for newlines_count in (self.newlines_before, self.newlines_after)
|
|
177
|
+
# ]
|
|
178
|
+
# return f"{newlines_before * '\n'}{md}{newlines_after * '\n'}"
|
|
179
|
+
|
|
180
|
+
def __str__(self) -> str:
|
|
181
|
+
return self.source()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class BlockMDContainer(MDContainer):
|
|
185
|
+
|
|
186
|
+
def __init__(self, content: dict[str | int, ContainerContent] | None = None):
|
|
187
|
+
super().__init__(content, content_separator="\n\n")
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class InlineMDContainer(MDContainer):
|
|
192
|
+
|
|
193
|
+
def __init__(self, content: dict[str | int, ContainerContent] | None = None):
|
|
194
|
+
super().__init__(content, content_separator="")
|
|
195
|
+
return
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Get package data files."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path as _Path
|
|
4
|
+
|
|
5
|
+
import pkgdata as _pkgdata
|
|
6
|
+
import pyserials as _ps
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
path = _pkgdata.get_package_path_from_caller(top_level=True) / "data"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def template(group: str, name: str) -> str:
|
|
13
|
+
"""Get the string content of a template."""
|
|
14
|
+
dir_path = path / "template" / group
|
|
15
|
+
for filepath in dir_path.iterdir():
|
|
16
|
+
if filepath.stem == name:
|
|
17
|
+
return filepath.read_text()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def schema(schema_id: str | None = None, relative_uri: bool = True) -> dict:
|
|
21
|
+
"""Get all JSON schemas as a dictionary of schema IDs to schema objects.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
schema_id : str, optional
|
|
26
|
+
If provided, return only the schema with the given ID, otherwise all schemas.
|
|
27
|
+
The schema ID can be either an absolute or relative URI, i.e., with or without
|
|
28
|
+
the "https://docsman.repodynamics.com/schema/" prefix.
|
|
29
|
+
relative_uri : bool, default: True
|
|
30
|
+
Only applies when `schema_id` is not provided:
|
|
31
|
+
If True, the schema IDs (i.e., the keys of the returned dictionary)
|
|
32
|
+
will be relative URIs instead of absolute URIs,
|
|
33
|
+
i.e., without the "https://docsman.repodynamics.com/schema/" prefix.
|
|
34
|
+
|
|
35
|
+
Returns
|
|
36
|
+
-------
|
|
37
|
+
dict
|
|
38
|
+
If `schema_id` is provided, the schema object is returned,
|
|
39
|
+
otherwise a dictionary of JSON schemas with their IDs as keys.
|
|
40
|
+
The schema IDs are relative URIs if `relative_uri` is True.
|
|
41
|
+
"""
|
|
42
|
+
dir_path = path / "schema"
|
|
43
|
+
if schema_id:
|
|
44
|
+
filepath = dir_path / schema_id.removeprefix("https://docsman.repodynamics.com/schema/")
|
|
45
|
+
filepath_full = filepath.with_suffix(".yaml")
|
|
46
|
+
return _ps.read.yaml_from_file(path=filepath_full)
|
|
47
|
+
schemas = {}
|
|
48
|
+
for filepath in dir_path.glob("**/*.yaml"):
|
|
49
|
+
schema = _ps.read.yaml_from_file(path=filepath)
|
|
50
|
+
schema_id = schema["$id"].removeprefix(
|
|
51
|
+
"https://docsman.repodynamics.com/schema/" if relative_uri else ""
|
|
52
|
+
)
|
|
53
|
+
schemas[schema_id] = schema
|
|
54
|
+
return schemas
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def from_filepath(filepath: str | _Path) -> str | dict | list:
|
|
58
|
+
"""Get a package data file from its relative path.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
filepath : str
|
|
63
|
+
Path of the data file relative to the package's `data` directory.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
file_content : str | dict | list
|
|
68
|
+
The content of the file.
|
|
69
|
+
If the file is a serialized data structure (e.g., JSON or YAML),
|
|
70
|
+
the content will be deserialized, otherwise a string is returned.
|
|
71
|
+
"""
|
|
72
|
+
absolute_filepath = path / filepath
|
|
73
|
+
if absolute_filepath.suffix in (".json", ".yaml", ".yml", "toml"):
|
|
74
|
+
return _ps.read.from_file(path=absolute_filepath)
|
|
75
|
+
return absolute_filepath.read_text()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Work with JSON schemas defined by the package."""
|
|
2
|
+
|
|
3
|
+
from typing import Callable as _Callable
|
|
4
|
+
|
|
5
|
+
import referencing as _referencing
|
|
6
|
+
from referencing import jsonschema as _ref_jsonschema
|
|
7
|
+
import jsonschemata as _jsonschemata
|
|
8
|
+
import pyserials as _ps
|
|
9
|
+
|
|
10
|
+
from mdit.data import file as _file
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def make_registry(
|
|
14
|
+
dynamic: bool = False,
|
|
15
|
+
crawl: bool = True,
|
|
16
|
+
add_resources: list[dict | _referencing.Resource | tuple[str, dict | _referencing.Resource]] | None = None,
|
|
17
|
+
add_resources_default_spec: _referencing.Specification = _ref_jsonschema.DRAFT202012,
|
|
18
|
+
retrieval_func: _Callable[[str], str | _referencing.Resource] = None,
|
|
19
|
+
) -> tuple[_referencing.Registry, dict[str, dict]]:
|
|
20
|
+
schemata = _file.schema(relative_uri=False)
|
|
21
|
+
resources = add_resources or []
|
|
22
|
+
for schema in schemata.values():
|
|
23
|
+
_jsonschemata.edit.required_last(schema)
|
|
24
|
+
resources.append(schema)
|
|
25
|
+
registry = _jsonschemata.registry.make(
|
|
26
|
+
dynamic=dynamic,
|
|
27
|
+
crawl=crawl,
|
|
28
|
+
add_resources=resources,
|
|
29
|
+
add_resources_default_spec=add_resources_default_spec,
|
|
30
|
+
retrieval_func=retrieval_func,
|
|
31
|
+
)
|
|
32
|
+
return registry, schemata
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def validate(data: dict, schema_id: str):
|
|
36
|
+
_ps.validate.jsonschema(
|
|
37
|
+
data=data,
|
|
38
|
+
schema=_SCHEMATA[schema_id],
|
|
39
|
+
registry=_REGISTRY,
|
|
40
|
+
fill_defaults=True,
|
|
41
|
+
)
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# _REGISTRY, _SCHEMATA = make_registry()
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import webbrowser as _webbrowser
|
|
4
4
|
import tempfile as _tempfile
|
|
5
5
|
import time as _time
|
|
6
|
-
from pathlib import Path as _Path
|
|
7
6
|
|
|
8
7
|
from IPython import display as _display
|
|
9
8
|
|
|
@@ -20,17 +19,15 @@ def browser(content: str) -> None:
|
|
|
20
19
|
content : str
|
|
21
20
|
HTML content to display.
|
|
22
21
|
"""
|
|
23
|
-
with _tempfile.NamedTemporaryFile('w',
|
|
22
|
+
with _tempfile.NamedTemporaryFile('w', suffix='.html') as temp_file:
|
|
24
23
|
temp_file.write(content)
|
|
25
24
|
temp_file.flush()
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
_time.sleep(10)
|
|
29
|
-
_Path(temp_filepath).unlink()
|
|
25
|
+
_webbrowser.open(f'file://{temp_file.name}')
|
|
26
|
+
_time.sleep(11)
|
|
30
27
|
return
|
|
31
28
|
|
|
32
29
|
|
|
33
|
-
def ipython(content: str, as_md: bool =
|
|
30
|
+
def ipython(content: str, as_md: bool = True) -> None:
|
|
34
31
|
"""Display HTML or Markdown content in an IPython notebook.
|
|
35
32
|
|
|
36
33
|
This function uses the `IPython.display` module to render the content
|
|
@@ -44,9 +41,6 @@ def ipython(content: str, as_md: bool = False) -> None:
|
|
|
44
41
|
If True, the function uses the `IPython.display.Markdown` renderer,
|
|
45
42
|
otherwise (by default) it uses the `IPython.display.HTML` renderer
|
|
46
43
|
"""
|
|
47
|
-
if
|
|
48
|
-
|
|
49
|
-
_display.display(renderer(content))
|
|
50
|
-
return
|
|
51
|
-
|
|
44
|
+
renderer = _display.Markdown if as_md else _display.HTML
|
|
45
|
+
_display.display(renderer(content))
|
|
52
46
|
return
|