djot-py 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. djot_py-0.1.0/PKG-INFO +85 -0
  2. djot_py-0.1.0/README.md +64 -0
  3. djot_py-0.1.0/pyproject.toml +32 -0
  4. djot_py-0.1.0/pyproject.toml.orig +31 -0
  5. djot_py-0.1.0/src/djot/__init__.py +81 -0
  6. djot_py-0.1.0/src/djot/ast/__init__.py +345 -0
  7. djot_py-0.1.0/src/djot/ast/base.py +67 -0
  8. djot_py-0.1.0/src/djot/ast/block.py +90 -0
  9. djot_py-0.1.0/src/djot/ast/doc.py +14 -0
  10. djot_py-0.1.0/src/djot/ast/inline.py +262 -0
  11. djot_py-0.1.0/src/djot/ast/list.py +125 -0
  12. djot_py-0.1.0/src/djot/ast/table.py +45 -0
  13. djot_py-0.1.0/src/djot/attributes.py +494 -0
  14. djot_py-0.1.0/src/djot/block/__init__.py +1 -0
  15. djot_py-0.1.0/src/djot/block/attribute.py +92 -0
  16. djot_py-0.1.0/src/djot/block/blockquote.py +86 -0
  17. djot_py-0.1.0/src/djot/block/caption.py +90 -0
  18. djot_py-0.1.0/src/djot/block/code_block.py +178 -0
  19. djot_py-0.1.0/src/djot/block/container.py +246 -0
  20. djot_py-0.1.0/src/djot/block/fenced.py +207 -0
  21. djot_py-0.1.0/src/djot/block/footnote.py +122 -0
  22. djot_py-0.1.0/src/djot/block/heading.py +140 -0
  23. djot_py-0.1.0/src/djot/block/list.py +294 -0
  24. djot_py-0.1.0/src/djot/block/para.py +84 -0
  25. djot_py-0.1.0/src/djot/block/parser.py +455 -0
  26. djot_py-0.1.0/src/djot/block/ref_def.py +148 -0
  27. djot_py-0.1.0/src/djot/block/state.py +154 -0
  28. djot_py-0.1.0/src/djot/block/table.py +415 -0
  29. djot_py-0.1.0/src/djot/block/thematic.py +79 -0
  30. djot_py-0.1.0/src/djot/common/__init__.py +23 -0
  31. djot_py-0.1.0/src/djot/common/align.py +8 -0
  32. djot_py-0.1.0/src/djot/common/list_style.py +295 -0
  33. djot_py-0.1.0/src/djot/common/pos.py +143 -0
  34. djot_py-0.1.0/src/djot/common/range.py +10 -0
  35. djot_py-0.1.0/src/djot/common/string.py +90 -0
  36. djot_py-0.1.0/src/djot/event/__init__.py +16 -0
  37. djot_py-0.1.0/src/djot/event/event.py +227 -0
  38. djot_py-0.1.0/src/djot/event/kind.py +107 -0
  39. djot_py-0.1.0/src/djot/experimental/lexer.py +107 -0
  40. djot_py-0.1.0/src/djot/inline/__init__.py +1 -0
  41. djot_py-0.1.0/src/djot/inline/ascii.py +55 -0
  42. djot_py-0.1.0/src/djot/inline/backslash.py +95 -0
  43. djot_py-0.1.0/src/djot/inline/backtick.py +69 -0
  44. djot_py-0.1.0/src/djot/inline/between.py +346 -0
  45. djot_py-0.1.0/src/djot/inline/brace_left.py +46 -0
  46. djot_py-0.1.0/src/djot/inline/bracket_left.py +39 -0
  47. djot_py-0.1.0/src/djot/inline/bracket_right.py +257 -0
  48. djot_py-0.1.0/src/djot/inline/colon.py +43 -0
  49. djot_py-0.1.0/src/djot/inline/hyphen.py +99 -0
  50. djot_py-0.1.0/src/djot/inline/lessthan.py +89 -0
  51. djot_py-0.1.0/src/djot/inline/matcher.py +10 -0
  52. djot_py-0.1.0/src/djot/inline/paren_left.py +24 -0
  53. djot_py-0.1.0/src/djot/inline/paren_right.py +118 -0
  54. djot_py-0.1.0/src/djot/inline/parser.py +325 -0
  55. djot_py-0.1.0/src/djot/inline/period.py +30 -0
  56. djot_py-0.1.0/src/djot/inline/state.py +485 -0
  57. djot_py-0.1.0/src/djot/input.py +543 -0
  58. djot_py-0.1.0/src/djot/logger.py +5 -0
  59. djot_py-0.1.0/src/djot/parser/__init__.py +8 -0
  60. djot_py-0.1.0/src/djot/parser/attr.py +103 -0
  61. djot_py-0.1.0/src/djot/parser/builder.py +168 -0
  62. djot_py-0.1.0/src/djot/parser/frame.py +327 -0
  63. djot_py-0.1.0/src/djot/parser/handle_attr.py +129 -0
  64. djot_py-0.1.0/src/djot/parser/handle_blockquote.py +20 -0
  65. djot_py-0.1.0/src/djot/parser/handle_default.py +209 -0
  66. djot_py-0.1.0/src/djot/parser/handle_div.py +23 -0
  67. djot_py-0.1.0/src/djot/parser/handle_footnote.py +63 -0
  68. djot_py-0.1.0/src/djot/parser/handle_heading.py +27 -0
  69. djot_py-0.1.0/src/djot/parser/handle_link.py +264 -0
  70. djot_py-0.1.0/src/djot/parser/handle_list.py +329 -0
  71. djot_py-0.1.0/src/djot/parser/handle_para.py +19 -0
  72. djot_py-0.1.0/src/djot/parser/handle_table.py +147 -0
  73. djot_py-0.1.0/src/djot/parser/handle_thematic.py +21 -0
  74. djot_py-0.1.0/src/djot/parser/handle_verb.py +161 -0
  75. djot_py-0.1.0/src/djot/parser/handler.py +64 -0
  76. djot_py-0.1.0/src/djot/parser/id.py +47 -0
  77. djot_py-0.1.0/src/djot/parser/normalize.py +35 -0
  78. djot_py-0.1.0/src/djot/parser/parse.py +93 -0
  79. djot_py-0.1.0/src/djot/parser/roman.py +53 -0
  80. djot_py-0.1.0/src/djot/parser/state.py +123 -0
  81. djot_py-0.1.0/src/djot/py.typed +0 -0
