MDit 0.0.0.dev0__tar.gz → 0.0.0.dev2__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.dev2/PKG-INFO +27 -0
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/pyproject.toml +19 -3
- mdit-0.0.0.dev2/src/MDit.egg-info/PKG-INFO +27 -0
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/src/MDit.egg-info/SOURCES.txt +12 -1
- mdit-0.0.0.dev2/src/MDit.egg-info/requires.txt +23 -0
- mdit-0.0.0.dev2/src/mdit/__init__.py +220 -0
- mdit-0.0.0.dev2/src/mdit/container.py +209 -0
- mdit-0.0.0.dev2/src/mdit/data/__init__.py +3 -0
- mdit-0.0.0.dev2/src/mdit/data/file.py +75 -0
- mdit-0.0.0.dev2/src/mdit/data/schema.py +50 -0
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/src/mdit/display.py +18 -11
- mdit-0.0.0.dev2/src/mdit/document.py +300 -0
- mdit-0.0.0.dev2/src/mdit/element.py +3718 -0
- mdit-0.0.0.dev2/src/mdit/generate.py +151 -0
- mdit-0.0.0.dev2/src/mdit/protocol.py +47 -0
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/src/mdit/render.py +87 -48
- mdit-0.0.0.dev2/src/mdit/renderable.py +68 -0
- mdit-0.0.0.dev2/src/mdit/target/__init__.py +300 -0
- mdit-0.0.0.dev2/src/mdit/target/md.py +166 -0
- mdit-0.0.0.dev2/src/mdit/target/rich.py +502 -0
- mdit-0.0.0.dev2/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.dev2}/README.md +0 -0
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/setup.cfg +0 -0
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/src/MDit.egg-info/dependency_links.txt +0 -0
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/src/MDit.egg-info/not-zip-safe +0 -0
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/src/MDit.egg-info/top_level.txt +0 -0
- {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/src/mdit/parse.py +0 -0
mdit-0.0.0.dev2/PKG-INFO
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: MDit
|
|
3
|
+
Version: 0.0.0.dev2
|
|
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: jupyterlab_myst
|
|
23
|
+
Requires-Dist: PyProtocol
|
|
24
|
+
Requires-Dist: HTMP
|
|
25
|
+
Requires-Dist: ansi-sgr
|
|
26
|
+
Requires-Dist: pygments
|
|
27
|
+
Requires-Dist: rich
|
|
@@ -17,15 +17,31 @@ namespaces = true
|
|
|
17
17
|
# ----------------------------------------- Project Metadata -------------------------------------
|
|
18
18
|
#
|
|
19
19
|
[project]
|
|
20
|
-
version = "0.0.0.
|
|
20
|
+
version = "0.0.0.dev2"
|
|
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
|
+
"jupyterlab_myst",
|
|
42
|
+
"PyProtocol",
|
|
43
|
+
"HTMP",
|
|
44
|
+
"ansi-sgr",
|
|
45
|
+
"pygments",
|
|
46
|
+
"rich",
|
|
31
47
|
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: MDit
|
|
3
|
+
Version: 0.0.0.dev2
|
|
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: jupyterlab_myst
|
|
23
|
+
Requires-Dist: PyProtocol
|
|
24
|
+
Requires-Dist: HTMP
|
|
25
|
+
Requires-Dist: ansi-sgr
|
|
26
|
+
Requires-Dist: pygments
|
|
27
|
+
Requires-Dist: rich
|
|
@@ -9,6 +9,17 @@ 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/renderable.py
|
|
19
|
+
src/mdit/template.py
|
|
20
|
+
src/mdit/data/__init__.py
|
|
21
|
+
src/mdit/data/file.py
|
|
22
|
+
src/mdit/data/schema.py
|
|
23
|
+
src/mdit/target/__init__.py
|
|
24
|
+
src/mdit/target/md.py
|
|
25
|
+
src/mdit/target/rich.py
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
jupyterlab_myst
|
|
19
|
+
PyProtocol
|
|
20
|
+
HTMP
|
|
21
|
+
ansi-sgr
|
|
22
|
+
pygments
|
|
23
|
+
rich
|
|
@@ -0,0 +1,220 @@
|
|
|
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 import display
|
|
13
|
+
from mdit.container import Container, MDContainer
|
|
14
|
+
from mdit.document import Document
|
|
15
|
+
from mdit.generate import DocumentGenerator
|
|
16
|
+
from mdit import render, target, element, parse, protocol, template
|
|
17
|
+
|
|
18
|
+
if _TYPE_CHECKING:
|
|
19
|
+
from typing import Callable
|
|
20
|
+
from mdit.protocol import ContainerContentType, ContainerContentInputType, ContainerContentSingleInputType, Stringable, TargetConfig, TargetConfigs, MDTargetConfig, RichTargetConfig, ContainerContentInputType
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def generate(config: dict | list):
|
|
24
|
+
return DocumentGenerator().generate(config)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def document(
|
|
28
|
+
heading: element.Heading | MDContainer | ContainerContentInputType = None,
|
|
29
|
+
body: MDContainer | ContainerContentInputType = None,
|
|
30
|
+
section: Container | None = None,
|
|
31
|
+
footer: MDContainer | ContainerContentInputType = None,
|
|
32
|
+
frontmatter: dict | element.FrontMatter | None = None,
|
|
33
|
+
frontmatter_conditions: list[str] | None = None,
|
|
34
|
+
separate_sections: bool = False,
|
|
35
|
+
current_section_key: list[str | int] | None = None,
|
|
36
|
+
toctree_args: dict[str, str] | None = None,
|
|
37
|
+
toctree_dirhtml: bool = True,
|
|
38
|
+
target_configs_md: dict[str, MDTargetConfig | dict] | None = None,
|
|
39
|
+
target_configs_rich: dict[str, RichTargetConfig | dict] | None = None,
|
|
40
|
+
target_default: str = "sphinx",
|
|
41
|
+
deep_section_generator: Callable[[Document], str] | None = None,
|
|
42
|
+
content_separator_heading: str = "",
|
|
43
|
+
):
|
|
44
|
+
# Process target configs
|
|
45
|
+
target_configs = {}
|
|
46
|
+
for key, config in (target_configs_md or {}).items():
|
|
47
|
+
config_obj = config if isinstance(config, protocol.MDTargetConfig) else target.md.Config(**config)
|
|
48
|
+
target_configs[key] = config_obj
|
|
49
|
+
for key, config in (target_configs_rich or {}).items():
|
|
50
|
+
config_obj = config if isinstance(config, protocol.RichTargetConfig) else target.rich.Config(**config)
|
|
51
|
+
if key in target_configs:
|
|
52
|
+
raise ValueError(f"Target config key '{key}' already exists.")
|
|
53
|
+
target_configs[key] = config_obj
|
|
54
|
+
# Process heading
|
|
55
|
+
if heading and not isinstance(heading, element.Heading):
|
|
56
|
+
heading = element.heading(
|
|
57
|
+
content=heading,
|
|
58
|
+
target_configs=target_configs,
|
|
59
|
+
target_default=target_default,
|
|
60
|
+
)
|
|
61
|
+
body = to_block_container(body)
|
|
62
|
+
if isinstance(section, Container):
|
|
63
|
+
pass
|
|
64
|
+
elif not section:
|
|
65
|
+
section = section_container()
|
|
66
|
+
elif isinstance(section, dict):
|
|
67
|
+
section = section_container(**section)
|
|
68
|
+
elif isinstance(section, (list, tuple)):
|
|
69
|
+
section = section_container(*section)
|
|
70
|
+
else:
|
|
71
|
+
section = section_container(section)
|
|
72
|
+
footer = to_block_container(footer)
|
|
73
|
+
if isinstance(frontmatter, dict):
|
|
74
|
+
frontmatter = element.frontmatter(frontmatter)
|
|
75
|
+
return Document(
|
|
76
|
+
heading=heading,
|
|
77
|
+
body=body,
|
|
78
|
+
section=section,
|
|
79
|
+
footer=footer,
|
|
80
|
+
frontmatter=frontmatter,
|
|
81
|
+
frontmatter_conditions=frontmatter_conditions,
|
|
82
|
+
separate_sections=separate_sections,
|
|
83
|
+
current_section_key=current_section_key,
|
|
84
|
+
toctree_args=toctree_args,
|
|
85
|
+
toctree_dirhtml=toctree_dirhtml,
|
|
86
|
+
target_configs=target_configs,
|
|
87
|
+
target_default=target_default,
|
|
88
|
+
deep_section_generator=deep_section_generator,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def block_container(
|
|
92
|
+
*contents: ContainerContentInputType,
|
|
93
|
+
html_container: Stringable | None = None,
|
|
94
|
+
html_container_attrs: dict | None = None,
|
|
95
|
+
html_container_conditions: list[str] | None = None,
|
|
96
|
+
target_configs: TargetConfigs = None,
|
|
97
|
+
target_default: str = "sphinx",
|
|
98
|
+
) -> MDContainer:
|
|
99
|
+
return container(
|
|
100
|
+
*contents,
|
|
101
|
+
content_separator="\n\n",
|
|
102
|
+
html_container=html_container,
|
|
103
|
+
html_container_attrs=html_container_attrs,
|
|
104
|
+
html_container_conditions=html_container_conditions,
|
|
105
|
+
target_configs=target_configs,
|
|
106
|
+
target_default=target_default,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def inline_container(
|
|
111
|
+
*contents: ContainerContentInputType,
|
|
112
|
+
separator: str = "",
|
|
113
|
+
html_container: Stringable | None = None,
|
|
114
|
+
html_container_attrs: dict | None = None,
|
|
115
|
+
html_container_conditions: list[str] | None = None,
|
|
116
|
+
target_configs: TargetConfigs = None,
|
|
117
|
+
target_default: str = "sphinx",
|
|
118
|
+
) -> MDContainer:
|
|
119
|
+
return container(
|
|
120
|
+
*contents,
|
|
121
|
+
content_separator=separator,
|
|
122
|
+
html_container=html_container,
|
|
123
|
+
html_container_attrs=html_container_attrs,
|
|
124
|
+
html_container_conditions=html_container_conditions,
|
|
125
|
+
target_configs=target_configs,
|
|
126
|
+
target_default=target_default,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def container(
|
|
131
|
+
*contents: ContainerContentInputType,
|
|
132
|
+
content_separator: str,
|
|
133
|
+
html_container: Stringable | None = None,
|
|
134
|
+
html_container_attrs: dict | None = None,
|
|
135
|
+
html_container_conditions: list[str] | None = None,
|
|
136
|
+
target_configs: TargetConfigs = None,
|
|
137
|
+
target_default: str = "sphinx",
|
|
138
|
+
) -> MDContainer:
|
|
139
|
+
container_ = MDContainer(
|
|
140
|
+
content_separator=content_separator,
|
|
141
|
+
html_container=html_container,
|
|
142
|
+
html_container_attrs=html_container_attrs,
|
|
143
|
+
html_container_conditions=html_container_conditions,
|
|
144
|
+
target_configs=target_configs,
|
|
145
|
+
target_default=target_default,
|
|
146
|
+
)
|
|
147
|
+
container_.extend(list(contents))
|
|
148
|
+
return container_
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def section_container(
|
|
152
|
+
*unlabeled_contents: ContainerContentInputType,
|
|
153
|
+
**labeled_contents: ContainerContentInputType,
|
|
154
|
+
) -> Container:
|
|
155
|
+
container_ = Container()
|
|
156
|
+
container_.extend(*unlabeled_contents, **labeled_contents)
|
|
157
|
+
return container_
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def to_block_container(
|
|
161
|
+
content: MDContainer | ContainerContentInputType,
|
|
162
|
+
separator: str = "\n\n",
|
|
163
|
+
html_container: Stringable | None = None,
|
|
164
|
+
html_container_attrs: dict | None = None,
|
|
165
|
+
html_container_conditions: list[str] | None = None,
|
|
166
|
+
target_configs: TargetConfigs = None,
|
|
167
|
+
target_default: str = "sphinx",
|
|
168
|
+
) -> MDContainer:
|
|
169
|
+
return to_md_container(
|
|
170
|
+
content,
|
|
171
|
+
content_separator=separator,
|
|
172
|
+
html_container=html_container,
|
|
173
|
+
html_container_attrs=html_container_attrs,
|
|
174
|
+
html_container_conditions=html_container_conditions,
|
|
175
|
+
target_configs=target_configs,
|
|
176
|
+
target_default=target_default,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def to_inline_container(
|
|
181
|
+
content: MDContainer | ContainerContentInputType,
|
|
182
|
+
separator: str = "",
|
|
183
|
+
html_container: Stringable | None = None,
|
|
184
|
+
html_container_attrs: dict | None = None,
|
|
185
|
+
html_container_conditions: list[str] | None = None,
|
|
186
|
+
target_configs: TargetConfigs = None,
|
|
187
|
+
target_default: str = "sphinx",
|
|
188
|
+
) -> MDContainer:
|
|
189
|
+
return to_md_container(
|
|
190
|
+
content,
|
|
191
|
+
content_separator=separator,
|
|
192
|
+
html_container=html_container,
|
|
193
|
+
html_container_attrs=html_container_attrs,
|
|
194
|
+
html_container_conditions=html_container_conditions,
|
|
195
|
+
target_configs=target_configs,
|
|
196
|
+
target_default=target_default,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def to_md_container(
|
|
201
|
+
content: MDContainer | ContainerContentInputType,
|
|
202
|
+
content_separator: str,
|
|
203
|
+
html_container: Stringable | None = None,
|
|
204
|
+
html_container_attrs: dict | None = None,
|
|
205
|
+
html_container_conditions: list[str] | None = None,
|
|
206
|
+
target_configs: TargetConfigs = None,
|
|
207
|
+
target_default: str = "sphinx",
|
|
208
|
+
) -> MDContainer:
|
|
209
|
+
if isinstance(content, MDContainer):
|
|
210
|
+
return content
|
|
211
|
+
container_ = MDContainer(
|
|
212
|
+
content_separator=content_separator,
|
|
213
|
+
html_container=html_container,
|
|
214
|
+
html_container_attrs=html_container_attrs,
|
|
215
|
+
html_container_conditions=html_container_conditions,
|
|
216
|
+
target_configs=target_configs,
|
|
217
|
+
target_default=target_default,
|
|
218
|
+
)
|
|
219
|
+
container_.extend(content)
|
|
220
|
+
return container_
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING as _TYPE_CHECKING, NamedTuple as _NamedTuple
|
|
4
|
+
|
|
5
|
+
import htmp as _htmp
|
|
6
|
+
import rich
|
|
7
|
+
import rich.text
|
|
8
|
+
import rich.markdown
|
|
9
|
+
|
|
10
|
+
from mdit.protocol import MDITRenderable as _MDCode
|
|
11
|
+
from mdit.renderable import Renderable as _Renderable
|
|
12
|
+
|
|
13
|
+
if _TYPE_CHECKING:
|
|
14
|
+
from rich.console import RenderableType
|
|
15
|
+
from mdit.protocol import ContainerContentType, ContainerContentConditionType, ContainerContentInputType, TargetConfigs, Stringable, MDTargetConfig, RichTargetConfig
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ContainerContent(_NamedTuple):
|
|
19
|
+
content: ContainerContentType
|
|
20
|
+
conditions: list[str]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Container:
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
data: dict[str | int, ContainerContent] | None = None,
|
|
28
|
+
*args,
|
|
29
|
+
**kwargs
|
|
30
|
+
):
|
|
31
|
+
self._data = data or {}
|
|
32
|
+
super().__init__(*args, **kwargs)
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
def append(
|
|
36
|
+
self,
|
|
37
|
+
content: ContainerContentType,
|
|
38
|
+
conditions: ContainerContentConditionType = None,
|
|
39
|
+
key: str | int | None = None
|
|
40
|
+
) -> str | int:
|
|
41
|
+
if not key:
|
|
42
|
+
key = max((key for key in self._data.keys() if isinstance(key, int)), default=-1) + 1
|
|
43
|
+
if key in self._data:
|
|
44
|
+
raise ValueError("Key already exists in content.")
|
|
45
|
+
if not conditions:
|
|
46
|
+
conditions = []
|
|
47
|
+
elif isinstance(conditions, str):
|
|
48
|
+
conditions = [conditions]
|
|
49
|
+
else:
|
|
50
|
+
conditions = list(conditions)
|
|
51
|
+
self._data[key] = ContainerContent(content=content, conditions=conditions)
|
|
52
|
+
return key
|
|
53
|
+
|
|
54
|
+
def extend(self, *unlabeled_contents: ContainerContentInputType, **labeled_contents: ContainerContentInputType) -> list[str | int]:
|
|
55
|
+
|
|
56
|
+
def resolve_value(input_values):
|
|
57
|
+
if not input_values:
|
|
58
|
+
return
|
|
59
|
+
if isinstance(input_values, list):
|
|
60
|
+
for input_value in input_values:
|
|
61
|
+
yield from resolve_value(input_value)
|
|
62
|
+
elif isinstance(input_values, tuple):
|
|
63
|
+
val = input_values[0]
|
|
64
|
+
cond = input_values[1] if len(input_values) > 1 else None
|
|
65
|
+
key = input_values[2] if len(input_values) > 2 else None
|
|
66
|
+
yield val, cond, key
|
|
67
|
+
elif isinstance(input_values, dict):
|
|
68
|
+
for k, v in input_values.items():
|
|
69
|
+
key = k
|
|
70
|
+
if isinstance(v, (list, tuple)):
|
|
71
|
+
val = v[0]
|
|
72
|
+
cond = v[1] if len(v) > 1 else None
|
|
73
|
+
else:
|
|
74
|
+
val = v
|
|
75
|
+
cond = None
|
|
76
|
+
yield val, cond, key
|
|
77
|
+
else:
|
|
78
|
+
yield input_values, None, None
|
|
79
|
+
|
|
80
|
+
added_keys = []
|
|
81
|
+
if unlabeled_contents:
|
|
82
|
+
first_available_key = max(
|
|
83
|
+
(key for key in self._data.keys() if isinstance(key, int)), default=-1
|
|
84
|
+
) + 1
|
|
85
|
+
for idx, value in enumerate(unlabeled_contents):
|
|
86
|
+
for content, conditions, key in resolve_value(value):
|
|
87
|
+
added_keys.append(self.append(content, conditions, key or first_available_key + idx))
|
|
88
|
+
if labeled_contents:
|
|
89
|
+
for key, value in labeled_contents.items():
|
|
90
|
+
for content, conditions, sub_key in resolve_value(value):
|
|
91
|
+
final_key = key if sub_key is None else f"{key}.{sub_key}"
|
|
92
|
+
added_keys.append(self.append(content, conditions, final_key))
|
|
93
|
+
return added_keys
|
|
94
|
+
|
|
95
|
+
def elements(
|
|
96
|
+
self,
|
|
97
|
+
target: TargetConfigs | None = None,
|
|
98
|
+
filters: str | list[str] | None = None,
|
|
99
|
+
source: bool = False,
|
|
100
|
+
) -> list:
|
|
101
|
+
elements = []
|
|
102
|
+
if isinstance(filters, str):
|
|
103
|
+
filters = [filters]
|
|
104
|
+
for content, conditions in self.values():
|
|
105
|
+
if not filters or not conditions or any(filter in conditions for filter in filters):
|
|
106
|
+
if not source:
|
|
107
|
+
elements.append(content)
|
|
108
|
+
elif isinstance(content, _MDCode):
|
|
109
|
+
elements.append(content.source(target=target, filters=filters))
|
|
110
|
+
else:
|
|
111
|
+
elements.append(str(content))
|
|
112
|
+
return elements
|
|
113
|
+
|
|
114
|
+
def get(self, key: str | int, default=None):
|
|
115
|
+
return self._data.get(key, default)
|
|
116
|
+
|
|
117
|
+
def keys(self):
|
|
118
|
+
return self._data.keys()
|
|
119
|
+
|
|
120
|
+
def values(self):
|
|
121
|
+
return self._data.values()
|
|
122
|
+
|
|
123
|
+
def items(self):
|
|
124
|
+
return self._data.items()
|
|
125
|
+
|
|
126
|
+
def __getitem__(self, item):
|
|
127
|
+
return self._data[item]
|
|
128
|
+
|
|
129
|
+
def __setitem__(self, key, value):
|
|
130
|
+
self._data[key] = value
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
def __contains__(self, item):
|
|
134
|
+
return item in self._data
|
|
135
|
+
|
|
136
|
+
def __bool__(self):
|
|
137
|
+
return bool(self._data)
|
|
138
|
+
|
|
139
|
+
def __len__(self):
|
|
140
|
+
return len(self._data)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class MDContainer(Container, _Renderable):
|
|
144
|
+
# Multiple inheritance: https://stackoverflow.com/questions/9575409/calling-parent-class-init-with-multiple-inheritance-whats-the-right-way
|
|
145
|
+
def __init__(
|
|
146
|
+
self,
|
|
147
|
+
content: dict[str | int, ContainerContent] | None = None,
|
|
148
|
+
content_separator: str = "\n\n",
|
|
149
|
+
html_container: Stringable | None = None,
|
|
150
|
+
html_container_attrs: dict | None = None,
|
|
151
|
+
html_container_conditions: list[str] | None = None,
|
|
152
|
+
target_configs: TargetConfigs = None,
|
|
153
|
+
target_default: str = "sphinx"
|
|
154
|
+
):
|
|
155
|
+
super().__init__(
|
|
156
|
+
data=content,
|
|
157
|
+
target_configs=target_configs,
|
|
158
|
+
target_default=target_default
|
|
159
|
+
)
|
|
160
|
+
self.content_separator = content_separator
|
|
161
|
+
self.html_container = html_container
|
|
162
|
+
self.html_container_attrs = html_container_attrs or {}
|
|
163
|
+
self.html_container_conditions = html_container_conditions or []
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def code_fence_count(self) -> int:
|
|
168
|
+
return max(
|
|
169
|
+
(
|
|
170
|
+
content.code_fence_count if isinstance(content, _MDCode)
|
|
171
|
+
else self._count_code_fence(str(content))
|
|
172
|
+
for content, _ in self._data.values()
|
|
173
|
+
),
|
|
174
|
+
default=0,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def _source_rich(self, target: RichTargetConfig, filters: str | list[str] | None = None) -> RenderableType:
|
|
178
|
+
block_container = "\n" in self.content_separator
|
|
179
|
+
elements = self.elements(target=target, filters=filters, source=True)
|
|
180
|
+
if not elements:
|
|
181
|
+
return ""
|
|
182
|
+
if block_container:
|
|
183
|
+
group = [
|
|
184
|
+
rich.markdown.Markdown(element) if isinstance(element, str) else element
|
|
185
|
+
for element in elements
|
|
186
|
+
]
|
|
187
|
+
return rich.console.Group(*group) if len(group) > 1 else group[0]
|
|
188
|
+
text = rich.text.Text()
|
|
189
|
+
for element in elements[:-1]:
|
|
190
|
+
text.append(element)
|
|
191
|
+
text.append(self.content_separator)
|
|
192
|
+
text.append(elements[-1])
|
|
193
|
+
return text
|
|
194
|
+
|
|
195
|
+
def _source_md(self, target: MDTargetConfig, filters: str | list[str] | None = None) -> str:
|
|
196
|
+
elements = self.elements(target=target, filters=filters, source=True)
|
|
197
|
+
elements_str = self.content_separator.join(elements)
|
|
198
|
+
if self.html_container and self.html_container_attrs and (
|
|
199
|
+
not filters
|
|
200
|
+
or not self.html_container_conditions
|
|
201
|
+
or any(filter in self.html_container_conditions for filter in filters)
|
|
202
|
+
):
|
|
203
|
+
container_func = getattr(_htmp.element, str(self.html_container))
|
|
204
|
+
return container_func(_htmp.elementor.markdown(elements_str), self.html_container_attrs).source(
|
|
205
|
+
indent=-1)
|
|
206
|
+
return elements_str
|
|
207
|
+
|
|
208
|
+
def __str__(self) -> str:
|
|
209
|
+
return self.source()
|
|
@@ -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,50 @@
|
|
|
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
|
+
_REGISTRY = None
|
|
14
|
+
_SCHEMATA = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def make_registry(
|
|
18
|
+
dynamic: bool = False,
|
|
19
|
+
crawl: bool = True,
|
|
20
|
+
add_resources: list[dict | _referencing.Resource | tuple[str, dict | _referencing.Resource]] | None = None,
|
|
21
|
+
add_resources_default_spec: _referencing.Specification = _ref_jsonschema.DRAFT202012,
|
|
22
|
+
retrieval_func: _Callable[[str], str | _referencing.Resource] = None,
|
|
23
|
+
) -> tuple[_referencing.Registry, dict[str, dict]]:
|
|
24
|
+
schemata = _file.schema(relative_uri=False)
|
|
25
|
+
resources = add_resources or []
|
|
26
|
+
for schema in schemata.values():
|
|
27
|
+
_jsonschemata.edit.required_last(schema)
|
|
28
|
+
resources.append(schema)
|
|
29
|
+
registry = _jsonschemata.registry.make(
|
|
30
|
+
dynamic=dynamic,
|
|
31
|
+
crawl=crawl,
|
|
32
|
+
add_resources=resources,
|
|
33
|
+
add_resources_default_spec=add_resources_default_spec,
|
|
34
|
+
retrieval_func=retrieval_func,
|
|
35
|
+
)
|
|
36
|
+
return registry, schemata
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def validate(data: dict, schema_id: str):
|
|
40
|
+
global _REGISTRY, _SCHEMATA
|
|
41
|
+
if not _REGISTRY:
|
|
42
|
+
_REGISTRY, _SCHEMATA = make_registry()
|
|
43
|
+
_ps.validate.jsonschema(
|
|
44
|
+
data=data,
|
|
45
|
+
schema=_SCHEMATA[schema_id],
|
|
46
|
+
registry=_REGISTRY,
|
|
47
|
+
fill_defaults=True,
|
|
48
|
+
)
|
|
49
|
+
return
|
|
50
|
+
|