MDit 0.0.0.dev1__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 (31) hide show
  1. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/PKG-INFO +4 -1
  2. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/pyproject.toml +4 -2
  3. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/src/MDit.egg-info/PKG-INFO +4 -1
  4. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/src/MDit.egg-info/SOURCES.txt +5 -2
  5. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/src/MDit.egg-info/requires.txt +3 -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.dev1 → mdit-0.0.0.dev2}/src/mdit/data/schema.py +7 -2
  9. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/src/mdit/display.py +13 -0
  10. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/src/mdit/document.py +60 -69
  11. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/src/mdit/element.py +1405 -477
  12. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/src/mdit/generate.py +8 -6
  13. mdit-0.0.0.dev2/src/mdit/protocol.py +47 -0
  14. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/src/mdit/render.py +33 -45
  15. mdit-0.0.0.dev2/src/mdit/renderable.py +68 -0
  16. mdit-0.0.0.dev2/src/mdit/target/__init__.py +300 -0
  17. mdit-0.0.0.dev2/src/mdit/target/md.py +166 -0
  18. mdit-0.0.0.dev2/src/mdit/target/rich.py +502 -0
  19. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/src/mdit/template.py +1 -1
  20. mdit-0.0.0.dev1/src/mdit/__init__.py +0 -133
  21. mdit-0.0.0.dev1/src/mdit/container.py +0 -195
  22. mdit-0.0.0.dev1/src/mdit/protocol.py +0 -90
  23. mdit-0.0.0.dev1/src/mdit/target.py +0 -202
  24. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/README.md +0 -0
  25. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/setup.cfg +0 -0
  26. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/src/MDit.egg-info/dependency_links.txt +0 -0
  27. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/src/MDit.egg-info/not-zip-safe +0 -0
  28. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/src/MDit.egg-info/top_level.txt +0 -0
  29. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/src/mdit/data/__init__.py +0 -0
  30. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/src/mdit/data/file.py +0 -0
  31. {mdit-0.0.0.dev1 → mdit-0.0.0.dev2}/src/mdit/parse.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: MDit
3
- Version: 0.0.0.dev1
3
+ Version: 0.0.0.dev2
4
4
  Requires-Python: >=3.10
5
5
  Requires-Dist: sphinx<8
6
6
  Requires-Dist: myst-parser<4,>3
@@ -19,6 +19,9 @@ Requires-Dist: PyLinks
19
19
  Requires-Dist: PyBadger
20
20
  Requires-Dist: PyColorIt
21
21
  Requires-Dist: IPython
22
+ Requires-Dist: jupyterlab_myst
22
23
  Requires-Dist: PyProtocol
23
24
  Requires-Dist: HTMP
24
25
  Requires-Dist: ansi-sgr
26
+ Requires-Dist: pygments
27
+ Requires-Dist: rich
@@ -17,7 +17,7 @@ namespaces = true
17
17
  # ----------------------------------------- Project Metadata -------------------------------------
18
18
  #
19
19
  [project]
20
- version = "0.0.0.dev1"
20
+ version = "0.0.0.dev2"
21
21
  name = "MDit"
22
22
  requires-python = ">=3.10"
23
23
  dependencies = [
@@ -38,8 +38,10 @@ dependencies = [
38
38
  "PyBadger",
39
39
  "PyColorIt",
40
40
  "IPython",
41
+ "jupyterlab_myst",
41
42
  "PyProtocol",
42
43
  "HTMP",
43
44
  "ansi-sgr",
44
-
45
+ "pygments",
46
+ "rich",
45
47
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: MDit
3
- Version: 0.0.0.dev1
3
+ Version: 0.0.0.dev2
4
4
  Requires-Python: >=3.10
5
5
  Requires-Dist: sphinx<8
6
6
  Requires-Dist: myst-parser<4,>3
@@ -19,6 +19,9 @@ Requires-Dist: PyLinks
19
19
  Requires-Dist: PyBadger
20
20
  Requires-Dist: PyColorIt
21
21
  Requires-Dist: IPython
22
+ Requires-Dist: jupyterlab_myst
22
23
  Requires-Dist: PyProtocol
23
24
  Requires-Dist: HTMP
24
25
  Requires-Dist: ansi-sgr
26
+ Requires-Dist: pygments
27
+ Requires-Dist: rich
@@ -15,8 +15,11 @@ src/mdit/generate.py
15
15
  src/mdit/parse.py
16
16
  src/mdit/protocol.py
17
17
  src/mdit/render.py
18
- src/mdit/target.py
18
+ src/mdit/renderable.py
19
19
  src/mdit/template.py
20
20
  src/mdit/data/__init__.py
21
21
  src/mdit/data/file.py
22
- src/mdit/data/schema.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
@@ -15,6 +15,9 @@ PyLinks
15
15
  PyBadger
16
16
  PyColorIt
17
17
  IPython
18
+ jupyterlab_myst
18
19
  PyProtocol
19
20
  HTMP
20
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()
@@ -10,6 +10,10 @@ import pyserials as _ps
10
10
  from mdit.data import file as _file
11
11
 
12
12
 
13
+ _REGISTRY = None
14
+ _SCHEMATA = None
15
+
16
+
13
17
  def make_registry(
14
18
  dynamic: bool = False,
15
19
  crawl: bool = True,
@@ -33,6 +37,9 @@ def make_registry(
33
37
 
34
38
 
35
39
  def validate(data: dict, schema_id: str):
40
+ global _REGISTRY, _SCHEMATA
41
+ if not _REGISTRY:
42
+ _REGISTRY, _SCHEMATA = make_registry()
36
43
  _ps.validate.jsonschema(
37
44
  data=data,
38
45
  schema=_SCHEMATA[schema_id],
@@ -41,5 +48,3 @@ def validate(data: dict, schema_id: str):
41
48
  )
42
49
  return
43
50
 
44
-
45
- # _REGISTRY, _SCHEMATA = make_registry()
@@ -1,11 +1,18 @@
1
1
  """Display HTML and Markdown content in web browser or IPython notebook."""
2
+ from __future__ import annotations as _annotations
3
+
4
+ from typing import TYPE_CHECKING as _TYPE_CHECKING
2
5
 
3
6
  import webbrowser as _webbrowser
4
7
  import tempfile as _tempfile
5
8
  import time as _time
6
9
 
10
+ import rich as _rich
7
11
  from IPython import display as _display
8
12
 
13
+ if _TYPE_CHECKING:
14
+ from rich.console import Console
15
+
9
16
 
10
17
  def browser(content: str) -> None:
11
18
  """Display HTML content in a web browser.
@@ -44,3 +51,9 @@ def ipython(content: str, as_md: bool = True) -> None:
44
51
  renderer = _display.Markdown if as_md else _display.HTML
45
52
  _display.display(renderer(content))
46
53
  return
54
+
55
+
56
+ def console(content, output: Console | None = None) -> None:
57
+ writer = output or _rich
58
+ writer.print(content)
59
+ return