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.
Files changed (33) hide show
  1. mdit-0.0.0.dev2/PKG-INFO +27 -0
  2. {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/pyproject.toml +19 -3
  3. mdit-0.0.0.dev2/src/MDit.egg-info/PKG-INFO +27 -0
  4. {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/src/MDit.egg-info/SOURCES.txt +12 -1
  5. mdit-0.0.0.dev2/src/MDit.egg-info/requires.txt +23 -0
  6. mdit-0.0.0.dev2/src/mdit/__init__.py +220 -0
  7. mdit-0.0.0.dev2/src/mdit/container.py +209 -0
  8. mdit-0.0.0.dev2/src/mdit/data/__init__.py +3 -0
  9. mdit-0.0.0.dev2/src/mdit/data/file.py +75 -0
  10. mdit-0.0.0.dev2/src/mdit/data/schema.py +50 -0
  11. {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/src/mdit/display.py +18 -11
  12. mdit-0.0.0.dev2/src/mdit/document.py +300 -0
  13. mdit-0.0.0.dev2/src/mdit/element.py +3718 -0
  14. mdit-0.0.0.dev2/src/mdit/generate.py +151 -0
  15. mdit-0.0.0.dev2/src/mdit/protocol.py +47 -0
  16. {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/src/mdit/render.py +87 -48
  17. mdit-0.0.0.dev2/src/mdit/renderable.py +68 -0
  18. mdit-0.0.0.dev2/src/mdit/target/__init__.py +300 -0
  19. mdit-0.0.0.dev2/src/mdit/target/md.py +166 -0
  20. mdit-0.0.0.dev2/src/mdit/target/rich.py +502 -0
  21. mdit-0.0.0.dev2/src/mdit/template.py +8 -0
  22. mdit-0.0.0.dev0/PKG-INFO +0 -11
  23. mdit-0.0.0.dev0/src/MDit.egg-info/PKG-INFO +0 -11
  24. mdit-0.0.0.dev0/src/MDit.egg-info/requires.txt +0 -7
  25. mdit-0.0.0.dev0/src/mdit/__init__.py +0 -6
  26. mdit-0.0.0.dev0/src/mdit/container.py +0 -71
  27. mdit-0.0.0.dev0/src/mdit/element.py +0 -558
  28. {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/README.md +0 -0
  29. {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/setup.cfg +0 -0
  30. {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/src/MDit.egg-info/dependency_links.txt +0 -0
  31. {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/src/MDit.egg-info/not-zip-safe +0 -0
  32. {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/src/MDit.egg-info/top_level.txt +0 -0
  33. {mdit-0.0.0.dev0 → mdit-0.0.0.dev2}/src/mdit/parse.py +0 -0
@@ -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.dev0"
20
+ version = "0.0.0.dev2"
21
21
  name = "MDit"
22
22
  requires-python = ">=3.10"
23
23
  dependencies = [
24
- "IPython",
25
- "PyProtocol",
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/render.py
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,3 @@
1
+ """Work with the package's data files and JSON schemas."""
2
+
3
+ from mdit.data import file, schema
@@ -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
+