wenmode 0.1.0__py3-none-any.whl
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.
- wenmode/__init__.py +17 -0
- wenmode/directives/__init__.py +9 -0
- wenmode/directives/abbr.py +27 -0
- wenmode/directives/admonition.py +36 -0
- wenmode/directives/details.py +32 -0
- wenmode/directives/figure.py +34 -0
- wenmode/directives/toc.py +60 -0
- wenmode/directives/util.py +15 -0
- wenmode/headings.py +110 -0
- wenmode/nodes.py +358 -0
- wenmode/parser.py +440 -0
- wenmode/presets.py +101 -0
- wenmode/py.typed +1 -0
- wenmode/renderers/__init__.py +18 -0
- wenmode/renderers/base.py +103 -0
- wenmode/renderers/html.py +532 -0
- wenmode/renderers/markdown.py +421 -0
- wenmode/renderers/rst.py +469 -0
- wenmode/rules/__init__.py +79 -0
- wenmode/rules/base.py +106 -0
- wenmode/rules/blocks/__init__.py +30 -0
- wenmode/rules/blocks/abbr.py +142 -0
- wenmode/rules/blocks/blockquote.py +87 -0
- wenmode/rules/blocks/definition_list.py +115 -0
- wenmode/rules/blocks/directive.py +153 -0
- wenmode/rules/blocks/fenced_code.py +73 -0
- wenmode/rules/blocks/heading.py +115 -0
- wenmode/rules/blocks/html.py +164 -0
- wenmode/rules/blocks/indented_code.py +63 -0
- wenmode/rules/blocks/list.py +258 -0
- wenmode/rules/blocks/math.py +49 -0
- wenmode/rules/blocks/spoiler.py +48 -0
- wenmode/rules/blocks/table.py +142 -0
- wenmode/rules/blocks/thematic_break.py +34 -0
- wenmode/rules/blocks/util.py +38 -0
- wenmode/rules/directives.py +136 -0
- wenmode/rules/footnotes.py +188 -0
- wenmode/rules/inlines/__init__.py +30 -0
- wenmode/rules/inlines/code.py +45 -0
- wenmode/rules/inlines/directive.py +92 -0
- wenmode/rules/inlines/emphasis.py +268 -0
- wenmode/rules/inlines/extended_autolink.py +195 -0
- wenmode/rules/inlines/formatting.py +139 -0
- wenmode/rules/inlines/html.py +95 -0
- wenmode/rules/inlines/link.py +350 -0
- wenmode/rules/inlines/math.py +83 -0
- wenmode/rules/inlines/ruby.py +82 -0
- wenmode/rules/inlines/spoiler.py +32 -0
- wenmode/rules/inlines/strikethrough.py +57 -0
- wenmode/rules/inlines/text.py +106 -0
- wenmode/rules/references.py +209 -0
- wenmode/rules/transforms.py +31 -0
- wenmode/state.py +193 -0
- wenmode/toc.py +72 -0
- wenmode/utils.py +80 -0
- wenmode/wenmode.py +102 -0
- wenmode-0.1.0.dist-info/METADATA +269 -0
- wenmode-0.1.0.dist-info/RECORD +60 -0
- wenmode-0.1.0.dist-info/WHEEL +4 -0
- wenmode-0.1.0.dist-info/licenses/LICENSE +28 -0
wenmode/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .parser import Parser, StreamingUnsupportedError
|
|
2
|
+
from .renderers import HTMLRenderer, MarkdownRenderer, RSTRenderer
|
|
3
|
+
from .wenmode import Wenmode
|
|
4
|
+
|
|
5
|
+
__version__ = '0.1.0'
|
|
6
|
+
__homepage__ = 'https://wenmode.lepture.com/'
|
|
7
|
+
__author__ = 'Hsiaoming Yang <me@lepture.com>'
|
|
8
|
+
__license__ = 'BSD-3-Clause'
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
'HTMLRenderer',
|
|
12
|
+
'MarkdownRenderer',
|
|
13
|
+
'RSTRenderer',
|
|
14
|
+
'Parser',
|
|
15
|
+
'StreamingUnsupportedError',
|
|
16
|
+
'Wenmode',
|
|
17
|
+
]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .abbr import Abbreviation
|
|
4
|
+
from .admonition import Admonition
|
|
5
|
+
from .details import Details
|
|
6
|
+
from .figure import Figure
|
|
7
|
+
from .toc import TableOfContents
|
|
8
|
+
|
|
9
|
+
__all__ = ['Abbreviation', 'Admonition', 'Details', 'Figure', 'TableOfContents']
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from wenmode.nodes import TextDirective
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from wenmode.renderers.html import HTMLRenderContext, HTMLRenderer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Abbreviation:
|
|
13
|
+
"""Render text directives as HTML ``abbr`` elements.
|
|
14
|
+
|
|
15
|
+
:param names: Directive names handled by this renderer.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
node_type = 'textDirective'
|
|
19
|
+
|
|
20
|
+
def __init__(self, names: Iterable[str] = ('abbr',)) -> None:
|
|
21
|
+
self.names = frozenset(names)
|
|
22
|
+
|
|
23
|
+
def render(self, renderer: HTMLRenderer, node: TextDirective, context: HTMLRenderContext) -> str:
|
|
24
|
+
attributes = dict(node.attributes or {})
|
|
25
|
+
if 'title' not in attributes:
|
|
26
|
+
return renderer.render_children(node.children, context)
|
|
27
|
+
return f'<abbr{renderer.render_attrs(attributes)}>{renderer.render_children(node.children, context)}</abbr>'
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from wenmode.nodes import ContainerDirective
|
|
7
|
+
|
|
8
|
+
from .util import append_class, split_directive_label
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from wenmode.renderers.html import HTMLRenderContext, HTMLRenderer
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Admonition:
|
|
15
|
+
"""Render container directives as admonition ``aside`` elements.
|
|
16
|
+
|
|
17
|
+
:param names: Directive names handled by this renderer.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
node_type = 'containerDirective'
|
|
21
|
+
|
|
22
|
+
def __init__(self, names: Iterable[str] = ('note', 'tip', 'caution', 'danger')) -> None:
|
|
23
|
+
self.names = frozenset(names)
|
|
24
|
+
|
|
25
|
+
def render(self, renderer: HTMLRenderer, node: ContainerDirective, context: HTMLRenderContext) -> str:
|
|
26
|
+
label, children = split_directive_label(node)
|
|
27
|
+
class_name = f'admonition admonition-{node.name}'
|
|
28
|
+
attrs = dict(node.attributes or {})
|
|
29
|
+
attrs['class'] = append_class(attrs.get('class'), class_name)
|
|
30
|
+
|
|
31
|
+
parts = [f'<aside{renderer.render_attrs(attrs)}>\n']
|
|
32
|
+
if label is not None:
|
|
33
|
+
parts.append(f'<p class="admonition-title">{renderer.render_children(label.children, context)}</p>\n')
|
|
34
|
+
parts.append(renderer.render_children(children, context))
|
|
35
|
+
parts.append('</aside>\n')
|
|
36
|
+
return ''.join(parts)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from wenmode.nodes import ContainerDirective
|
|
7
|
+
|
|
8
|
+
from .util import split_directive_label
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from wenmode.renderers.html import HTMLRenderContext, HTMLRenderer
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Details:
|
|
15
|
+
"""Render ``details`` container directives as HTML ``details`` elements.
|
|
16
|
+
|
|
17
|
+
:param names: Directive names handled by this renderer.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
node_type = 'containerDirective'
|
|
21
|
+
|
|
22
|
+
def __init__(self, names: Iterable[str] = ('details',)) -> None:
|
|
23
|
+
self.names = frozenset(names)
|
|
24
|
+
|
|
25
|
+
def render(self, renderer: HTMLRenderer, node: ContainerDirective, context: HTMLRenderContext) -> str:
|
|
26
|
+
label, children = split_directive_label(node)
|
|
27
|
+
parts = [f'<details{renderer.render_attrs(node.attributes or {})}>\n']
|
|
28
|
+
if label is not None:
|
|
29
|
+
parts.append(f'<summary>{renderer.render_children(label.children, context)}</summary>\n')
|
|
30
|
+
parts.append(renderer.render_children(children, context))
|
|
31
|
+
parts.append('</details>\n')
|
|
32
|
+
return ''.join(parts)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from wenmode.nodes import ContainerDirective
|
|
7
|
+
|
|
8
|
+
from .util import split_directive_label
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from wenmode.renderers.html import HTMLRenderContext, HTMLRenderer
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Figure:
|
|
15
|
+
"""Render ``figure`` container directives as HTML ``figure`` elements.
|
|
16
|
+
|
|
17
|
+
:param names: Directive names handled by this renderer.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
node_type = 'containerDirective'
|
|
21
|
+
|
|
22
|
+
def __init__(self, names: Iterable[str] = ('figure',)) -> None:
|
|
23
|
+
self.names = frozenset(names)
|
|
24
|
+
|
|
25
|
+
def render(self, renderer: HTMLRenderer, node: ContainerDirective, context: HTMLRenderContext) -> str:
|
|
26
|
+
label, children = split_directive_label(node)
|
|
27
|
+
parts = [
|
|
28
|
+
f'<figure{renderer.render_attrs(node.attributes or {})}>\n',
|
|
29
|
+
renderer.render_children(children, context),
|
|
30
|
+
]
|
|
31
|
+
if label is not None:
|
|
32
|
+
parts.append(f'<figcaption>{renderer.render_children(label.children, context)}</figcaption>\n')
|
|
33
|
+
parts.append('</figure>\n')
|
|
34
|
+
return ''.join(parts)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from wenmode.headings import plain_text
|
|
7
|
+
from wenmode.nodes import LeafDirective
|
|
8
|
+
from wenmode.toc import collect_toc, render_toc_list
|
|
9
|
+
|
|
10
|
+
from .util import append_class
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from wenmode.renderers.html import HTMLRenderContext, HTMLRenderer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TableOfContents:
|
|
17
|
+
"""Render ``toc`` leaf directives as HTML tables of contents.
|
|
18
|
+
|
|
19
|
+
:param names: Directive names handled by this renderer.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
node_type = 'leafDirective'
|
|
23
|
+
|
|
24
|
+
def __init__(self, names: Iterable[str] = ('toc',)) -> None:
|
|
25
|
+
self.names = frozenset(names)
|
|
26
|
+
|
|
27
|
+
def render(self, renderer: HTMLRenderer, node: LeafDirective, context: HTMLRenderContext) -> str:
|
|
28
|
+
if context.root is None:
|
|
29
|
+
return ''
|
|
30
|
+
|
|
31
|
+
attributes = dict(node.attributes or {})
|
|
32
|
+
min_depth = parse_depth(attributes, ('min', 'min-depth', 'min_depth'), 1)
|
|
33
|
+
max_depth = parse_depth(attributes, ('max', 'max-depth', 'max_depth'), 6)
|
|
34
|
+
label_attribute = attributes.pop('label') if 'label' in attributes else None
|
|
35
|
+
label = plain_text(node.children) or label_attribute or 'Table of contents'
|
|
36
|
+
items = collect_toc(context.root, min_depth=min_depth, max_depth=max_depth)
|
|
37
|
+
if not items:
|
|
38
|
+
return ''
|
|
39
|
+
|
|
40
|
+
attributes.pop('min', None)
|
|
41
|
+
attributes.pop('min-depth', None)
|
|
42
|
+
attributes.pop('min_depth', None)
|
|
43
|
+
attributes.pop('max', None)
|
|
44
|
+
attributes.pop('max-depth', None)
|
|
45
|
+
attributes.pop('max_depth', None)
|
|
46
|
+
attributes['aria-label'] = label
|
|
47
|
+
attributes['class'] = append_class(attributes.get('class'), 'toc')
|
|
48
|
+
return f'<nav{renderer.render_attrs(attributes)}>\n{render_toc_list(items)}</nav>\n'
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def parse_depth(attributes: dict[str, str], keys: tuple[str, ...], default: int) -> int:
|
|
52
|
+
for key in keys:
|
|
53
|
+
value = attributes.get(key)
|
|
54
|
+
if value is None:
|
|
55
|
+
continue
|
|
56
|
+
try:
|
|
57
|
+
return max(1, min(6, int(value)))
|
|
58
|
+
except ValueError:
|
|
59
|
+
return default
|
|
60
|
+
return default
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from wenmode.nodes import ContainerDirective, Node, Paragraph
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def split_directive_label(node: ContainerDirective) -> tuple[Paragraph | None, list[Node]]:
|
|
7
|
+
"""Split a container directive into its label paragraph and body nodes."""
|
|
8
|
+
if node.children and isinstance(node.children[0], Paragraph) and node.children[0].data == {'directiveLabel': True}:
|
|
9
|
+
return node.children[0], node.children[1:]
|
|
10
|
+
return None, node.children
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def append_class(current: str | None, value: str) -> str:
|
|
14
|
+
"""Append one CSS class value to an existing class attribute."""
|
|
15
|
+
return f'{current} {value}' if current else value
|
wenmode/headings.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import string
|
|
5
|
+
|
|
6
|
+
from .nodes import Heading, Image, Literal, Node, Parent
|
|
7
|
+
|
|
8
|
+
SLUG_PUNCTUATION = ''.join(char for char in string.punctuation if char not in '-_')
|
|
9
|
+
SLUG_PUNCTUATION_RE = re.compile('[' + re.escape(SLUG_PUNCTUATION) + ']')
|
|
10
|
+
SLUG_SPACE_RE = re.compile(r'\s+')
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Slugger:
|
|
14
|
+
"""Generate unique slug IDs for headings."""
|
|
15
|
+
|
|
16
|
+
name = 'default'
|
|
17
|
+
|
|
18
|
+
def __init__(self) -> None:
|
|
19
|
+
self.seen: dict[str, int] = {}
|
|
20
|
+
|
|
21
|
+
def slug(self, value: str) -> str:
|
|
22
|
+
"""Return a unique slug for a heading title."""
|
|
23
|
+
base = slugify(value)
|
|
24
|
+
index = self.seen.get(base, 0)
|
|
25
|
+
self.seen[base] = index + 1
|
|
26
|
+
if index == 0:
|
|
27
|
+
return base
|
|
28
|
+
return f'{base}-{index}'
|
|
29
|
+
|
|
30
|
+
def use(self, value: str) -> None:
|
|
31
|
+
"""Mark an existing slug as already used."""
|
|
32
|
+
self.seen[value] = self.seen.get(value, 0) + 1
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def add_heading_ids(
|
|
36
|
+
node: Node,
|
|
37
|
+
*,
|
|
38
|
+
slugger: Slugger,
|
|
39
|
+
min_depth: int = 1,
|
|
40
|
+
max_depth: int = 6,
|
|
41
|
+
overwrite: bool = False,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""Add generated IDs to heading nodes in a tree.
|
|
44
|
+
|
|
45
|
+
Existing heading IDs are preserved unless ``overwrite`` is ``True``.
|
|
46
|
+
|
|
47
|
+
:param node: Root or subtree to update.
|
|
48
|
+
:param slugger: Slug generator used to create unique IDs.
|
|
49
|
+
:param min_depth: Minimum heading depth to update.
|
|
50
|
+
:param max_depth: Maximum heading depth to update.
|
|
51
|
+
:param overwrite: Whether to replace existing heading IDs.
|
|
52
|
+
"""
|
|
53
|
+
for heading in iter_headings(node):
|
|
54
|
+
if not (min_depth <= heading.depth <= max_depth):
|
|
55
|
+
continue
|
|
56
|
+
current_id = heading.data.get('id') if heading.data else None
|
|
57
|
+
if isinstance(current_id, str) and not overwrite:
|
|
58
|
+
slugger.use(current_id)
|
|
59
|
+
continue
|
|
60
|
+
if heading.data is None:
|
|
61
|
+
heading.data = {}
|
|
62
|
+
heading.data['id'] = slugger.slug(plain_text(heading.children))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def iter_headings(node: Node) -> list[Heading]:
|
|
66
|
+
"""Return all heading nodes under a node."""
|
|
67
|
+
headings: list[Heading] = []
|
|
68
|
+
collect_headings(node, headings)
|
|
69
|
+
return headings
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def collect_headings(node: Node, headings: list[Heading]) -> None:
|
|
73
|
+
"""Append heading descendants of ``node`` to ``headings``."""
|
|
74
|
+
if isinstance(node, Heading):
|
|
75
|
+
headings.append(node)
|
|
76
|
+
children = getattr(node, 'children', None)
|
|
77
|
+
if isinstance(children, list):
|
|
78
|
+
for child in children:
|
|
79
|
+
collect_headings(child, headings)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def plain_text(nodes: list[Node]) -> str:
|
|
83
|
+
"""Return the plain text content of a node list."""
|
|
84
|
+
return ''.join(plain_text_node(node) for node in nodes)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def plain_text_node(node: Node) -> str:
|
|
88
|
+
"""Return the plain text content of one node."""
|
|
89
|
+
if isinstance(node, Image):
|
|
90
|
+
return node.alt
|
|
91
|
+
if isinstance(node, Literal):
|
|
92
|
+
return node.value
|
|
93
|
+
if isinstance(node, Parent):
|
|
94
|
+
return plain_text(node.children)
|
|
95
|
+
label = getattr(node, 'label', None)
|
|
96
|
+
if isinstance(label, str):
|
|
97
|
+
return label
|
|
98
|
+
identifier = getattr(node, 'identifier', None)
|
|
99
|
+
if isinstance(identifier, str):
|
|
100
|
+
return identifier
|
|
101
|
+
return ''
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def slugify(value: str) -> str:
|
|
105
|
+
"""Convert text into a URL-friendly slug."""
|
|
106
|
+
slug = value.strip().lower()
|
|
107
|
+
slug = SLUG_PUNCTUATION_RE.sub('', slug)
|
|
108
|
+
slug = SLUG_SPACE_RE.sub('-', slug)
|
|
109
|
+
slug = slug.strip('-')
|
|
110
|
+
return slug or 'section'
|
wenmode/nodes.py
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Node:
|
|
9
|
+
"""Base class for all Wenmode AST nodes.
|
|
10
|
+
|
|
11
|
+
:param type: mdast-compatible node type name.
|
|
12
|
+
:param data: Optional extension data used by transforms or renderers.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
type: str
|
|
16
|
+
data: dict[str, Any] | None = None
|
|
17
|
+
|
|
18
|
+
def to_ast(self) -> dict[str, Any]:
|
|
19
|
+
"""Convert this node and its children to plain Python data.
|
|
20
|
+
|
|
21
|
+
:returns: A dictionary made from strings, numbers, lists, and nested
|
|
22
|
+
dictionaries.
|
|
23
|
+
"""
|
|
24
|
+
data: dict[str, Any] = {'type': self.type}
|
|
25
|
+
for key, value in self.__dict__.items():
|
|
26
|
+
if key == 'type' or key.startswith('_') or value is None:
|
|
27
|
+
continue
|
|
28
|
+
if isinstance(value, list):
|
|
29
|
+
data[key] = [item.to_ast() if isinstance(item, Node) else item for item in value]
|
|
30
|
+
elif isinstance(value, Node):
|
|
31
|
+
data[key] = value.to_ast()
|
|
32
|
+
else:
|
|
33
|
+
data[key] = value
|
|
34
|
+
return data
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class Parent(Node):
|
|
39
|
+
"""Base class for nodes that contain child nodes."""
|
|
40
|
+
|
|
41
|
+
children: list[Node] = field(default_factory=list)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class Literal(Node):
|
|
46
|
+
"""Base class for nodes that store literal text."""
|
|
47
|
+
|
|
48
|
+
value: str = ''
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class Root(Parent):
|
|
53
|
+
"""Document root node."""
|
|
54
|
+
|
|
55
|
+
_footnote_definitions: dict[str, FootnoteDefinition] | None = field(default=None, repr=False)
|
|
56
|
+
type: str = 'root'
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def footnote_definitions(self) -> dict[str, FootnoteDefinition] | None:
|
|
60
|
+
"""Collected footnote definitions, if the footnote transform ran."""
|
|
61
|
+
return self._footnote_definitions
|
|
62
|
+
|
|
63
|
+
@footnote_definitions.setter
|
|
64
|
+
def footnote_definitions(self, definitions: dict[str, FootnoteDefinition] | None) -> None:
|
|
65
|
+
self._footnote_definitions = definitions
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class Paragraph(Parent):
|
|
70
|
+
"""Paragraph node."""
|
|
71
|
+
|
|
72
|
+
type: str = 'paragraph'
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class Heading(Parent):
|
|
77
|
+
"""Heading node.
|
|
78
|
+
|
|
79
|
+
:param depth: Heading depth from 1 through 6.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
depth: int = 1
|
|
83
|
+
type: str = 'heading'
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class Blockquote(Parent):
|
|
88
|
+
"""Block quote container node."""
|
|
89
|
+
|
|
90
|
+
type: str = 'blockquote'
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class BlockSpoiler(Parent):
|
|
95
|
+
"""Block spoiler container node."""
|
|
96
|
+
|
|
97
|
+
type: str = 'blockSpoiler'
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class List(Parent):
|
|
102
|
+
"""Ordered or unordered list node."""
|
|
103
|
+
|
|
104
|
+
ordered: bool = False
|
|
105
|
+
start: int | None = None
|
|
106
|
+
spread: bool = False
|
|
107
|
+
type: str = 'list'
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass
|
|
111
|
+
class ListItem(Parent):
|
|
112
|
+
"""List item node."""
|
|
113
|
+
|
|
114
|
+
checked: bool | None = None
|
|
115
|
+
spread: bool = False
|
|
116
|
+
type: str = 'listItem'
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass
|
|
120
|
+
class DefinitionList(Parent):
|
|
121
|
+
"""Definition list node."""
|
|
122
|
+
|
|
123
|
+
type: str = 'definitionList'
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass
|
|
127
|
+
class DefinitionTerm(Parent):
|
|
128
|
+
"""Definition term node."""
|
|
129
|
+
|
|
130
|
+
type: str = 'definitionTerm'
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@dataclass
|
|
134
|
+
class DefinitionDescription(Parent):
|
|
135
|
+
"""Definition description node."""
|
|
136
|
+
|
|
137
|
+
spread: bool = False
|
|
138
|
+
type: str = 'definitionDescription'
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@dataclass
|
|
142
|
+
class Code(Literal):
|
|
143
|
+
"""Fenced or indented code block node."""
|
|
144
|
+
|
|
145
|
+
lang: str | None = None
|
|
146
|
+
meta: str | None = None
|
|
147
|
+
type: str = 'code'
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass
|
|
151
|
+
class Math(Literal):
|
|
152
|
+
"""Display math block node."""
|
|
153
|
+
|
|
154
|
+
type: str = 'math'
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass
|
|
158
|
+
class ThematicBreak(Node):
|
|
159
|
+
"""Thematic break node."""
|
|
160
|
+
|
|
161
|
+
type: str = 'thematicBreak'
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@dataclass
|
|
165
|
+
class Html(Literal):
|
|
166
|
+
"""Raw HTML node."""
|
|
167
|
+
|
|
168
|
+
type: str = 'html'
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@dataclass
|
|
172
|
+
class Text(Literal):
|
|
173
|
+
"""Plain text node."""
|
|
174
|
+
|
|
175
|
+
_parse_emphasis: bool = True
|
|
176
|
+
type: str = 'text'
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@dataclass
|
|
180
|
+
class InlineCode(Literal):
|
|
181
|
+
"""Inline code span node."""
|
|
182
|
+
|
|
183
|
+
type: str = 'inlineCode'
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@dataclass
|
|
187
|
+
class InlineMath(Literal):
|
|
188
|
+
"""Inline math node."""
|
|
189
|
+
|
|
190
|
+
type: str = 'inlineMath'
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@dataclass
|
|
194
|
+
class Strong(Parent):
|
|
195
|
+
"""Strong emphasis node."""
|
|
196
|
+
|
|
197
|
+
type: str = 'strong'
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@dataclass
|
|
201
|
+
class Emphasis(Parent):
|
|
202
|
+
"""Emphasis node."""
|
|
203
|
+
|
|
204
|
+
type: str = 'emphasis'
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@dataclass
|
|
208
|
+
class Delete(Parent):
|
|
209
|
+
"""Deleted text node."""
|
|
210
|
+
|
|
211
|
+
type: str = 'delete'
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@dataclass
|
|
215
|
+
class Mark(Parent):
|
|
216
|
+
"""Highlighted text node."""
|
|
217
|
+
|
|
218
|
+
type: str = 'mark'
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@dataclass
|
|
222
|
+
class Insert(Parent):
|
|
223
|
+
"""Inserted text node."""
|
|
224
|
+
|
|
225
|
+
type: str = 'insert'
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@dataclass
|
|
229
|
+
class Superscript(Parent):
|
|
230
|
+
"""Superscript node."""
|
|
231
|
+
|
|
232
|
+
type: str = 'superscript'
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@dataclass
|
|
236
|
+
class Subscript(Parent):
|
|
237
|
+
"""Subscript node."""
|
|
238
|
+
|
|
239
|
+
type: str = 'subscript'
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@dataclass
|
|
243
|
+
class Ruby(Node):
|
|
244
|
+
"""Ruby annotation node."""
|
|
245
|
+
|
|
246
|
+
segments: list[dict[str, str]] = field(default_factory=list)
|
|
247
|
+
type: str = 'ruby'
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@dataclass
|
|
251
|
+
class InlineSpoiler(Parent):
|
|
252
|
+
"""Inline spoiler node."""
|
|
253
|
+
|
|
254
|
+
type: str = 'inlineSpoiler'
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@dataclass
|
|
258
|
+
class Abbreviation(Parent):
|
|
259
|
+
"""Abbreviation node."""
|
|
260
|
+
|
|
261
|
+
title: str = ''
|
|
262
|
+
type: str = 'abbreviation'
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@dataclass
|
|
266
|
+
class Table(Parent):
|
|
267
|
+
"""Table node."""
|
|
268
|
+
|
|
269
|
+
align: list[str | None] = field(default_factory=list)
|
|
270
|
+
type: str = 'table'
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@dataclass
|
|
274
|
+
class TableRow(Parent):
|
|
275
|
+
"""Table row node."""
|
|
276
|
+
|
|
277
|
+
type: str = 'tableRow'
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@dataclass
|
|
281
|
+
class TableCell(Parent):
|
|
282
|
+
"""Table cell node."""
|
|
283
|
+
|
|
284
|
+
type: str = 'tableCell'
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@dataclass
|
|
288
|
+
class Link(Parent):
|
|
289
|
+
"""Link node."""
|
|
290
|
+
|
|
291
|
+
url: str = ''
|
|
292
|
+
title: str | None = None
|
|
293
|
+
type: str = 'link'
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@dataclass
|
|
297
|
+
class Image(Node):
|
|
298
|
+
"""Image node."""
|
|
299
|
+
|
|
300
|
+
url: str = ''
|
|
301
|
+
alt: str = ''
|
|
302
|
+
title: str | None = None
|
|
303
|
+
type: str = 'image'
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@dataclass
|
|
307
|
+
class Break(Node):
|
|
308
|
+
"""Hard line break node."""
|
|
309
|
+
|
|
310
|
+
type: str = 'break'
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@dataclass
|
|
314
|
+
class FootnoteReference(Node):
|
|
315
|
+
"""Footnote reference node."""
|
|
316
|
+
|
|
317
|
+
identifier: str = ''
|
|
318
|
+
label: str = ''
|
|
319
|
+
type: str = 'footnoteReference'
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@dataclass
|
|
323
|
+
class FootnoteDefinition(Parent):
|
|
324
|
+
"""Footnote definition node."""
|
|
325
|
+
|
|
326
|
+
identifier: str = ''
|
|
327
|
+
label: str = ''
|
|
328
|
+
type: str = 'footnoteDefinition'
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@dataclass
|
|
332
|
+
class TextDirective(Parent):
|
|
333
|
+
"""Inline directive node."""
|
|
334
|
+
|
|
335
|
+
name: str = ''
|
|
336
|
+
attributes: dict[str, str] | None = None
|
|
337
|
+
type: str = 'textDirective'
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
@dataclass
|
|
341
|
+
class LeafDirective(Parent):
|
|
342
|
+
"""Leaf block directive node."""
|
|
343
|
+
|
|
344
|
+
name: str = ''
|
|
345
|
+
attributes: dict[str, str] | None = None
|
|
346
|
+
type: str = 'leafDirective'
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
@dataclass
|
|
350
|
+
class ContainerDirective(Parent):
|
|
351
|
+
"""Container block directive node."""
|
|
352
|
+
|
|
353
|
+
name: str = ''
|
|
354
|
+
attributes: dict[str, str] | None = None
|
|
355
|
+
type: str = 'containerDirective'
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
DirectiveNode = TextDirective | LeafDirective | ContainerDirective
|