marklas 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.
- marklas-0.1.0/PKG-INFO +150 -0
- marklas-0.1.0/README.md +131 -0
- marklas-0.1.0/pyproject.toml +28 -0
- marklas-0.1.0/src/marklas/__init__.py +3 -0
- marklas-0.1.0/src/marklas/ast/__init__.py +0 -0
- marklas-0.1.0/src/marklas/ast/base.py +6 -0
- marklas-0.1.0/src/marklas/ast/blocks.py +77 -0
- marklas-0.1.0/src/marklas/ast/inlines.py +59 -0
- marklas-0.1.0/src/marklas/parser/__init__.py +0 -0
- marklas-0.1.0/src/marklas/parser/adf.py +381 -0
- marklas-0.1.0/src/marklas/parser/md.py +168 -0
- marklas-0.1.0/src/marklas/py.typed +0 -0
- marklas-0.1.0/src/marklas/renderer/__init__.py +0 -0
- marklas-0.1.0/src/marklas/renderer/adf.py +265 -0
- marklas-0.1.0/src/marklas/renderer/md.py +177 -0
- marklas-0.1.0/src/marklas/schema.py +525 -0
marklas-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: marklas
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Bidirectional converter between GitHub Flavored Markdown and Atlassian Document Format
|
|
5
|
+
Author: byExist
|
|
6
|
+
Author-email: byExist <jongbeom.kwon@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Typing :: Typed
|
|
12
|
+
Requires-Dist: mistune>=3.2
|
|
13
|
+
Requires-Dist: black>=26.1.0 ; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest>=8.0.0 ; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest-cov>=4.1.0 ; extra == 'dev'
|
|
16
|
+
Requires-Python: >=3.13
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# marklas
|
|
21
|
+
|
|
22
|
+
Bidirectional conversion library between GFM (GitHub Flavored Markdown) and ADF (Atlassian Document Format).
|
|
23
|
+
|
|
24
|
+
Converts between Markdown and ADF via an intermediate AST representation.
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
ADF ──parser──▶ AST ──renderer──▶ Markdown
|
|
28
|
+
Markdown ──parser──▶ AST ──renderer──▶ ADF
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install marklas
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### Markdown → ADF
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from marklas.parser.md import parse
|
|
43
|
+
from marklas.renderer.adf import render
|
|
44
|
+
|
|
45
|
+
doc = parse("**Hello** world")
|
|
46
|
+
adf = render(doc)
|
|
47
|
+
# {
|
|
48
|
+
# "type": "doc",
|
|
49
|
+
# "version": 1,
|
|
50
|
+
# "content": [
|
|
51
|
+
# {
|
|
52
|
+
# "type": "paragraph",
|
|
53
|
+
# "content": [
|
|
54
|
+
# {"type": "text", "text": "Hello", "marks": [{"type": "strong"}]},
|
|
55
|
+
# {"type": "text", "text": " world"}
|
|
56
|
+
# ]
|
|
57
|
+
# }
|
|
58
|
+
# ]
|
|
59
|
+
# }
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### ADF → Markdown
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from typing import Any
|
|
66
|
+
|
|
67
|
+
from marklas.parser.adf import parse
|
|
68
|
+
from marklas.renderer.md import render
|
|
69
|
+
|
|
70
|
+
adf: dict[str, Any] = {
|
|
71
|
+
"type": "doc",
|
|
72
|
+
"version": 1,
|
|
73
|
+
"content": [
|
|
74
|
+
{
|
|
75
|
+
"type": "paragraph",
|
|
76
|
+
"content": [
|
|
77
|
+
{"type": "text", "text": "Hello", "marks": [{"type": "strong"}]},
|
|
78
|
+
{"type": "text", "text": " world"},
|
|
79
|
+
],
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
}
|
|
83
|
+
doc = parse(adf)
|
|
84
|
+
md = render(doc)
|
|
85
|
+
# "**Hello** world\n"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Conversion Rules
|
|
89
|
+
|
|
90
|
+
### Block
|
|
91
|
+
|
|
92
|
+
| ADF | AST | Markdown |
|
|
93
|
+
| ------------------------------------------ | ------------------------------------------------ | ----------------------------- |
|
|
94
|
+
| `paragraph` | `Paragraph(children)` | inline content |
|
|
95
|
+
| `heading` (level 1-6) | `Heading(level, children)` | `# ~ ######` |
|
|
96
|
+
| `codeBlock` (language?) | `CodeBlock(code, language?)` | ` ```lang\ncode\n``` ` |
|
|
97
|
+
| `blockquote` | `BlockQuote(children)` | `> text` |
|
|
98
|
+
| `bulletList > listItem` | `BulletList(items) > ListItem(children)` | `- item` |
|
|
99
|
+
| `orderedList > listItem` | `OrderedList(items, start) > ListItem(children)` | `1. item` |
|
|
100
|
+
| `taskList > taskItem` | `BulletList > ListItem(checked=bool)` | `- [x]` / `- [ ]` |
|
|
101
|
+
| `decisionList > decisionItem` | `BulletList > ListItem(checked=bool)` | `- [x]` / `- [ ]` |
|
|
102
|
+
| `rule` | `ThematicBreak` | `---` |
|
|
103
|
+
| `table > tableRow > tableHeader/tableCell` | `Table(head, body, alignments)` | GFM table |
|
|
104
|
+
| `mediaSingle > media` (external) | `Paragraph > Image(url, alt)` | `` |
|
|
105
|
+
| `mediaSingle > media` (non-external) | `Paragraph > Text("[Image: id]")` | `[Image: id]` |
|
|
106
|
+
| `mediaGroup > media` | `Paragraph > Image/Text` | `` / `[Image: id]` |
|
|
107
|
+
| `panel` | `BlockQuote(children)` | `> text` |
|
|
108
|
+
| `expand` / `nestedExpand` (title?) | `BlockQuote(children)` (title prepended) | `> title\n> text` |
|
|
109
|
+
| `layoutSection > layoutColumn` | flattened blocks | columns flattened |
|
|
110
|
+
| `blockCard` (url) | `Paragraph > Link(url)` | `[url](url)` |
|
|
111
|
+
| `embedCard` (url) | `Paragraph > Link(url)` | `[url](url)` |
|
|
112
|
+
|
|
113
|
+
### Inline
|
|
114
|
+
|
|
115
|
+
| ADF | AST | Markdown |
|
|
116
|
+
| ---------------------- | ----------------------------- | ------------------- |
|
|
117
|
+
| `text` | `Text(text)` | plain text |
|
|
118
|
+
| `text` + `strong` mark | `Strong(children)` | `**text**` |
|
|
119
|
+
| `text` + `em` mark | `Emphasis(children)` | `*text*` |
|
|
120
|
+
| `text` + `strike` mark | `Strikethrough(children)` | `~~text~~` |
|
|
121
|
+
| `text` + `code` mark | `CodeSpan(code)` | `` `code` `` |
|
|
122
|
+
| `text` + `link` mark | `Link(url, children, title?)` | `[text](url)` |
|
|
123
|
+
| `hardBreak` | `HardBreak` | `\` + newline |
|
|
124
|
+
| — | `SoftBreak` | newline |
|
|
125
|
+
| `mention` | `CodeSpan(code)` | `` `@user` `` |
|
|
126
|
+
| `emoji` | `Text(text)` | `:shortName:` |
|
|
127
|
+
| `date` | `CodeSpan(code)` | `` `2024-01-01` `` |
|
|
128
|
+
| `status` | `CodeSpan(code)` | `` `status text` `` |
|
|
129
|
+
| `inlineCard` (url) | `Link(url)` | `[url](url)` |
|
|
130
|
+
| — | `Image(url, alt, title?)` | `` |
|
|
131
|
+
|
|
132
|
+
### Not Supported
|
|
133
|
+
|
|
134
|
+
| Element | Behavior |
|
|
135
|
+
| ---------------------------------------------------------------------- | -------------------- |
|
|
136
|
+
| ADF marks: `underline`, `textColor`, `backgroundColor`, `subsup` | silently ignored |
|
|
137
|
+
| ADF blocks: `extension`, `bodiedExtension`, `syncBlock`, `bodiedSyncBlock` | `[type]` placeholder |
|
|
138
|
+
| ADF inlines: `placeholder`, `inlineExtension`, `mediaInline` | `[type]` placeholder |
|
|
139
|
+
| ADF table: `colspan`, `rowspan` | expanded with empty cells |
|
|
140
|
+
| ADF table: `background`, `colwidth` | attributes ignored |
|
|
141
|
+
| ADF table: non-paragraph cell content | `[type]` placeholder |
|
|
142
|
+
| Markdown: raw HTML (block, inline) | silently ignored |
|
|
143
|
+
|
|
144
|
+
## Development
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
uv sync --extra dev
|
|
148
|
+
uv run pytest -v
|
|
149
|
+
uv run black src/ tests/
|
|
150
|
+
```
|
marklas-0.1.0/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# marklas
|
|
2
|
+
|
|
3
|
+
Bidirectional conversion library between GFM (GitHub Flavored Markdown) and ADF (Atlassian Document Format).
|
|
4
|
+
|
|
5
|
+
Converts between Markdown and ADF via an intermediate AST representation.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
ADF ──parser──▶ AST ──renderer──▶ Markdown
|
|
9
|
+
Markdown ──parser──▶ AST ──renderer──▶ ADF
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install marklas
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### Markdown → ADF
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from marklas.parser.md import parse
|
|
24
|
+
from marklas.renderer.adf import render
|
|
25
|
+
|
|
26
|
+
doc = parse("**Hello** world")
|
|
27
|
+
adf = render(doc)
|
|
28
|
+
# {
|
|
29
|
+
# "type": "doc",
|
|
30
|
+
# "version": 1,
|
|
31
|
+
# "content": [
|
|
32
|
+
# {
|
|
33
|
+
# "type": "paragraph",
|
|
34
|
+
# "content": [
|
|
35
|
+
# {"type": "text", "text": "Hello", "marks": [{"type": "strong"}]},
|
|
36
|
+
# {"type": "text", "text": " world"}
|
|
37
|
+
# ]
|
|
38
|
+
# }
|
|
39
|
+
# ]
|
|
40
|
+
# }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### ADF → Markdown
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from typing import Any
|
|
47
|
+
|
|
48
|
+
from marklas.parser.adf import parse
|
|
49
|
+
from marklas.renderer.md import render
|
|
50
|
+
|
|
51
|
+
adf: dict[str, Any] = {
|
|
52
|
+
"type": "doc",
|
|
53
|
+
"version": 1,
|
|
54
|
+
"content": [
|
|
55
|
+
{
|
|
56
|
+
"type": "paragraph",
|
|
57
|
+
"content": [
|
|
58
|
+
{"type": "text", "text": "Hello", "marks": [{"type": "strong"}]},
|
|
59
|
+
{"type": "text", "text": " world"},
|
|
60
|
+
],
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
}
|
|
64
|
+
doc = parse(adf)
|
|
65
|
+
md = render(doc)
|
|
66
|
+
# "**Hello** world\n"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Conversion Rules
|
|
70
|
+
|
|
71
|
+
### Block
|
|
72
|
+
|
|
73
|
+
| ADF | AST | Markdown |
|
|
74
|
+
| ------------------------------------------ | ------------------------------------------------ | ----------------------------- |
|
|
75
|
+
| `paragraph` | `Paragraph(children)` | inline content |
|
|
76
|
+
| `heading` (level 1-6) | `Heading(level, children)` | `# ~ ######` |
|
|
77
|
+
| `codeBlock` (language?) | `CodeBlock(code, language?)` | ` ```lang\ncode\n``` ` |
|
|
78
|
+
| `blockquote` | `BlockQuote(children)` | `> text` |
|
|
79
|
+
| `bulletList > listItem` | `BulletList(items) > ListItem(children)` | `- item` |
|
|
80
|
+
| `orderedList > listItem` | `OrderedList(items, start) > ListItem(children)` | `1. item` |
|
|
81
|
+
| `taskList > taskItem` | `BulletList > ListItem(checked=bool)` | `- [x]` / `- [ ]` |
|
|
82
|
+
| `decisionList > decisionItem` | `BulletList > ListItem(checked=bool)` | `- [x]` / `- [ ]` |
|
|
83
|
+
| `rule` | `ThematicBreak` | `---` |
|
|
84
|
+
| `table > tableRow > tableHeader/tableCell` | `Table(head, body, alignments)` | GFM table |
|
|
85
|
+
| `mediaSingle > media` (external) | `Paragraph > Image(url, alt)` | `` |
|
|
86
|
+
| `mediaSingle > media` (non-external) | `Paragraph > Text("[Image: id]")` | `[Image: id]` |
|
|
87
|
+
| `mediaGroup > media` | `Paragraph > Image/Text` | `` / `[Image: id]` |
|
|
88
|
+
| `panel` | `BlockQuote(children)` | `> text` |
|
|
89
|
+
| `expand` / `nestedExpand` (title?) | `BlockQuote(children)` (title prepended) | `> title\n> text` |
|
|
90
|
+
| `layoutSection > layoutColumn` | flattened blocks | columns flattened |
|
|
91
|
+
| `blockCard` (url) | `Paragraph > Link(url)` | `[url](url)` |
|
|
92
|
+
| `embedCard` (url) | `Paragraph > Link(url)` | `[url](url)` |
|
|
93
|
+
|
|
94
|
+
### Inline
|
|
95
|
+
|
|
96
|
+
| ADF | AST | Markdown |
|
|
97
|
+
| ---------------------- | ----------------------------- | ------------------- |
|
|
98
|
+
| `text` | `Text(text)` | plain text |
|
|
99
|
+
| `text` + `strong` mark | `Strong(children)` | `**text**` |
|
|
100
|
+
| `text` + `em` mark | `Emphasis(children)` | `*text*` |
|
|
101
|
+
| `text` + `strike` mark | `Strikethrough(children)` | `~~text~~` |
|
|
102
|
+
| `text` + `code` mark | `CodeSpan(code)` | `` `code` `` |
|
|
103
|
+
| `text` + `link` mark | `Link(url, children, title?)` | `[text](url)` |
|
|
104
|
+
| `hardBreak` | `HardBreak` | `\` + newline |
|
|
105
|
+
| — | `SoftBreak` | newline |
|
|
106
|
+
| `mention` | `CodeSpan(code)` | `` `@user` `` |
|
|
107
|
+
| `emoji` | `Text(text)` | `:shortName:` |
|
|
108
|
+
| `date` | `CodeSpan(code)` | `` `2024-01-01` `` |
|
|
109
|
+
| `status` | `CodeSpan(code)` | `` `status text` `` |
|
|
110
|
+
| `inlineCard` (url) | `Link(url)` | `[url](url)` |
|
|
111
|
+
| — | `Image(url, alt, title?)` | `` |
|
|
112
|
+
|
|
113
|
+
### Not Supported
|
|
114
|
+
|
|
115
|
+
| Element | Behavior |
|
|
116
|
+
| ---------------------------------------------------------------------- | -------------------- |
|
|
117
|
+
| ADF marks: `underline`, `textColor`, `backgroundColor`, `subsup` | silently ignored |
|
|
118
|
+
| ADF blocks: `extension`, `bodiedExtension`, `syncBlock`, `bodiedSyncBlock` | `[type]` placeholder |
|
|
119
|
+
| ADF inlines: `placeholder`, `inlineExtension`, `mediaInline` | `[type]` placeholder |
|
|
120
|
+
| ADF table: `colspan`, `rowspan` | expanded with empty cells |
|
|
121
|
+
| ADF table: `background`, `colwidth` | attributes ignored |
|
|
122
|
+
| ADF table: non-paragraph cell content | `[type]` placeholder |
|
|
123
|
+
| Markdown: raw HTML (block, inline) | silently ignored |
|
|
124
|
+
|
|
125
|
+
## Development
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
uv sync --extra dev
|
|
129
|
+
uv run pytest -v
|
|
130
|
+
uv run black src/ tests/
|
|
131
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "marklas"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Bidirectional converter between GitHub Flavored Markdown and Atlassian Document Format"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "byExist", email = "jongbeom.kwon@gmail.com" }
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.13"
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Programming Language :: Python :: 3.13",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Typing :: Typed",
|
|
16
|
+
]
|
|
17
|
+
dependencies = ["mistune>=3.2"]
|
|
18
|
+
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
dev = [
|
|
21
|
+
"black>=26.1.0",
|
|
22
|
+
"pytest>=8.0.0",
|
|
23
|
+
"pytest-cov>=4.1.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[build-system]
|
|
27
|
+
requires = ["uv_build>=0.9.21,<0.10.0"]
|
|
28
|
+
build-backend = "uv_build"
|
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from marklas.ast.base import Node
|
|
7
|
+
from marklas.ast.inlines import Inline
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Block(Node):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class Document(Node):
|
|
17
|
+
children: list[Block]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Paragraph(Block):
|
|
22
|
+
children: list[Inline]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class Heading(Block):
|
|
27
|
+
level: Literal[1, 2, 3, 4, 5, 6]
|
|
28
|
+
children: list[Inline]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class CodeBlock(Block):
|
|
33
|
+
code: str
|
|
34
|
+
language: str | None = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class BlockQuote(Block):
|
|
39
|
+
children: list[Block]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class ThematicBreak(Block):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class ListItem(Block):
|
|
49
|
+
children: list[Block]
|
|
50
|
+
checked: bool | None = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class BulletList(Block):
|
|
55
|
+
items: list[ListItem]
|
|
56
|
+
tight: bool = True
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class OrderedList(Block):
|
|
61
|
+
items: list[ListItem]
|
|
62
|
+
start: int = 1
|
|
63
|
+
tight: bool = True
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class TableCell(Node):
|
|
68
|
+
children: list[Inline]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class Table(Block):
|
|
73
|
+
head: list[TableCell]
|
|
74
|
+
body: list[list[TableCell]]
|
|
75
|
+
alignments: list[Literal["left", "center", "right"] | None] = field(
|
|
76
|
+
default_factory=list[Literal["left", "center", "right"] | None]
|
|
77
|
+
)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from marklas.ast.base import Node
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class Inline(Node):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class Text(Inline):
|
|
15
|
+
text: str
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class Strong(Inline):
|
|
20
|
+
children: list[Inline]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class Emphasis(Inline):
|
|
25
|
+
children: list[Inline]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class Strikethrough(Inline):
|
|
30
|
+
children: list[Inline]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class Link(Inline):
|
|
35
|
+
url: str
|
|
36
|
+
children: list[Inline]
|
|
37
|
+
title: str | None = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class Image(Inline):
|
|
42
|
+
url: str
|
|
43
|
+
alt: str = ""
|
|
44
|
+
title: str | None = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class CodeSpan(Inline):
|
|
49
|
+
code: str
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class HardBreak(Inline):
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class SoftBreak(Inline):
|
|
59
|
+
pass
|
|
File without changes
|