djot_py-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.4
2
+ Name: djot-py
3
+ Version: 0.1.0
4
+ Summary: A pure Python implementation of the Jot markup language
5
+ Author: Victor Nee
6
+ Author-email: Victor Nee <neefrankie@gmail.com>
7
+ License-Expression: MIT
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
11
+ Classifier: Topic :: Text Processing :: Markup
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.12
20
+ Description-Content-Type: text/markdown
21
+
22
+ # djot-py
23
+
24
+ A Python library for parsing the light markup format [djot](https://github.com/jgm/djot).
25
+
26
+ This implementation is based on and modified from [djot.js](https://github.com/jgm/djot.js).
27
+
28
+ ## Usage
29
+
30
+ ```bash
31
+ pip install djot-py
32
+ ```
33
+
34
+ ## API
35
+
36
+ To parse text into an AST, call `parse`:
37
+
38
+ ```python
39
+ from djot import parse
40
+
41
+ ast = parse("hi there\nfriend\n\nnew para")
42
+ ```
43
+
44
+ To parse text into events:
45
+
46
+ ```python
47
+ from djot import EventParser
48
+
49
+ text = "hello *world*\n\nfoo"
50
+
51
+ parser = EventParser(text)
52
+
53
+ # The parse method returns an iterator of events.
54
+ for event in parser.parse():
55
+ print(event)
56
+ ```
57
+
58
+ There are no rendering features available currently. Will be added in the future.
59
+
60
+ ## Differences from Djot.js
61
+
62
+ - Comment is not supported yet.
63
+ - Multi-line attributes is not supported yet.
64
+
65
+ ## My personal view on the above not-yet-implemented features
66
+
67
+ ### Comment
68
+
69
+ Comment should not be mixed with attributes.
70
+
71
+ I hope there's a specific comment syntax in the future. For example, there could be two types of comments:
72
+
73
+ - Line comment appeared at the end of each line, using symbols like `%%`
74
+ - Block comment wrapped in `%%%`, similar to code block.
75
+
76
+ ### Multi-line attributes
77
+
78
+ Djot.js current implementation to support multi-line attributes involves a lot of backtracing,
79
+ which is in direct conflict with the philosophy of djot. To support multi-line attributes,
80
+ there needs a fundamental change in the line-based parsing algorithm.
81
+
82
+ ### List
83
+
84
+ This is not hard to implement. However, too much choices might introduce future ambiguity.
85
+ I prefere simplicity rather than feature-rich. For example, Typst style simply uses `-` and `+`. Leave styling to a renderer. And block attributes could be used to convey some basic styling information.
@@ -0,0 +1,64 @@
1
+ # djot-py
2
+
3
+ A Python library for parsing the light markup format [djot](https://github.com/jgm/djot).
4
+
5
+ This implementation is based on and modified from [djot.js](https://github.com/jgm/djot.js).
6
+
7
+ ## Usage
8
+
9
+ ```bash
10
+ pip install djot-py
11
+ ```
12
+
13
+ ## API
14
+
15
+ To parse text into an AST, call `parse`:
16
+
17
+ ```python
18
+ from djot import parse
19
+
20
+ ast = parse("hi there\nfriend\n\nnew para")
21
+ ```
22
+
23
+ To parse text into events:
24
+
25
+ ```python
26
+ from djot import EventParser
27
+
28
+ text = "hello *world*\n\nfoo"
29
+
30
+ parser = EventParser(text)
31
+
32
+ # The parse method returns an iterator of events.
33
+ for event in parser.parse():
34
+ print(event)
35
+ ```
36
+
37
+ There are no rendering features available currently. Will be added in the future.
38
+
39
+ ## Differences from Djot.js
40
+
41
+ - Comment is not supported yet.
42
+ - Multi-line attributes is not supported yet.
43
+
44
+ ## My personal view on the above not-yet-implemented features
45
+
46
+ ### Comment
47
+
48
+ Comment should not be mixed with attributes.
49
+
50
+ I hope there's a specific comment syntax in the future. For example, there could be two types of comments:
51
+
52
+ - Line comment appeared at the end of each line, using symbols like `%%`
53
+ - Block comment wrapped in `%%%`, similar to code block.
54
+
55
+ ### Multi-line attributes
56
+
57
+ Djot.js current implementation to support multi-line attributes involves a lot of backtracing,
58
+ which is in direct conflict with the philosophy of djot. To support multi-line attributes,
59
+ there needs a fundamental change in the line-based parsing algorithm.
60
+
61
+ ### List
62
+
63
+ This is not hard to implement. However, too much choices might introduce future ambiguity.
64
+ I prefere simplicity rather than feature-rich. For example, Typst style simply uses `-` and `+`. Leave styling to a renderer. And block attributes could be used to convey some basic styling information.
@@ -0,0 +1,32 @@
1
+ [project]
2
+ name = "djot-py"
3
+ version = "0.1.0"
4
+ description = "A pure Python implementation of the Jot markup language"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ classifiers = [
8
+ "Development Status :: 3 - Alpha",
9
+ "Intended Audience :: Developers",
10
+ "Topic :: Software Development :: Libraries :: Python Modules",
11
+ "Topic :: Text Processing :: Markup",
12
+ "Operating System :: OS Independent",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3 :: Only",
15
+ "Programming Language :: Python :: 3.10",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Typing :: Typed",
19
+ ]
20
+ license = "MIT"
21
+ dependencies = []
22
+
23
+ [[project.authors]]
24
+ name = "Victor Nee"
25
+ email = "neefrankie@gmail.com"
26
+
27
+ [build-system]
28
+ requires = ["uv_build>=0.12.6,<0.13.0"]
29
+ build-backend = "uv_build"
30
+
31
+ [tool.uv.build-backend]
32
+ module-name = "djot"
@@ -0,0 +1,31 @@
1
+ [project]
2
+ name = "djot-py"
3
+ version = "0.1.0"
4
+ description = "A pure Python implementation of the Jot markup language"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Victor Nee", email = "neefrankie@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.12"
10
+ classifiers = [
11
+ "Development Status :: 3 - Alpha",
12
+ "Intended Audience :: Developers",
13
+ "Topic :: Software Development :: Libraries :: Python Modules",
14
+ "Topic :: Text Processing :: Markup",
15
+ "Operating System :: OS Independent",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3 :: Only",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Typing :: Typed",
22
+ ]
23
+ license = "MIT"
24
+ dependencies = []
25
+
26
+ [build-system]
27
+ requires = ["uv_build>=0.12.6,<0.13.0"]
28
+ build-backend = "uv_build"
29
+
30
+ [tool.uv.build-backend]
31
+ module-name = "djot"
@@ -0,0 +1,81 @@
1
+ from .parser import (
2
+ parse,
3
+ ASTBuilder,
4
+ render_ast,
5
+ )
6
+ from .block import (
7
+ EventParser,
8
+ )
9
+ from .event import (
10
+ Event,
11
+ )
12
+
13
+ from .common import (
14
+ Alignment,
15
+ unescape_djot,
16
+ Pos,
17
+ SourceLoc,
18
+ )
19
+
20
+ from .ast import (
21
+ AstNode,
22
+ Attributes,
23
+ BlockNode,
24
+ BlockQuote,
25
+ BulletList,
26
+ BulletListStyle,
27
+ Caption,
28
+ Cell,
29
+ CheckboxStatus,
30
+ CodeBlock,
31
+ Definition,
32
+ DefinitionList,
33
+ DefinitionListItem,
34
+ Delete,
35
+ DisplayMath,
36
+ Div,
37
+ Doc,
38
+ DoubleQuoted,
39
+ Email,
40
+ Emph,
41
+ Footnote,
42
+ FootnoteReference,
43
+ HardBreak,
44
+ HasChildren,
45
+ HasText,
46
+ Heading,
47
+ Image,
48
+ InlineNode,
49
+ InlineMath,
50
+ Insert,
51
+ Link,
52
+ ListItem,
53
+ Mark,
54
+ NonBreakingSpace,
55
+ OrderedList,
56
+ OrderedListStyle,
57
+ Para,
58
+ RawBlock,
59
+ RawInline,
60
+ Reference,
61
+ Row,
62
+ Section,
63
+ SingleQuoted,
64
+ SmartPunctuation,
65
+ SmartPunctType,
66
+ SoftBreak,
67
+ Span,
68
+ Str,
69
+ Strong,
70
+ Subscript,
71
+ Superscript,
72
+ Symb,
73
+ Table,
74
+ TaskList,
75
+ TaskListItem,
76
+ Term,
77
+ ThematicBreak,
78
+ Url,
79
+ Verbatim,
80
+ Visitor,
81
+ )
@@ -0,0 +1,345 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import TypeGuard, TypeVar
3
+
4
+ from .base import (
5
+ AstNode,
6
+ Attributes,
7
+ merge_attributes,
8
+ BlockNode,
9
+ InlineNode,
10
+ HasChildren,
11
+ HasText,
12
+ ContentBlock,
13
+ DefinitionBlock,
14
+ )
15
+ from .doc import Doc
16
+ from .block import (
17
+ Para,
18
+ Heading,
19
+ ThematicBreak,
20
+ Section,
21
+ Div,
22
+ CodeBlock,
23
+ RawBlock,
24
+ BlockQuote,
25
+ Footnote,
26
+ Reference,
27
+ )
28
+ from .list import (
29
+ OrderedList,
30
+ BulletList,
31
+ TaskList,
32
+ DefinitionList,
33
+ ListItem,
34
+ TaskListItem,
35
+ DefinitionListItem,
36
+ Term,
37
+ Definition,
38
+ BulletListStyle,
39
+ OrderedListStyle,
40
+ CheckboxStatus,
41
+ )
42
+ from .table import (
43
+ Table,
44
+ Caption,
45
+ Row,
46
+ Cell,
47
+ )
48
+ from .inline import (
49
+ Str,
50
+ SoftBreak,
51
+ HardBreak,
52
+ NonBreakingSpace,
53
+ Symb,
54
+ Verbatim,
55
+ RawInline,
56
+ InlineMath,
57
+ DisplayMath,
58
+ Url,
59
+ Email,
60
+ FootnoteReference,
61
+ SmartPunctuation,
62
+ SmartPunctType,
63
+ Emph,
64
+ Strong,
65
+ Link,
66
+ Image,
67
+ Span,
68
+ Mark,
69
+ Superscript,
70
+ Subscript,
71
+ Insert,
72
+ Delete,
73
+ DoubleQuoted,
74
+ SingleQuoted,
75
+ )
76
+
77
+
78
+ C = TypeVar('C')
79
+ R = TypeVar('R')
80
+
81
+ class Visitor[C, R](ABC):
82
+ @abstractmethod
83
+ def doc(self, node: Doc, context: C) -> R:
84
+ pass
85
+
86
+ @abstractmethod
87
+ def para(self, node: Para, context: C) -> R:
88
+ pass
89
+
90
+ @abstractmethod
91
+ def heading(self, node: Heading, context: C) -> R:
92
+ pass
93
+
94
+ @abstractmethod
95
+ def thematic_break(self, node: ThematicBreak, context: C) -> R:
96
+ pass
97
+
98
+ @abstractmethod
99
+ def section(self, node: Section, context: C) -> R:
100
+ pass
101
+
102
+ @abstractmethod
103
+ def div(self, node: Div, context: C) -> R:
104
+ pass
105
+
106
+ @abstractmethod
107
+ def code_block(self, node: CodeBlock, context: C) -> R:
108
+ pass
109
+
110
+ @abstractmethod
111
+ def raw_block(self, node: RawBlock, context: C) -> R:
112
+ pass
113
+
114
+ @abstractmethod
115
+ def block_quote(self, node: BlockQuote, context: C) -> R:
116
+ pass
117
+
118
+ @abstractmethod
119
+ def ordered_list(self, node: OrderedList, context: C) -> R:
120
+ pass
121
+
122
+ @abstractmethod
123
+ def bullet_list(self, node: BulletList, context: C) -> R:
124
+ pass
125
+
126
+ @abstractmethod
127
+ def task_list(self, node: TaskList, context: C) -> R:
128
+ pass
129
+
130
+ @abstractmethod
131
+ def definition_list(self, node: DefinitionList, context: C) -> R:
132
+ pass
133
+
134
+ @abstractmethod
135
+ def table(self, node: Table, context: C) -> R:
136
+ pass
137
+
138
+ @abstractmethod
139
+ def str(self, node: Str, context: C) -> R:
140
+ pass
141
+
142
+ @abstractmethod
143
+ def soft_break(self, node: SoftBreak, context: C) -> R:
144
+ pass
145
+
146
+ @abstractmethod
147
+ def hard_break(self, node: HardBreak, context: C) -> R:
148
+ pass
149
+
150
+ @abstractmethod
151
+ def non_breaking_space(self, node: NonBreakingSpace, context: C) -> R:
152
+ pass
153
+
154
+ @abstractmethod
155
+ def symb(self, node: Symb, context: C) -> R:
156
+ pass
157
+
158
+ @abstractmethod
159
+ def verbatim(self, node: Verbatim, context: C) -> R:
160
+ pass
161
+
162
+ @abstractmethod
163
+ def raw_inline(self, node: RawInline, context: C) -> R:
164
+ pass
165
+
166
+ @abstractmethod
167
+ def display_math(self, node: DisplayMath, context: C) -> R:
168
+ pass
169
+
170
+ @abstractmethod
171
+ def url(self, node: Url, context: C) -> R:
172
+ pass
173
+
174
+ @abstractmethod
175
+ def email(self, node: Email, context: C) -> R:
176
+ pass
177
+
178
+ @abstractmethod
179
+ def footnote_reference(self, node: FootnoteReference, context: C) -> R:
180
+ pass
181
+
182
+ @abstractmethod
183
+ def smart_punctuation(self, node: SmartPunctuation, context: C) -> R:
184
+ pass
185
+
186
+ @abstractmethod
187
+ def emph(self, node: Emph, context: C) -> R:
188
+ pass
189
+
190
+ @abstractmethod
191
+ def strong(self, node: Strong, context: C) -> R:
192
+ pass
193
+
194
+ @abstractmethod
195
+ def link(self, node: Link, context: C) -> R:
196
+ pass
197
+
198
+ @abstractmethod
199
+ def image(self, node: Image, context: C) -> R:
200
+ pass
201
+
202
+ @abstractmethod
203
+ def span(self, node: Span, context: C) -> R:
204
+ pass
205
+
206
+ @abstractmethod
207
+ def mark(self, node: Mark, context: C) -> R:
208
+ pass
209
+
210
+ @abstractmethod
211
+ def superscript(self, node: Superscript, context: C) -> R:
212
+ pass
213
+
214
+ @abstractmethod
215
+ def subscript(self, node: Subscript, context: C) -> R:
216
+ pass
217
+
218
+ @abstractmethod
219
+ def insert(self, node: Insert, context: C) -> R:
220
+ pass
221
+
222
+ @abstractmethod
223
+ def delete(self, node: Delete, context: C) -> R:
224
+ pass
225
+
226
+ @abstractmethod
227
+ def double_quoted(self, node: DoubleQuoted, context: C) -> R:
228
+ pass
229
+
230
+ @abstractmethod
231
+ def single_quoted(self, node: SingleQuoted, context: C) -> R:
232
+ pass
233
+
234
+ @abstractmethod
235
+ def list_item(self, node: ListItem, context: C) -> R:
236
+ pass
237
+
238
+ @abstractmethod
239
+ def task_list_item(self, node: TaskListItem, context: C) -> R:
240
+ pass
241
+
242
+ @abstractmethod
243
+ def definition_list_item(self, node: DefinitionListItem, context: C) -> R:
244
+ pass
245
+
246
+ @abstractmethod
247
+ def term(self, node: Term, context: C) -> R:
248
+ pass
249
+
250
+ @abstractmethod
251
+ def definition(self, node: Definition, context: C) -> R:
252
+ pass
253
+
254
+ @abstractmethod
255
+ def row(self, node: Row, context: C) -> R:
256
+ pass
257
+
258
+ @abstractmethod
259
+ def cell(self, node: Cell, context: C) -> R:
260
+ pass
261
+
262
+ @abstractmethod
263
+ def caption(self, node: Caption, context: C) -> R:
264
+ pass
265
+
266
+ @abstractmethod
267
+ def footnote(self, node: Footnote, context: C) -> R:
268
+ pass
269
+
270
+ @abstractmethod
271
+ def reference(self, node: Reference, context: C) -> R:
272
+ pass
273
+
274
+ def is_block(node: AstNode) -> TypeGuard[BlockNode]:
275
+ match node:
276
+ case (
277
+ Para()
278
+ | Heading()
279
+ | BlockQuote()
280
+ | ThematicBreak()
281
+ | Section()
282
+ | Div()
283
+ | CodeBlock()
284
+ | RawBlock()
285
+ | BulletList()
286
+ | OrderedList()
287
+ | TaskList()
288
+ | DefinitionList()
289
+ | Table()
290
+ | Reference()
291
+ | Footnote()
292
+ ):
293
+ return True
294
+
295
+ case _:
296
+ return False
297
+
298
+ def is_inline(node: AstNode) -> bool:
299
+ match node:
300
+ case (
301
+ Str()
302
+ | SoftBreak()
303
+ | HardBreak()
304
+ | NonBreakingSpace()
305
+ | Symb()
306
+ | Verbatim()
307
+ | RawInline()
308
+ | InlineMath()
309
+ | DisplayMath()
310
+ | Url()
311
+ | Email()
312
+ | FootnoteReference()
313
+ | SmartPunctuation()
314
+ | Emph()
315
+ | Strong()
316
+ | Link()
317
+ | Image()
318
+ | Span()
319
+ | Mark()
320
+ | Superscript()
321
+ | Subscript()
322
+ | Insert()
323
+ | Delete()
324
+ | DoubleQuoted()
325
+ | SingleQuoted()
326
+ ):
327
+ return True
328
+
329
+ case _:
330
+ return False
331
+
332
+
333
+ def is_row(node: Row | Caption) -> TypeGuard[Row]:
334
+ match node:
335
+ case Row(head=_):
336
+ return True
337
+ case _:
338
+ return False
339
+
340
+ def is_caption(node: Row | Caption) -> TypeGuard[Caption]:
341
+ match node:
342
+ case Row(head=_):
343
+ return False
344
+ case _:
345
+ return True
@@ -0,0 +1,67 @@
1
+ from abc import ABC, abstractmethod
2
+ import copy
3
+ from dataclasses import dataclass
4
+ from typing import Dict, Generic, List, Optional, TypeVar
5
+
6
+ from ..common import Pos
7
+
8
+ type Attributes = Dict[str, str]
9
+
10
+ def merge_attributes(
11
+ target: Optional[Attributes],
12
+ source: Attributes
13
+ ) -> Attributes:
14
+ if not target:
15
+ return source.copy()
16
+
17
+ result = target.copy()
18
+
19
+ for key, value in source.items():
20
+ if key == 'class' and key in result:
21
+ result[key] = f'{result[key]} {value}'
22
+ else:
23
+ result[key] = value
24
+
25
+ return result
26
+
27
+ @dataclass(kw_only=True)
28
+ class AstNode(ABC):
29
+ attributes: Attributes | None = None
30
+ pos: Pos | None = None
31
+
32
+ @property
33
+ @abstractmethod
34
+ def tag(self) -> str:
35
+ pass
36
+
37
+ def merge_attributes(self, other: Attributes):
38
+ self.attributes = merge_attributes(self.attributes, other)
39
+
40
+ T = TypeVar("T")
41
+
42
+ @dataclass(kw_only=True)
43
+ class HasChildren(Generic[T], ABC):
44
+ children: List[T]
45
+
46
+ @dataclass(kw_only=True)
47
+ class HasText(ABC):
48
+ text: str
49
+
50
+ @dataclass(kw_only=True)
51
+ class InlineNode(AstNode):
52
+ pass
53
+
54
+ @dataclass(kw_only=True)
55
+ class BlockNode(AstNode, ABC):
56
+ pass
57
+
58
+ # 区分常规内容块(Para, Heading 等)与 定义类块(Footnote, Reference)
59
+ @dataclass(kw_only=True)
60
+ class ContentBlock(BlockNode, ABC):
61
+ """能够嵌入普通 Container(如 BlockQuote, Div, Doc)的内容块"""
62
+ pass
63
+
64
+ @dataclass(kw_only=True)
65
+ class DefinitionBlock(BlockNode, ABC):
66
+ """元数据/定义类块(如 Footnote, Reference)"""
67
+ pass