md2typst 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.
- md2typst-0.1.0/PKG-INFO +215 -0
- md2typst-0.1.0/README.md +202 -0
- md2typst-0.1.0/pyproject.toml +46 -0
- md2typst-0.1.0/src/md2typst/__init__.py +169 -0
- md2typst-0.1.0/src/md2typst/ast.py +248 -0
- md2typst-0.1.0/src/md2typst/config.py +201 -0
- md2typst-0.1.0/src/md2typst/generator.py +341 -0
- md2typst-0.1.0/src/md2typst/parsers/__init__.py +55 -0
- md2typst-0.1.0/src/md2typst/parsers/base.py +57 -0
- md2typst-0.1.0/src/md2typst/parsers/markdown_it.py +419 -0
- md2typst-0.1.0/src/md2typst/parsers/marko.py +275 -0
- md2typst-0.1.0/src/md2typst/parsers/mistune.py +260 -0
md2typst-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: md2typst
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Markdown to Typst converter with multiple parser support
|
|
5
|
+
Author: Stefane Fermigier
|
|
6
|
+
Author-email: Stefane Fermigier <sf@abilian.com>
|
|
7
|
+
Requires-Dist: markdown-it-py>=3.0.0
|
|
8
|
+
Requires-Dist: click>=8.0.0
|
|
9
|
+
Requires-Dist: mistune>=3.0.0
|
|
10
|
+
Requires-Dist: marko>=2.0.0
|
|
11
|
+
Requires-Python: >=3.12
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# md2typst
|
|
15
|
+
|
|
16
|
+
A robust Markdown to [Typst](https://typst.app/) converter in Python with support for multiple Markdown parsers.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- **Multiple parser backends**: Choose from markdown-it-py, mistune, or marko at runtime
|
|
21
|
+
- **GFM support**: Tables, strikethrough, and other GitHub Flavored Markdown extensions
|
|
22
|
+
- **Configurable**: TOML configuration files and CLI options
|
|
23
|
+
- **Extensible**: Plugin support for parser-specific extensions
|
|
24
|
+
- **Well-tested**: Comprehensive test suite with TCK validation against CommonMark
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Using pip
|
|
30
|
+
pip install md2typst
|
|
31
|
+
|
|
32
|
+
# Using uv
|
|
33
|
+
uv add md2typst
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
### Command Line
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Convert a file
|
|
42
|
+
md2typst input.md -o output.typ
|
|
43
|
+
|
|
44
|
+
# Convert from stdin
|
|
45
|
+
echo "# Hello **World**" | md2typst
|
|
46
|
+
|
|
47
|
+
# Use a specific parser
|
|
48
|
+
md2typst --parser mistune input.md
|
|
49
|
+
|
|
50
|
+
# List available parsers
|
|
51
|
+
md2typst --list-parsers
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Python API
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from md2typst import convert
|
|
58
|
+
|
|
59
|
+
# Simple conversion
|
|
60
|
+
typst = convert("# Hello **World**")
|
|
61
|
+
print(typst)
|
|
62
|
+
# Output: = Hello *World*
|
|
63
|
+
|
|
64
|
+
# With specific parser
|
|
65
|
+
typst = convert("~~deleted~~", parser="mistune")
|
|
66
|
+
print(typst)
|
|
67
|
+
# Output: #strike[deleted]
|
|
68
|
+
|
|
69
|
+
# With configuration
|
|
70
|
+
from md2typst import convert_with_config
|
|
71
|
+
from md2typst.config import Config
|
|
72
|
+
|
|
73
|
+
config = Config(parser="marko", plugins=["gfm"])
|
|
74
|
+
typst = convert_with_config("| A | B |\n|---|---|\n| 1 | 2 |", config)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Supported Parsers
|
|
78
|
+
|
|
79
|
+
| Parser | CLI Name | Description |
|
|
80
|
+
|--------|----------|-------------|
|
|
81
|
+
| [markdown-it-py](https://github.com/executablebooks/markdown-it-py) | `markdown-it` | Default. CommonMark compliant, extensible |
|
|
82
|
+
| [mistune](https://github.com/lepture/mistune) | `mistune` | Fast, pure Python |
|
|
83
|
+
| [marko](https://github.com/frostming/marko) | `marko` | CommonMark compliant, extensible |
|
|
84
|
+
|
|
85
|
+
All parsers have GFM extensions (tables, strikethrough) enabled by default.
|
|
86
|
+
|
|
87
|
+
## Markdown to Typst Mapping
|
|
88
|
+
|
|
89
|
+
| Markdown | Typst |
|
|
90
|
+
|----------|-------|
|
|
91
|
+
| `# Heading` | `= Heading` |
|
|
92
|
+
| `## Heading 2` | `== Heading 2` |
|
|
93
|
+
| `*italic*` | `_italic_` |
|
|
94
|
+
| `**bold**` | `*bold*` |
|
|
95
|
+
| `~~strike~~` | `#strike[strike]` |
|
|
96
|
+
| `` `code` `` | `` `code` `` |
|
|
97
|
+
| `[text](url)` | `#link("url")[text]` |
|
|
98
|
+
| `` | `#image("url", alt: "alt")` |
|
|
99
|
+
| `> quote` | `#block(...)[quote]` |
|
|
100
|
+
| `---` | `#line(length: 100%)` |
|
|
101
|
+
| GFM tables | `#table(...)` |
|
|
102
|
+
|
|
103
|
+
## Configuration
|
|
104
|
+
|
|
105
|
+
Configuration is loaded from multiple sources (highest priority first):
|
|
106
|
+
|
|
107
|
+
1. CLI arguments (`--parser`, `--plugin`)
|
|
108
|
+
2. Explicit config file (`--config path/to/config.toml`)
|
|
109
|
+
3. `.md2typst.toml` in the current or parent directories
|
|
110
|
+
4. `[tool.md2typst]` section in `pyproject.toml`
|
|
111
|
+
|
|
112
|
+
### Example Configuration
|
|
113
|
+
|
|
114
|
+
**.md2typst.toml**:
|
|
115
|
+
```toml
|
|
116
|
+
parser = "mistune"
|
|
117
|
+
plugins = ["strikethrough", "table"]
|
|
118
|
+
|
|
119
|
+
[parser_options]
|
|
120
|
+
html = true
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**pyproject.toml**:
|
|
124
|
+
```toml
|
|
125
|
+
[tool.md2typst]
|
|
126
|
+
parser = "markdown-it"
|
|
127
|
+
plugins = ["gfm"]
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### CLI Options
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
md2typst --help
|
|
134
|
+
|
|
135
|
+
Options:
|
|
136
|
+
-o, --output FILE Output file (default: stdout)
|
|
137
|
+
-p, --parser NAME Parser to use (markdown-it, mistune, marko)
|
|
138
|
+
--plugin NAME Load parser plugin (can be repeated)
|
|
139
|
+
--config FILE Path to configuration file
|
|
140
|
+
--list-parsers List available parsers
|
|
141
|
+
--show-config Show effective configuration
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Development
|
|
145
|
+
|
|
146
|
+
### Setup
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
git clone https://github.com/user/md2typst.git
|
|
150
|
+
cd md2typst
|
|
151
|
+
uv sync
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Running Tests
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Run all tests (benchmarks skipped by default)
|
|
158
|
+
uv run pytest
|
|
159
|
+
|
|
160
|
+
# Run by category
|
|
161
|
+
uv run pytest -m unit # Unit tests (fast)
|
|
162
|
+
uv run pytest -m integration # Integration tests
|
|
163
|
+
uv run pytest -m e2e # End-to-end tests
|
|
164
|
+
uv run pytest -m benchmark # Benchmark tests
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Test Structure
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
tests/
|
|
171
|
+
├── a_unit/ # Unit tests (AST, generator)
|
|
172
|
+
├── b_integration/ # Integration tests (parsers, config, TCK)
|
|
173
|
+
├── c_e2e/ # End-to-end tests
|
|
174
|
+
├── d_benchmark/ # Performance benchmarks
|
|
175
|
+
└── fixtures/ # Test fixtures (CommonMark, GFM)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Code Quality
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# Type checking
|
|
182
|
+
uv run mypy src/
|
|
183
|
+
|
|
184
|
+
# Linting
|
|
185
|
+
uv run ruff check src/
|
|
186
|
+
|
|
187
|
+
# Formatting
|
|
188
|
+
uv run ruff format src/
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Architecture
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
Markdown Input → Parser → AST → Generator → Typst Output
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The converter uses a parser-agnostic AST (Abstract Syntax Tree) that decouples parsing from code generation. This allows:
|
|
198
|
+
|
|
199
|
+
- Swapping parsers without changing the generator
|
|
200
|
+
- Consistent output regardless of parser choice
|
|
201
|
+
- Easy extension with new parsers
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT
|
|
206
|
+
|
|
207
|
+
## Contributing
|
|
208
|
+
|
|
209
|
+
Contributions are welcome! Please:
|
|
210
|
+
|
|
211
|
+
1. Fork the repository
|
|
212
|
+
2. Create a feature branch
|
|
213
|
+
3. Add tests for new functionality
|
|
214
|
+
4. Ensure all tests pass
|
|
215
|
+
5. Submit a pull request
|
md2typst-0.1.0/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# md2typst
|
|
2
|
+
|
|
3
|
+
A robust Markdown to [Typst](https://typst.app/) converter in Python with support for multiple Markdown parsers.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multiple parser backends**: Choose from markdown-it-py, mistune, or marko at runtime
|
|
8
|
+
- **GFM support**: Tables, strikethrough, and other GitHub Flavored Markdown extensions
|
|
9
|
+
- **Configurable**: TOML configuration files and CLI options
|
|
10
|
+
- **Extensible**: Plugin support for parser-specific extensions
|
|
11
|
+
- **Well-tested**: Comprehensive test suite with TCK validation against CommonMark
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Using pip
|
|
17
|
+
pip install md2typst
|
|
18
|
+
|
|
19
|
+
# Using uv
|
|
20
|
+
uv add md2typst
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### Command Line
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Convert a file
|
|
29
|
+
md2typst input.md -o output.typ
|
|
30
|
+
|
|
31
|
+
# Convert from stdin
|
|
32
|
+
echo "# Hello **World**" | md2typst
|
|
33
|
+
|
|
34
|
+
# Use a specific parser
|
|
35
|
+
md2typst --parser mistune input.md
|
|
36
|
+
|
|
37
|
+
# List available parsers
|
|
38
|
+
md2typst --list-parsers
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Python API
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from md2typst import convert
|
|
45
|
+
|
|
46
|
+
# Simple conversion
|
|
47
|
+
typst = convert("# Hello **World**")
|
|
48
|
+
print(typst)
|
|
49
|
+
# Output: = Hello *World*
|
|
50
|
+
|
|
51
|
+
# With specific parser
|
|
52
|
+
typst = convert("~~deleted~~", parser="mistune")
|
|
53
|
+
print(typst)
|
|
54
|
+
# Output: #strike[deleted]
|
|
55
|
+
|
|
56
|
+
# With configuration
|
|
57
|
+
from md2typst import convert_with_config
|
|
58
|
+
from md2typst.config import Config
|
|
59
|
+
|
|
60
|
+
config = Config(parser="marko", plugins=["gfm"])
|
|
61
|
+
typst = convert_with_config("| A | B |\n|---|---|\n| 1 | 2 |", config)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Supported Parsers
|
|
65
|
+
|
|
66
|
+
| Parser | CLI Name | Description |
|
|
67
|
+
|--------|----------|-------------|
|
|
68
|
+
| [markdown-it-py](https://github.com/executablebooks/markdown-it-py) | `markdown-it` | Default. CommonMark compliant, extensible |
|
|
69
|
+
| [mistune](https://github.com/lepture/mistune) | `mistune` | Fast, pure Python |
|
|
70
|
+
| [marko](https://github.com/frostming/marko) | `marko` | CommonMark compliant, extensible |
|
|
71
|
+
|
|
72
|
+
All parsers have GFM extensions (tables, strikethrough) enabled by default.
|
|
73
|
+
|
|
74
|
+
## Markdown to Typst Mapping
|
|
75
|
+
|
|
76
|
+
| Markdown | Typst |
|
|
77
|
+
|----------|-------|
|
|
78
|
+
| `# Heading` | `= Heading` |
|
|
79
|
+
| `## Heading 2` | `== Heading 2` |
|
|
80
|
+
| `*italic*` | `_italic_` |
|
|
81
|
+
| `**bold**` | `*bold*` |
|
|
82
|
+
| `~~strike~~` | `#strike[strike]` |
|
|
83
|
+
| `` `code` `` | `` `code` `` |
|
|
84
|
+
| `[text](url)` | `#link("url")[text]` |
|
|
85
|
+
| `` | `#image("url", alt: "alt")` |
|
|
86
|
+
| `> quote` | `#block(...)[quote]` |
|
|
87
|
+
| `---` | `#line(length: 100%)` |
|
|
88
|
+
| GFM tables | `#table(...)` |
|
|
89
|
+
|
|
90
|
+
## Configuration
|
|
91
|
+
|
|
92
|
+
Configuration is loaded from multiple sources (highest priority first):
|
|
93
|
+
|
|
94
|
+
1. CLI arguments (`--parser`, `--plugin`)
|
|
95
|
+
2. Explicit config file (`--config path/to/config.toml`)
|
|
96
|
+
3. `.md2typst.toml` in the current or parent directories
|
|
97
|
+
4. `[tool.md2typst]` section in `pyproject.toml`
|
|
98
|
+
|
|
99
|
+
### Example Configuration
|
|
100
|
+
|
|
101
|
+
**.md2typst.toml**:
|
|
102
|
+
```toml
|
|
103
|
+
parser = "mistune"
|
|
104
|
+
plugins = ["strikethrough", "table"]
|
|
105
|
+
|
|
106
|
+
[parser_options]
|
|
107
|
+
html = true
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**pyproject.toml**:
|
|
111
|
+
```toml
|
|
112
|
+
[tool.md2typst]
|
|
113
|
+
parser = "markdown-it"
|
|
114
|
+
plugins = ["gfm"]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### CLI Options
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
md2typst --help
|
|
121
|
+
|
|
122
|
+
Options:
|
|
123
|
+
-o, --output FILE Output file (default: stdout)
|
|
124
|
+
-p, --parser NAME Parser to use (markdown-it, mistune, marko)
|
|
125
|
+
--plugin NAME Load parser plugin (can be repeated)
|
|
126
|
+
--config FILE Path to configuration file
|
|
127
|
+
--list-parsers List available parsers
|
|
128
|
+
--show-config Show effective configuration
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
### Setup
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
git clone https://github.com/user/md2typst.git
|
|
137
|
+
cd md2typst
|
|
138
|
+
uv sync
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Running Tests
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Run all tests (benchmarks skipped by default)
|
|
145
|
+
uv run pytest
|
|
146
|
+
|
|
147
|
+
# Run by category
|
|
148
|
+
uv run pytest -m unit # Unit tests (fast)
|
|
149
|
+
uv run pytest -m integration # Integration tests
|
|
150
|
+
uv run pytest -m e2e # End-to-end tests
|
|
151
|
+
uv run pytest -m benchmark # Benchmark tests
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Test Structure
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
tests/
|
|
158
|
+
├── a_unit/ # Unit tests (AST, generator)
|
|
159
|
+
├── b_integration/ # Integration tests (parsers, config, TCK)
|
|
160
|
+
├── c_e2e/ # End-to-end tests
|
|
161
|
+
├── d_benchmark/ # Performance benchmarks
|
|
162
|
+
└── fixtures/ # Test fixtures (CommonMark, GFM)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Code Quality
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# Type checking
|
|
169
|
+
uv run mypy src/
|
|
170
|
+
|
|
171
|
+
# Linting
|
|
172
|
+
uv run ruff check src/
|
|
173
|
+
|
|
174
|
+
# Formatting
|
|
175
|
+
uv run ruff format src/
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Architecture
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
Markdown Input → Parser → AST → Generator → Typst Output
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
The converter uses a parser-agnostic AST (Abstract Syntax Tree) that decouples parsing from code generation. This allows:
|
|
185
|
+
|
|
186
|
+
- Swapping parsers without changing the generator
|
|
187
|
+
- Consistent output regardless of parser choice
|
|
188
|
+
- Easy extension with new parsers
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT
|
|
193
|
+
|
|
194
|
+
## Contributing
|
|
195
|
+
|
|
196
|
+
Contributions are welcome! Please:
|
|
197
|
+
|
|
198
|
+
1. Fork the repository
|
|
199
|
+
2. Create a feature branch
|
|
200
|
+
3. Add tests for new functionality
|
|
201
|
+
4. Ensure all tests pass
|
|
202
|
+
5. Submit a pull request
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "md2typst"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Markdown to Typst converter with multiple parser support"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Stefane Fermigier", email = "sf@abilian.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"markdown-it-py>=3.0.0",
|
|
12
|
+
"click>=8.0.0",
|
|
13
|
+
"mistune>=3.0.0",
|
|
14
|
+
"marko>=2.0.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
md2typst = "md2typst:main"
|
|
19
|
+
|
|
20
|
+
[dependency-groups]
|
|
21
|
+
dev = [
|
|
22
|
+
"pytest>=8.0.0",
|
|
23
|
+
"pytest-cov>=4.0.0",
|
|
24
|
+
"pytest-benchmark>=4.0.0",
|
|
25
|
+
"hypothesis>=6.0.0",
|
|
26
|
+
"pyyaml>=6.0.0",
|
|
27
|
+
"ty>=0.0.8",
|
|
28
|
+
"pyrefly>=0.46.3",
|
|
29
|
+
"mypy>=1.19.1",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[build-system]
|
|
33
|
+
requires = ["uv_build>=0.9.13,<0.10.0"]
|
|
34
|
+
build-backend = "uv_build"
|
|
35
|
+
|
|
36
|
+
[tool.pytest.ini_options]
|
|
37
|
+
testpaths = ["tests"]
|
|
38
|
+
pythonpath = ["src"]
|
|
39
|
+
addopts = "-m 'not benchmark'"
|
|
40
|
+
markers = [
|
|
41
|
+
"unit: Unit tests (fast, isolated)",
|
|
42
|
+
"integration: Integration tests (multiple components)",
|
|
43
|
+
"e2e: End-to-end tests (full workflow)",
|
|
44
|
+
"benchmark: Benchmark tests (slow, run with -m benchmark)",
|
|
45
|
+
"slow: Slow tests",
|
|
46
|
+
]
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""md2typst - Markdown to Typst converter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import contextlib
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from md2typst.ast import Document
|
|
10
|
+
from md2typst.config import Config, load_config
|
|
11
|
+
from md2typst.generator import TypstGenerator, generate_typst
|
|
12
|
+
from md2typst.parsers import get_parser, list_parsers
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"Config",
|
|
18
|
+
"Document",
|
|
19
|
+
"TypstGenerator",
|
|
20
|
+
"convert",
|
|
21
|
+
"convert_with_config",
|
|
22
|
+
"generate_typst",
|
|
23
|
+
"get_parser",
|
|
24
|
+
"list_parsers",
|
|
25
|
+
"main",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def convert(
|
|
30
|
+
markdown: str,
|
|
31
|
+
parser: str | None = None,
|
|
32
|
+
parser_options: dict[str, Any] | None = None,
|
|
33
|
+
plugins: list[str] | None = None,
|
|
34
|
+
) -> str:
|
|
35
|
+
"""Convert Markdown text to Typst.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
markdown: The Markdown source text.
|
|
39
|
+
parser: Optional parser name. Uses default if not specified.
|
|
40
|
+
parser_options: Optional parser-specific options.
|
|
41
|
+
plugins: Optional list of parser plugins to load.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
The generated Typst source code.
|
|
45
|
+
"""
|
|
46
|
+
p = get_parser(parser)
|
|
47
|
+
|
|
48
|
+
if parser_options:
|
|
49
|
+
p.configure(parser_options)
|
|
50
|
+
|
|
51
|
+
if plugins:
|
|
52
|
+
for plugin in plugins:
|
|
53
|
+
with contextlib.suppress(NotImplementedError):
|
|
54
|
+
p.load_plugin(plugin)
|
|
55
|
+
|
|
56
|
+
doc = p.parse(markdown)
|
|
57
|
+
return generate_typst(doc)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def convert_with_config(markdown: str, config: Config) -> str:
|
|
61
|
+
"""Convert Markdown text to Typst using a Config object.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
markdown: The Markdown source text.
|
|
65
|
+
config: Configuration object.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
The generated Typst source code.
|
|
69
|
+
"""
|
|
70
|
+
return convert(
|
|
71
|
+
markdown,
|
|
72
|
+
parser=config.parser,
|
|
73
|
+
parser_options=config.parser_options,
|
|
74
|
+
plugins=config.plugins,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def main() -> None:
|
|
79
|
+
"""CLI entry point."""
|
|
80
|
+
import sys
|
|
81
|
+
|
|
82
|
+
import click
|
|
83
|
+
|
|
84
|
+
@click.command()
|
|
85
|
+
@click.argument("input", type=click.Path(exists=True), required=False)
|
|
86
|
+
@click.option(
|
|
87
|
+
"-o", "--output", type=click.Path(), help="Output file (default: stdout)"
|
|
88
|
+
)
|
|
89
|
+
@click.option("-p", "--parser", default=None, help="Parser to use")
|
|
90
|
+
@click.option(
|
|
91
|
+
"-c",
|
|
92
|
+
"--config",
|
|
93
|
+
"config_file",
|
|
94
|
+
type=click.Path(exists=True),
|
|
95
|
+
help="Config file path",
|
|
96
|
+
)
|
|
97
|
+
@click.option(
|
|
98
|
+
"--plugin", multiple=True, help="Load parser plugin (can be repeated)"
|
|
99
|
+
)
|
|
100
|
+
@click.option("--list-parsers", is_flag=True, help="List available parsers")
|
|
101
|
+
@click.option("--show-config", is_flag=True, help="Show effective configuration")
|
|
102
|
+
@click.version_option(__version__)
|
|
103
|
+
def cli(
|
|
104
|
+
input: str | None,
|
|
105
|
+
output: str | None,
|
|
106
|
+
parser: str | None,
|
|
107
|
+
config_file: str | None,
|
|
108
|
+
plugin: tuple[str, ...],
|
|
109
|
+
list_parsers: bool,
|
|
110
|
+
show_config: bool,
|
|
111
|
+
) -> None:
|
|
112
|
+
"""Convert Markdown to Typst.
|
|
113
|
+
|
|
114
|
+
INPUT is the Markdown file to convert. Use - for stdin.
|
|
115
|
+
"""
|
|
116
|
+
if list_parsers:
|
|
117
|
+
from md2typst.parsers import list_parsers as lp
|
|
118
|
+
|
|
119
|
+
click.echo("Available parsers:")
|
|
120
|
+
for name in lp():
|
|
121
|
+
click.echo(f" - {name}")
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
# Determine start directory for config search
|
|
125
|
+
if input and input != "-":
|
|
126
|
+
start_dir = Path(input).parent
|
|
127
|
+
else:
|
|
128
|
+
start_dir = Path.cwd()
|
|
129
|
+
|
|
130
|
+
# Build CLI overrides
|
|
131
|
+
cli_overrides: dict[str, Any] = {}
|
|
132
|
+
if parser:
|
|
133
|
+
cli_overrides["parser"] = parser
|
|
134
|
+
if plugin:
|
|
135
|
+
cli_overrides["plugins"] = list(plugin)
|
|
136
|
+
|
|
137
|
+
# Load configuration
|
|
138
|
+
config = load_config(
|
|
139
|
+
config_file=Path(config_file) if config_file else None,
|
|
140
|
+
start_dir=start_dir,
|
|
141
|
+
cli_overrides=cli_overrides,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if show_config:
|
|
145
|
+
click.echo("Effective configuration:")
|
|
146
|
+
click.echo(f" parser: {config.parser}")
|
|
147
|
+
click.echo(f" plugins: {config.plugins}")
|
|
148
|
+
click.echo(f" parser_options: {config.parser_options}")
|
|
149
|
+
click.echo(f" output_options: {config.output_options}")
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
if input is None:
|
|
153
|
+
# Read from stdin
|
|
154
|
+
text = sys.stdin.read()
|
|
155
|
+
elif input == "-":
|
|
156
|
+
text = sys.stdin.read()
|
|
157
|
+
else:
|
|
158
|
+
with Path(input).open() as f:
|
|
159
|
+
text = f.read()
|
|
160
|
+
|
|
161
|
+
result = convert_with_config(text, config)
|
|
162
|
+
|
|
163
|
+
if output:
|
|
164
|
+
with Path(output).open("w") as f:
|
|
165
|
+
f.write(result)
|
|
166
|
+
else:
|
|
167
|
+
click.echo(result)
|
|
168
|
+
|
|
169
|
+
cli()
|