termrender 3.0.1__tar.gz → 4.0.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.
- {termrender-3.0.1 → termrender-4.0.0}/CHANGELOG.md +57 -0
- {termrender-3.0.1 → termrender-4.0.0}/PKG-INFO +29 -27
- {termrender-3.0.1 → termrender-4.0.0}/README.md +28 -26
- termrender-4.0.0/src/termrender/CLAUDE.md +3 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/__main__.py +8 -6
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/parser.py +15 -4
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/renderers/CLAUDE.md +2 -0
- termrender-4.0.0/src/termrender/renderers/mermaid.py +142 -0
- {termrender-3.0.1 → termrender-4.0.0}/tests/test_mermaid_compat.py +37 -1
- {termrender-3.0.1 → termrender-4.0.0}/tests/test_myst_gaps.py +5 -5
- termrender-3.0.1/src/termrender/CLAUDE.md +0 -1
- termrender-3.0.1/src/termrender/renderers/mermaid.py +0 -99
- {termrender-3.0.1 → termrender-4.0.0}/.github/workflows/publish.yml +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/.gitignore +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/CLAUDE.md +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/LICENSE +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/design.json +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/pyproject.toml +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/requirements.json +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/scripts/build-mermaid-ascii.sh +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/__init__.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/_bin/mermaid-ascii-darwin-arm64 +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/_mermaid_bin.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/blocks.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/emit.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/layout.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/py.typed +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/renderers/__init__.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/renderers/borders.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/renderers/charts.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/renderers/code.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/renderers/columns.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/renderers/diff.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/renderers/divider.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/renderers/panel.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/renderers/quote.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/renderers/stat.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/renderers/table.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/renderers/text.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/renderers/timeline.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/renderers/tree.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/src/termrender/style.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/tests/__init__.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/tests/test_charts.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/tests/test_cli_contract.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/tests/test_column_alignment.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/tests/test_diff.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/tests/test_inline_badge.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/tests/test_linebreak.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/tests/test_stat.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/tests/test_tasklist.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/tests/test_timeline.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/tests/test_variable_colons.py +0 -0
- {termrender-3.0.1 → termrender-4.0.0}/uv.lock +0 -0
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v4.0.0 (2026-07-06)
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
- **mermaid**: Also route ```mermaid fences through the mermaid renderer
|
|
9
|
+
([`99a8017`](https://github.com/crouton-labs/termrender/commit/99a801783d719125505eef2dc1194e2e418ed025))
|
|
10
|
+
|
|
11
|
+
BREAKING CHANGE: partially reverses 083f590. A fenced code block whose info string is `mermaid`
|
|
12
|
+
(e.g. GFM ````mermaid```` fences used by GitHub and most docs/agents unaware of the :::mermaid
|
|
13
|
+
directive) now renders as an ASCII mermaid diagram, same as :::mermaid. Any other language tag
|
|
14
|
+
still falls through to a plain CODE block; the :::mermaid directive is unchanged.
|
|
15
|
+
|
|
16
|
+
Root-caused: the reported bug (fences render as raw source, not a diagram) was not a regression or
|
|
17
|
+
missing binary — the vendored mermaid-ascii binary and subprocess pipeline both work correctly. It
|
|
18
|
+
was 083f590's deliberate, documented breaking change dropping fence support. Human chose to extend
|
|
19
|
+
support back to fences rather than close as working-as-intended.
|
|
20
|
+
|
|
21
|
+
### Breaking Changes
|
|
22
|
+
|
|
23
|
+
- **mermaid**: Partially reverses 083f590. A fenced code block whose info string is `mermaid` (e.g.
|
|
24
|
+
GFM ````mermaid```` fences used by GitHub and most docs/agents unaware of the :::mermaid
|
|
25
|
+
directive) now renders as an ASCII mermaid diagram, same as :::mermaid. Any other language tag
|
|
26
|
+
still falls through to a plain CODE block; the :::mermaid directive is unchanged.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## v3.0.2 (2026-06-17)
|
|
30
|
+
|
|
31
|
+
### Bug Fixes
|
|
32
|
+
|
|
33
|
+
- **docs**: Correct nested-directive examples to increasing-colon rule
|
|
34
|
+
([`4332854`](https://github.com/crouton-labs/termrender/commit/4332854cc1bdab8122f7e022c2d79a69e2d8a719))
|
|
35
|
+
|
|
36
|
+
Every nested example in the README and the doc -h 'Document format' help predated 4a501d9 (require
|
|
37
|
+
strictly more colons on outer fences) and fails `termrender doc check`: the flagship deploy panel,
|
|
38
|
+
the Columns example, and the Nesting section all used equal colons. Rewrote them so each outer
|
|
39
|
+
directive uses strictly more colons than the one nested inside (MyST-style), documented the rule
|
|
40
|
+
in the intro + Nesting prose + the agent-facing doc help, and added the gotcha to src CLAUDE.md.
|
|
41
|
+
Rendered output is unchanged (colon count is pure syntax). All 11 README markdown blocks now pass
|
|
42
|
+
doc check.
|
|
43
|
+
|
|
44
|
+
- **mermaid**: Normalize flowchart node shapes to rectangles
|
|
45
|
+
([`8c45f7c`](https://github.com/crouton-labs/termrender/commit/8c45f7c20810a3960cb037831a7c5f152406b3f6))
|
|
46
|
+
|
|
47
|
+
mermaid-ascii (vendored, pinned master 6fffb8e) only parses [text] rectangle nodes; every other
|
|
48
|
+
mermaid node shape leaked raw delimiters or the bare node id into the rendered box — B{Auth?}
|
|
49
|
+
rendered the literal 'B{Auth?}', E[(Database)] rendered '(Database)'. The preprocessor only
|
|
50
|
+
handled sequence diagrams; flowcharts passed through untouched.
|
|
51
|
+
|
|
52
|
+
Add a flowchart shape normalizer that rewrites rhombus {}, cylinder [()], circle (()), stadium ([]),
|
|
53
|
+
hexagon {{}}, subroutine [[]], and parallelogram/trapezoid [/ /] nodes to id[label], preserving
|
|
54
|
+
the label text. The backend draws a rectangle regardless, so only the text matters.
|
|
55
|
+
|
|
56
|
+
Node shapes remain unsupported on current upstream master (parseNode handles only rectangles; the 6
|
|
57
|
+
commits since our pin are arrow-parsing only), so this is a permanent workaround, not a stopgap
|
|
58
|
+
for a pin bump. Regression test + CLAUDE.md note added.
|
|
59
|
+
|
|
60
|
+
|
|
4
61
|
## v3.0.1 (2026-06-10)
|
|
5
62
|
|
|
6
63
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: termrender
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Summary: Rich terminal rendering of directive-flavored markdown
|
|
5
5
|
Project-URL: Homepage, https://github.com/CaptainCrouton89/termrender
|
|
6
6
|
Project-URL: Repository, https://github.com/CaptainCrouton89/termrender
|
|
@@ -77,13 +77,13 @@ Here's a realistic example — the kind of thing an LLM agent might produce afte
|
|
|
77
77
|
The agent writes this:
|
|
78
78
|
|
|
79
79
|
```markdown
|
|
80
|
-
|
|
80
|
+
:::::::panel{title="Deploy — api-gateway v3.2.0" color="cyan"}
|
|
81
81
|
|
|
82
82
|
Completed at **14:32 UTC** on `prod-us-east-1`. Health checks passing.
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
::::::columns
|
|
85
|
+
:::::col{width="55%"}
|
|
86
|
+
::::panel{title="Services" color="green"}
|
|
87
87
|
:::tree
|
|
88
88
|
api-gateway/ [x]
|
|
89
89
|
auth/ [x]
|
|
@@ -94,23 +94,23 @@ worker-pool/
|
|
|
94
94
|
scheduler/ [!]
|
|
95
95
|
dead-letter/ [x]
|
|
96
96
|
:::
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
::::
|
|
98
|
+
:::::
|
|
99
|
+
:::::col{width="45%"}
|
|
100
|
+
::::callout{type="success"}
|
|
101
101
|
6 of 7 services healthy
|
|
102
|
-
|
|
102
|
+
::::
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
::::callout{type="warning"}
|
|
105
105
|
scheduler: 83% memory
|
|
106
106
|
GC tuning shipping next release
|
|
107
|
-
|
|
107
|
+
::::
|
|
108
108
|
|
|
109
109
|
- **p99 latency**: 34ms
|
|
110
110
|
- **error rate**: 0.02%
|
|
111
111
|
- **throughput**: 12.4k req/s
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
:::::
|
|
113
|
+
::::::
|
|
114
114
|
|
|
115
115
|
:::divider{label="rollback"}
|
|
116
116
|
:::
|
|
@@ -124,7 +124,7 @@ kubectl rollout status deployment/api-gateway -n prod
|
|
|
124
124
|
:::quote{author="deploy-bot"}
|
|
125
125
|
Previous stable: v3.1.4 (deployed 2025-03-28)
|
|
126
126
|
:::
|
|
127
|
-
|
|
127
|
+
:::::::
|
|
128
128
|
```
|
|
129
129
|
|
|
130
130
|
And termrender produces this:
|
|
@@ -208,7 +208,7 @@ One function. Source in, ANSI string out.
|
|
|
208
208
|
|
|
209
209
|
## Directives
|
|
210
210
|
|
|
211
|
-
Triple-colon syntax with optional attributes in curly braces. They nest
|
|
211
|
+
Triple-colon syntax with optional attributes in curly braces. They nest, but an outer directive must use strictly **more** colons than the one nested inside it (`::::columns` wraps `:::col`) — see [Nesting](#nesting). Standard markdown works everywhere inside them.
|
|
212
212
|
|
|
213
213
|
```
|
|
214
214
|
:::name{key="value" key2="value2"}
|
|
@@ -246,7 +246,7 @@ Side-by-side layout. Each `:::col` takes a width as a percentage.
|
|
|
246
246
|
|
|
247
247
|
**Input:**
|
|
248
248
|
```markdown
|
|
249
|
-
|
|
249
|
+
::::columns
|
|
250
250
|
:::col{width="50%"}
|
|
251
251
|
**Before**
|
|
252
252
|
- Manual deploys
|
|
@@ -259,7 +259,7 @@ Side-by-side layout. Each `:::col` takes a width as a percentage.
|
|
|
259
259
|
- 2 min rollbacks
|
|
260
260
|
- Full staging env
|
|
261
261
|
:::
|
|
262
|
-
|
|
262
|
+
::::
|
|
263
263
|
```
|
|
264
264
|
|
|
265
265
|
**Output:**
|
|
@@ -408,7 +408,7 @@ Horizontal rules with optional centered labels.
|
|
|
408
408
|
|
|
409
409
|
### Mermaid diagrams
|
|
410
410
|
|
|
411
|
-
Renders mermaid flowcharts as ASCII art via [mermaid-ascii](https://github.com/mermaid-js/mermaid-ascii).
|
|
411
|
+
Renders mermaid flowcharts as ASCII art via [mermaid-ascii](https://github.com/mermaid-js/mermaid-ascii). Use the `:::mermaid` directive, or a standard GFM ` ```mermaid ` fenced code block — both render the same diagram.
|
|
412
412
|
|
|
413
413
|
**Input:**
|
|
414
414
|
```markdown
|
|
@@ -427,31 +427,33 @@ The diagram gets rendered as ASCII art inline in the terminal output. Requires `
|
|
|
427
427
|
|
|
428
428
|
Directives compose. Put a tree inside a panel, panels inside columns, callouts next to trees with a divider between sections.
|
|
429
429
|
|
|
430
|
+
**Nesting is by colon count: an outer directive must use strictly more colons than the directive nested inside it, and each block closes with a bare run of its own colon count on its own line.** Below, the panel uses 6 colons, the columns 5, each column 4, and the leaf tree/callout/divider 3. Innermost is always `:::` (3); add one colon per level as you go outward. (`termrender doc check` reports the exact fix when counts are off.)
|
|
431
|
+
|
|
430
432
|
**Input:**
|
|
431
433
|
```markdown
|
|
432
|
-
|
|
434
|
+
::::::panel{title="Deployment" color="green"}
|
|
433
435
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
+
:::::columns
|
|
437
|
+
::::col{width="60%"}
|
|
436
438
|
:::tree
|
|
437
439
|
services/
|
|
438
440
|
api/ [x]
|
|
439
441
|
worker/ [x]
|
|
440
442
|
scheduler/ [!]
|
|
441
443
|
:::
|
|
442
|
-
|
|
443
|
-
|
|
444
|
+
::::
|
|
445
|
+
::::col{width="40%"}
|
|
444
446
|
:::callout{type="success"}
|
|
445
447
|
2 of 3 services deployed
|
|
446
448
|
:::
|
|
447
|
-
|
|
448
|
-
|
|
449
|
+
::::
|
|
450
|
+
:::::
|
|
449
451
|
|
|
450
452
|
:::divider{label="logs"}
|
|
451
453
|
:::
|
|
452
454
|
|
|
453
455
|
Last deploy: `api@v2.4.1` at 14:32 UTC
|
|
454
|
-
|
|
456
|
+
::::::
|
|
455
457
|
```
|
|
456
458
|
|
|
457
459
|
A bordered panel containing two columns (file tree on the left, status callout on the right), a labeled divider, and a status line. All from nested directives.
|
|
@@ -49,13 +49,13 @@ Here's a realistic example — the kind of thing an LLM agent might produce afte
|
|
|
49
49
|
The agent writes this:
|
|
50
50
|
|
|
51
51
|
```markdown
|
|
52
|
-
|
|
52
|
+
:::::::panel{title="Deploy — api-gateway v3.2.0" color="cyan"}
|
|
53
53
|
|
|
54
54
|
Completed at **14:32 UTC** on `prod-us-east-1`. Health checks passing.
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
::::::columns
|
|
57
|
+
:::::col{width="55%"}
|
|
58
|
+
::::panel{title="Services" color="green"}
|
|
59
59
|
:::tree
|
|
60
60
|
api-gateway/ [x]
|
|
61
61
|
auth/ [x]
|
|
@@ -66,23 +66,23 @@ worker-pool/
|
|
|
66
66
|
scheduler/ [!]
|
|
67
67
|
dead-letter/ [x]
|
|
68
68
|
:::
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
::::
|
|
70
|
+
:::::
|
|
71
|
+
:::::col{width="45%"}
|
|
72
|
+
::::callout{type="success"}
|
|
73
73
|
6 of 7 services healthy
|
|
74
|
-
|
|
74
|
+
::::
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
::::callout{type="warning"}
|
|
77
77
|
scheduler: 83% memory
|
|
78
78
|
GC tuning shipping next release
|
|
79
|
-
|
|
79
|
+
::::
|
|
80
80
|
|
|
81
81
|
- **p99 latency**: 34ms
|
|
82
82
|
- **error rate**: 0.02%
|
|
83
83
|
- **throughput**: 12.4k req/s
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
:::::
|
|
85
|
+
::::::
|
|
86
86
|
|
|
87
87
|
:::divider{label="rollback"}
|
|
88
88
|
:::
|
|
@@ -96,7 +96,7 @@ kubectl rollout status deployment/api-gateway -n prod
|
|
|
96
96
|
:::quote{author="deploy-bot"}
|
|
97
97
|
Previous stable: v3.1.4 (deployed 2025-03-28)
|
|
98
98
|
:::
|
|
99
|
-
|
|
99
|
+
:::::::
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
And termrender produces this:
|
|
@@ -180,7 +180,7 @@ One function. Source in, ANSI string out.
|
|
|
180
180
|
|
|
181
181
|
## Directives
|
|
182
182
|
|
|
183
|
-
Triple-colon syntax with optional attributes in curly braces. They nest
|
|
183
|
+
Triple-colon syntax with optional attributes in curly braces. They nest, but an outer directive must use strictly **more** colons than the one nested inside it (`::::columns` wraps `:::col`) — see [Nesting](#nesting). Standard markdown works everywhere inside them.
|
|
184
184
|
|
|
185
185
|
```
|
|
186
186
|
:::name{key="value" key2="value2"}
|
|
@@ -218,7 +218,7 @@ Side-by-side layout. Each `:::col` takes a width as a percentage.
|
|
|
218
218
|
|
|
219
219
|
**Input:**
|
|
220
220
|
```markdown
|
|
221
|
-
|
|
221
|
+
::::columns
|
|
222
222
|
:::col{width="50%"}
|
|
223
223
|
**Before**
|
|
224
224
|
- Manual deploys
|
|
@@ -231,7 +231,7 @@ Side-by-side layout. Each `:::col` takes a width as a percentage.
|
|
|
231
231
|
- 2 min rollbacks
|
|
232
232
|
- Full staging env
|
|
233
233
|
:::
|
|
234
|
-
|
|
234
|
+
::::
|
|
235
235
|
```
|
|
236
236
|
|
|
237
237
|
**Output:**
|
|
@@ -380,7 +380,7 @@ Horizontal rules with optional centered labels.
|
|
|
380
380
|
|
|
381
381
|
### Mermaid diagrams
|
|
382
382
|
|
|
383
|
-
Renders mermaid flowcharts as ASCII art via [mermaid-ascii](https://github.com/mermaid-js/mermaid-ascii).
|
|
383
|
+
Renders mermaid flowcharts as ASCII art via [mermaid-ascii](https://github.com/mermaid-js/mermaid-ascii). Use the `:::mermaid` directive, or a standard GFM ` ```mermaid ` fenced code block — both render the same diagram.
|
|
384
384
|
|
|
385
385
|
**Input:**
|
|
386
386
|
```markdown
|
|
@@ -399,31 +399,33 @@ The diagram gets rendered as ASCII art inline in the terminal output. Requires `
|
|
|
399
399
|
|
|
400
400
|
Directives compose. Put a tree inside a panel, panels inside columns, callouts next to trees with a divider between sections.
|
|
401
401
|
|
|
402
|
+
**Nesting is by colon count: an outer directive must use strictly more colons than the directive nested inside it, and each block closes with a bare run of its own colon count on its own line.** Below, the panel uses 6 colons, the columns 5, each column 4, and the leaf tree/callout/divider 3. Innermost is always `:::` (3); add one colon per level as you go outward. (`termrender doc check` reports the exact fix when counts are off.)
|
|
403
|
+
|
|
402
404
|
**Input:**
|
|
403
405
|
```markdown
|
|
404
|
-
|
|
406
|
+
::::::panel{title="Deployment" color="green"}
|
|
405
407
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
+
:::::columns
|
|
409
|
+
::::col{width="60%"}
|
|
408
410
|
:::tree
|
|
409
411
|
services/
|
|
410
412
|
api/ [x]
|
|
411
413
|
worker/ [x]
|
|
412
414
|
scheduler/ [!]
|
|
413
415
|
:::
|
|
414
|
-
|
|
415
|
-
|
|
416
|
+
::::
|
|
417
|
+
::::col{width="40%"}
|
|
416
418
|
:::callout{type="success"}
|
|
417
419
|
2 of 3 services deployed
|
|
418
420
|
:::
|
|
419
|
-
|
|
420
|
-
|
|
421
|
+
::::
|
|
422
|
+
:::::
|
|
421
423
|
|
|
422
424
|
:::divider{label="logs"}
|
|
423
425
|
:::
|
|
424
426
|
|
|
425
427
|
Last deploy: `api@v2.4.1` at 14:32 UTC
|
|
426
|
-
|
|
428
|
+
::::::
|
|
427
429
|
```
|
|
428
430
|
|
|
429
431
|
A bordered panel containing two columns (file tree on the left, status callout on the right), a labeled divider, and a status line. All from nested directives.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
- **Nesting is by colon count** (`parser.py`, since `4a501d9`): an outer directive must use strictly MORE colons than the directive nested inside it — `::::columns` (4) wraps `:::col` (3); a leaf closes with a bare run of its own colon count. Equal-colon nesting is a `syntax_error` (the closer matches the wrong opener). Mirrors MyST colon fences. All docs/examples must follow this — `termrender doc check` is the gate.
|
|
2
|
+
- **QUOTE** height gets `+1` only when `author` or `by` attr is set. Using any other key (`attribution`, `source`) silently omits the extra line — the renderer's attribution line is clipped.
|
|
3
|
+
- **``` mermaid fences and `:::mermaid` both produce `BlockType.MERMAID`** (`parser.py::_convert_ast`, block_code handling): a fenced code block whose info string's first word is `mermaid` (case-insensitive) is special-cased to `BlockType.MERMAID` instead of `BlockType.CODE`, so GFM-style ``` ```mermaid ``` docs render the same as the directive form. This was a deliberate reversal of `083f590` (which had dropped fence support); any other language tag still falls through to a plain CODE block.
|
|
@@ -63,20 +63,22 @@ Branches
|
|
|
63
63
|
|
|
64
64
|
Document format
|
|
65
65
|
Directive-flavored markdown. Triple-colon blocks carry attributes in {} and
|
|
66
|
-
nest
|
|
67
|
-
(
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
nest by COLON COUNT: an outer block needs strictly MORE colons than the block
|
|
67
|
+
nested inside it (::::columns wraps :::col), and each block closes with a bare
|
|
68
|
+
run of its own colon count on its own line. Plain markdown (headings, **bold**,
|
|
69
|
+
*italic*, `code`, bullet/numbered lists) works everywhere, including inside
|
|
70
|
+
directives. Write structure, not box-drawing — layout, padding, and guide
|
|
71
|
+
lines are computed.
|
|
70
72
|
|
|
71
73
|
:::panel{title="" color=""} bordered box; color e.g. green|cyan|red
|
|
72
|
-
|
|
74
|
+
::::columns / :::col{width="N%"} side-by-side; col widths sum to 100%
|
|
73
75
|
:::tree{color=""} indentation → Unicode guide lines;
|
|
74
76
|
[x]=done [!]=warning status markers
|
|
75
77
|
:::callout{type=""} admonition; type success|warning|error|info
|
|
76
78
|
:::divider{label=""} labeled horizontal rule
|
|
77
79
|
:::code{lang=""} syntax-highlighted block (``` fences also work)
|
|
78
80
|
:::quote{author=""} attributed block quote
|
|
79
|
-
:::mermaid flowchart → ASCII (
|
|
81
|
+
:::mermaid flowchart → ASCII (``` mermaid fences also work)
|
|
80
82
|
"""
|
|
81
83
|
|
|
82
84
|
_DOC_RENDER_HELP = """\
|
|
@@ -303,10 +303,21 @@ def _convert_ast(nodes: list[dict], _depth: int = 0) -> list[Block]:
|
|
|
303
303
|
elif ntype == "block_code":
|
|
304
304
|
raw = node.get("raw", "")
|
|
305
305
|
info = node.get("attrs", {}).get("info", "")
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
306
|
+
# A ```mermaid fence is GFM's standard way to tag a mermaid diagram
|
|
307
|
+
# (GitHub, and most docs/agents that don't know this project's
|
|
308
|
+
# :::mermaid directive, use this form) — route it through the
|
|
309
|
+
# mermaid renderer same as :::mermaid, instead of a plain code panel.
|
|
310
|
+
lang = info.split()[0].lower() if info.split() else ""
|
|
311
|
+
if lang == "mermaid":
|
|
312
|
+
blocks.append(Block(
|
|
313
|
+
type=BlockType.MERMAID,
|
|
314
|
+
attrs={"source": raw},
|
|
315
|
+
))
|
|
316
|
+
else:
|
|
317
|
+
blocks.append(Block(
|
|
318
|
+
type=BlockType.CODE,
|
|
319
|
+
attrs={"lang": info, "source": raw},
|
|
320
|
+
))
|
|
310
321
|
|
|
311
322
|
elif ntype == "list":
|
|
312
323
|
ordered = node.get("attrs", {}).get("ordered", False)
|
|
@@ -53,4 +53,6 @@ Attribution line is rendered for `block.attrs["author"]` **or** `block.attrs["by
|
|
|
53
53
|
|
|
54
54
|
Layout pre-renders into `block.attrs["_rendered"]` (see `src/termrender/CLAUDE.md`); this renderer re-runs the subprocess only when that key is absent.
|
|
55
55
|
|
|
56
|
+
**Flowchart node shapes are normalized to rectangles**: `preprocess_mermaid_for_ascii` rewrites every non-rectangle flowchart node shape (`{rhombus}`, `[(cylinder)]`, `((circle))`, `([stadium])`, `{{hexagon}}`, `[[subroutine]]`, `[/parallelogram/]`) to the `id[label]` form via `_FLOWCHART_SHAPE_SUBS`, because mermaid-ascii's `parseNode` only recognizes `[text]` rectangles — other shapes otherwise leak raw delimiters or the bare node id into the box. This is a permanent workaround: node shapes are unsupported on current upstream master (pin `6fffb8e`) and the 6 newer master commits are arrow-parsing only, so don't expect a pin bump to remove the need. The backend draws a rectangle regardless, so only the label text matters.
|
|
57
|
+
|
|
56
58
|
**Binary resolution & no width flag**: the executable comes from `_mermaid_bin.mermaid_ascii_bin()` — the vendored `_bin/mermaid-ascii-<os>-<arch>` (pinned upstream master `6fffb8e`, built via `scripts/build-mermaid-ascii.sh`) if present for the platform, else `mermaid-ascii` on PATH (the PyPI wheel, capped at 1.2.0). **`mermaid-ascii` has no width-control flag** (never has — `--maxWidth`/`-w` do not exist); the call passes only `-f - -y 1`, so `block.width` does not constrain the diagram and wide output overflows (compounding the no-truncation gotcha above). A labeled back-edge (`X -->|lbl| Y` in `graph LR`) panics the binary on **both** 1.2.0 and master; the non-zero exit is caught and the block degrades to raw source.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Mermaid diagram renderer for termrender."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import subprocess
|
|
7
|
+
|
|
8
|
+
from termrender._mermaid_bin import mermaid_ascii_bin
|
|
9
|
+
from termrender.blocks import Block
|
|
10
|
+
from termrender.style import visual_ljust
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def fix_mermaid_encoding(text: str) -> str:
|
|
14
|
+
"""Undo mermaid-ascii's double-encoding of UTF-8 characters.
|
|
15
|
+
|
|
16
|
+
mermaid-ascii misinterprets UTF-8 input bytes as Latin-1 and re-encodes
|
|
17
|
+
to UTF-8, corrupting multi-byte characters (e.g. → becomes â\\x86\\x92).
|
|
18
|
+
Reversing the process: encode back to Latin-1 to recover the original
|
|
19
|
+
UTF-8 bytes, then decode as UTF-8.
|
|
20
|
+
"""
|
|
21
|
+
try:
|
|
22
|
+
return text.encode("latin-1").decode("utf-8")
|
|
23
|
+
except (UnicodeDecodeError, UnicodeEncodeError):
|
|
24
|
+
return text
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
_NOTE_RE = re.compile(
|
|
28
|
+
r"^(\s*)[Nn]ote\s+(?:over|left\s+of|right\s+of)\s+([^:]+?)\s*:\s*(.*)$"
|
|
29
|
+
)
|
|
30
|
+
_BR_RE = re.compile(r"<br\s*/?>", re.IGNORECASE)
|
|
31
|
+
|
|
32
|
+
# Flowchart node-shape normalizers. mermaid-ascii only parses the ``[text]``
|
|
33
|
+
# rectangle form; every other mermaid node shape leaks its raw delimiters (or
|
|
34
|
+
# the node id) into the rendered box — e.g. ``B{Auth?}`` renders the literal
|
|
35
|
+
# ``B{Auth?}`` and ``E[(Database)]`` renders ``(Database)``. Since mermaid-ascii
|
|
36
|
+
# draws every node as a rectangle regardless, we rewrite each alternate shape to
|
|
37
|
+
# ``id[label]``, preserving the label text and dropping only the shape (which
|
|
38
|
+
# the ASCII backend cannot draw anyway). Order matters: multi-char delimiters
|
|
39
|
+
# (``{{``, ``[[``, ``[(``, ``((``, ``([``) must run before their single-char
|
|
40
|
+
# counterparts so the greedy single forms don't bite off half a delimiter.
|
|
41
|
+
_FLOWCHART_SHAPE_SUBS: list[tuple[re.Pattern[str], str]] = [
|
|
42
|
+
(re.compile(r"(\w+)\{\{(.+?)\}\}"), r"\1[\2]"), # hexagon {{ }}
|
|
43
|
+
(re.compile(r"(\w+)\[\[(.+?)\]\]"), r"\1[\2]"), # subroutine [[ ]]
|
|
44
|
+
(re.compile(r"(\w+)\[\((.+?)\)\]"), r"\1[\2]"), # cylinder [( )]
|
|
45
|
+
(re.compile(r"(\w+)\(\(\((.+?)\)\)\)"), r"\1[\2]"), # double circle ((( )))
|
|
46
|
+
(re.compile(r"(\w+)\(\((.+?)\)\)"), r"\1[\2]"), # circle (( ))
|
|
47
|
+
(re.compile(r"(\w+)\(\[(.+?)\]\)"), r"\1[\2]"), # stadium ([ ])
|
|
48
|
+
(re.compile(r"(\w+)\[[\\/](.+?)[\\/]\]"), r"\1[\2]"), # parallelogram/trapezoid [/ /] [\ \] [/ \] [\ /]
|
|
49
|
+
(re.compile(r"(\w+)\{(.+?)\}"), r"\1[\2]"), # rhombus { }
|
|
50
|
+
(re.compile(r"(\w+)>(.+?)\]"), r"\1[\2]"), # asymmetric/flag > ]
|
|
51
|
+
(re.compile(r"(\w+)\((.+?)\)"), r"\1[\2]"), # round ( )
|
|
52
|
+
]
|
|
53
|
+
_UNSUPPORTED_BLOCK_RE = re.compile(
|
|
54
|
+
r"^\s*(?:loop|alt|else|opt|par|and|critical|option|break|rect|"
|
|
55
|
+
r"activate|deactivate|autonumber|end)\b.*$",
|
|
56
|
+
re.IGNORECASE,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def normalize_flowchart_shapes(source: str) -> str:
|
|
61
|
+
"""Rewrite alternate flowchart node shapes to the ``[text]`` form.
|
|
62
|
+
|
|
63
|
+
mermaid-ascii only parses rectangle nodes (``id[text]``); rhombus ``{}``,
|
|
64
|
+
cylinder ``[()]``, circle ``(())``, stadium ``([])``, hexagon ``{{}}``,
|
|
65
|
+
subroutine ``[[]]``, and parallelogram/trapezoid ``[/ /]`` nodes otherwise
|
|
66
|
+
render with their raw delimiters or bare node id. Each is rewritten to
|
|
67
|
+
``id[text]`` so the label survives; the backend draws a rectangle either way.
|
|
68
|
+
"""
|
|
69
|
+
out: list[str] = []
|
|
70
|
+
for line in source.splitlines():
|
|
71
|
+
for pattern, repl in _FLOWCHART_SHAPE_SUBS:
|
|
72
|
+
line = pattern.sub(repl, line)
|
|
73
|
+
out.append(line)
|
|
74
|
+
return "\n".join(out)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def preprocess_mermaid_for_ascii(source: str) -> str:
|
|
78
|
+
"""Rewrite a mermaid diagram into the subset mermaid-ascii supports.
|
|
79
|
+
|
|
80
|
+
For sequence diagrams: convert ``Note`` lines into self-loops, map the
|
|
81
|
+
arrow variants (``->``, ``-x``, ``--x``, ``-)``, ``--)``, ``-->``) to the
|
|
82
|
+
supported ``->>``/``-->>`` pair, drop block keywords (``loop``, ``alt``,
|
|
83
|
+
``activate``…), and flatten ``<br/>`` tags. For flowcharts (``graph`` /
|
|
84
|
+
``flowchart``): normalize alternate node shapes to ``[text]`` rectangles.
|
|
85
|
+
Other diagram types are returned unchanged.
|
|
86
|
+
"""
|
|
87
|
+
lines = source.splitlines()
|
|
88
|
+
first = next((l.strip() for l in lines if l.strip()), "")
|
|
89
|
+
first_lower = first.lower()
|
|
90
|
+
if not first_lower.startswith("sequencediagram"):
|
|
91
|
+
if first_lower.startswith(("graph", "flowchart")):
|
|
92
|
+
return normalize_flowchart_shapes(source)
|
|
93
|
+
return source
|
|
94
|
+
|
|
95
|
+
out: list[str] = []
|
|
96
|
+
for line in lines:
|
|
97
|
+
m = _NOTE_RE.match(line)
|
|
98
|
+
if m:
|
|
99
|
+
indent, parts, msg = m.group(1), m.group(2), m.group(3)
|
|
100
|
+
first_p = parts.split(",")[0].strip()
|
|
101
|
+
msg = _BR_RE.sub(" / ", msg)
|
|
102
|
+
out.append(f"{indent}{first_p}->>{first_p}: 📝 {msg}")
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
if _UNSUPPORTED_BLOCK_RE.match(line):
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
line = _BR_RE.sub(" / ", line)
|
|
109
|
+
line = re.sub(r"--x(?=\s|\w|\()", "-->>", line)
|
|
110
|
+
line = re.sub(r"-x(?=\s|\w|\()", "->>", line)
|
|
111
|
+
line = re.sub(r"--\)(?=\s|\w|\()", "-->>", line)
|
|
112
|
+
line = re.sub(r"-\)(?=\s|\w|\()", "->>", line)
|
|
113
|
+
line = re.sub(r"-->(?!>)", "-->>", line)
|
|
114
|
+
line = re.sub(r"(?<!-)->(?!>)", "->>", line)
|
|
115
|
+
out.append(line)
|
|
116
|
+
return "\n".join(out)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def render(block: Block, color: bool) -> list[str]:
|
|
120
|
+
"""Render a mermaid diagram from pre-rendered or on-the-fly ASCII output."""
|
|
121
|
+
w = block.width
|
|
122
|
+
rendered = block.attrs.get("_rendered")
|
|
123
|
+
|
|
124
|
+
if rendered is None:
|
|
125
|
+
source = block.attrs.get("source", "")
|
|
126
|
+
try:
|
|
127
|
+
result = subprocess.run(
|
|
128
|
+
[mermaid_ascii_bin(), "-f", "-", "-y", "1"],
|
|
129
|
+
input=preprocess_mermaid_for_ascii(source),
|
|
130
|
+
capture_output=True,
|
|
131
|
+
text=True,
|
|
132
|
+
timeout=30,
|
|
133
|
+
)
|
|
134
|
+
rendered = fix_mermaid_encoding(result.stdout)
|
|
135
|
+
except Exception:
|
|
136
|
+
rendered = source
|
|
137
|
+
|
|
138
|
+
lines: list[str] = []
|
|
139
|
+
for raw_line in rendered.split("\n"):
|
|
140
|
+
lines.append(visual_ljust(raw_line, w))
|
|
141
|
+
|
|
142
|
+
return lines
|
|
@@ -5,10 +5,46 @@ from termrender.renderers.mermaid import preprocess_mermaid_for_ascii
|
|
|
5
5
|
|
|
6
6
|
class TestMermaidPreprocessor(unittest.TestCase):
|
|
7
7
|
|
|
8
|
-
def
|
|
8
|
+
def test_unknown_diagram_types_pass_through(self):
|
|
9
|
+
src = "pie\n \"A\" : 40\n \"B\" : 60"
|
|
10
|
+
self.assertEqual(preprocess_mermaid_for_ascii(src), src)
|
|
11
|
+
|
|
12
|
+
def test_flowchart_plain_edges_pass_through(self):
|
|
9
13
|
src = "flowchart TD\n A-->B\n B-->C"
|
|
10
14
|
self.assertEqual(preprocess_mermaid_for_ascii(src), src)
|
|
11
15
|
|
|
16
|
+
# Regression: mermaid-ascii (pinned master 6fffb8e) only parses ``[text]``
|
|
17
|
+
# rectangle nodes — every other mermaid node shape leaked its raw delimiters
|
|
18
|
+
# or bare node id into the rendered box (``B{Auth?}`` rendered the literal
|
|
19
|
+
# ``B{Auth?}``; ``E[(Database)]`` rendered ``(Database)``). Node shapes are
|
|
20
|
+
# still unsupported upstream (parseNode only handles rectangles), so we
|
|
21
|
+
# normalize every shape to ``id[label]`` on our side.
|
|
22
|
+
def test_flowchart_node_shapes_normalized_to_rectangles(self):
|
|
23
|
+
src = (
|
|
24
|
+
"graph LR\n"
|
|
25
|
+
" A[Client] --> B{Auth?}\n"
|
|
26
|
+
" B -->|Yes| C[Handler]\n"
|
|
27
|
+
" C --> E[(Database)]\n"
|
|
28
|
+
" F((Circle)) --> G([Stadium])\n"
|
|
29
|
+
" G --> H{{Hex}}\n"
|
|
30
|
+
" H --> I[[Sub]]\n"
|
|
31
|
+
" I --> J[/Para/]"
|
|
32
|
+
)
|
|
33
|
+
out = preprocess_mermaid_for_ascii(src)
|
|
34
|
+
self.assertIn("B[Auth?]", out)
|
|
35
|
+
self.assertIn("E[Database]", out)
|
|
36
|
+
self.assertIn("F[Circle]", out)
|
|
37
|
+
self.assertIn("G[Stadium]", out)
|
|
38
|
+
self.assertIn("H[Hex]", out)
|
|
39
|
+
self.assertIn("I[Sub]", out)
|
|
40
|
+
self.assertIn("J[Para]", out)
|
|
41
|
+
# Rectangle nodes and edge labels are untouched.
|
|
42
|
+
self.assertIn("A[Client]", out)
|
|
43
|
+
self.assertIn("|Yes|", out)
|
|
44
|
+
# No raw alternate delimiters survive.
|
|
45
|
+
for leak in ("{Auth?}", "[(", "((", "))", "{{", "}}", "[[", "]]"):
|
|
46
|
+
self.assertNotIn(leak, out)
|
|
47
|
+
|
|
12
48
|
def test_note_over_becomes_self_loop(self):
|
|
13
49
|
src = "sequenceDiagram\n participant A\n participant B\n Note over A: hello"
|
|
14
50
|
out = preprocess_mermaid_for_ascii(src)
|
|
@@ -7,7 +7,7 @@ from termrender.parser import parse, _strip_options
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class TestMermaidDirective(unittest.TestCase):
|
|
10
|
-
""":::mermaid
|
|
10
|
+
""":::mermaid and ```mermaid fences both produce mermaid diagrams."""
|
|
11
11
|
|
|
12
12
|
def test_basic_mermaid_directive(self):
|
|
13
13
|
""":::mermaid\ngraph LR\nA-->B\n::: → BlockType.MERMAID"""
|
|
@@ -26,12 +26,12 @@ class TestMermaidDirective(unittest.TestCase):
|
|
|
26
26
|
self.assertIn("graph LR", block.attrs["source"])
|
|
27
27
|
self.assertNotIn(":title:", block.attrs["source"])
|
|
28
28
|
|
|
29
|
-
def
|
|
30
|
-
"""```mermaid
|
|
29
|
+
def test_backtick_mermaid_is_mermaid_block(self):
|
|
30
|
+
"""```mermaid fences route through the mermaid renderer, same as :::mermaid."""
|
|
31
31
|
doc = parse("```mermaid\ngraph LR\nA-->B\n```")
|
|
32
32
|
block = doc.children[0]
|
|
33
|
-
self.assertEqual(block.type, BlockType.
|
|
34
|
-
self.
|
|
33
|
+
self.assertEqual(block.type, BlockType.MERMAID)
|
|
34
|
+
self.assertIn("graph LR", block.attrs["source"])
|
|
35
35
|
|
|
36
36
|
def test_backtick_directive_is_plain_code_block(self):
|
|
37
37
|
"""```{panel} is no longer a directive — it renders as a code block."""
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
- **QUOTE** height gets `+1` only when `author` or `by` attr is set. Using any other key (`attribution`, `source`) silently omits the extra line — the renderer's attribution line is clipped.
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
"""Mermaid diagram renderer for termrender."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import re
|
|
6
|
-
import subprocess
|
|
7
|
-
|
|
8
|
-
from termrender._mermaid_bin import mermaid_ascii_bin
|
|
9
|
-
from termrender.blocks import Block
|
|
10
|
-
from termrender.style import visual_ljust
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def fix_mermaid_encoding(text: str) -> str:
|
|
14
|
-
"""Undo mermaid-ascii's double-encoding of UTF-8 characters.
|
|
15
|
-
|
|
16
|
-
mermaid-ascii misinterprets UTF-8 input bytes as Latin-1 and re-encodes
|
|
17
|
-
to UTF-8, corrupting multi-byte characters (e.g. → becomes â\\x86\\x92).
|
|
18
|
-
Reversing the process: encode back to Latin-1 to recover the original
|
|
19
|
-
UTF-8 bytes, then decode as UTF-8.
|
|
20
|
-
"""
|
|
21
|
-
try:
|
|
22
|
-
return text.encode("latin-1").decode("utf-8")
|
|
23
|
-
except (UnicodeDecodeError, UnicodeEncodeError):
|
|
24
|
-
return text
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
_NOTE_RE = re.compile(
|
|
28
|
-
r"^(\s*)[Nn]ote\s+(?:over|left\s+of|right\s+of)\s+([^:]+?)\s*:\s*(.*)$"
|
|
29
|
-
)
|
|
30
|
-
_BR_RE = re.compile(r"<br\s*/?>", re.IGNORECASE)
|
|
31
|
-
_UNSUPPORTED_BLOCK_RE = re.compile(
|
|
32
|
-
r"^\s*(?:loop|alt|else|opt|par|and|critical|option|break|rect|"
|
|
33
|
-
r"activate|deactivate|autonumber|end)\b.*$",
|
|
34
|
-
re.IGNORECASE,
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def preprocess_mermaid_for_ascii(source: str) -> str:
|
|
39
|
-
"""Rewrite mermaid sequence diagrams into the subset mermaid-ascii supports.
|
|
40
|
-
|
|
41
|
-
mermaid-ascii only understands ``->>`` and ``-->>`` arrows plus ``participant``
|
|
42
|
-
declarations. This helper converts ``Note`` lines into self-loops, maps the
|
|
43
|
-
other arrow variants (``->``, ``-x``, ``--x``, ``-)``, ``--)``, ``-->``) to
|
|
44
|
-
the supported pair, drops block keywords (``loop``, ``alt``, ``activate``…),
|
|
45
|
-
and flattens ``<br/>`` tags. Non-sequence diagrams are returned unchanged.
|
|
46
|
-
"""
|
|
47
|
-
lines = source.splitlines()
|
|
48
|
-
first = next((l.strip() for l in lines if l.strip()), "")
|
|
49
|
-
if not first.lower().startswith("sequencediagram"):
|
|
50
|
-
return source
|
|
51
|
-
|
|
52
|
-
out: list[str] = []
|
|
53
|
-
for line in lines:
|
|
54
|
-
m = _NOTE_RE.match(line)
|
|
55
|
-
if m:
|
|
56
|
-
indent, parts, msg = m.group(1), m.group(2), m.group(3)
|
|
57
|
-
first_p = parts.split(",")[0].strip()
|
|
58
|
-
msg = _BR_RE.sub(" / ", msg)
|
|
59
|
-
out.append(f"{indent}{first_p}->>{first_p}: 📝 {msg}")
|
|
60
|
-
continue
|
|
61
|
-
|
|
62
|
-
if _UNSUPPORTED_BLOCK_RE.match(line):
|
|
63
|
-
continue
|
|
64
|
-
|
|
65
|
-
line = _BR_RE.sub(" / ", line)
|
|
66
|
-
line = re.sub(r"--x(?=\s|\w|\()", "-->>", line)
|
|
67
|
-
line = re.sub(r"-x(?=\s|\w|\()", "->>", line)
|
|
68
|
-
line = re.sub(r"--\)(?=\s|\w|\()", "-->>", line)
|
|
69
|
-
line = re.sub(r"-\)(?=\s|\w|\()", "->>", line)
|
|
70
|
-
line = re.sub(r"-->(?!>)", "-->>", line)
|
|
71
|
-
line = re.sub(r"(?<!-)->(?!>)", "->>", line)
|
|
72
|
-
out.append(line)
|
|
73
|
-
return "\n".join(out)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def render(block: Block, color: bool) -> list[str]:
|
|
77
|
-
"""Render a mermaid diagram from pre-rendered or on-the-fly ASCII output."""
|
|
78
|
-
w = block.width
|
|
79
|
-
rendered = block.attrs.get("_rendered")
|
|
80
|
-
|
|
81
|
-
if rendered is None:
|
|
82
|
-
source = block.attrs.get("source", "")
|
|
83
|
-
try:
|
|
84
|
-
result = subprocess.run(
|
|
85
|
-
[mermaid_ascii_bin(), "-f", "-", "-y", "1"],
|
|
86
|
-
input=preprocess_mermaid_for_ascii(source),
|
|
87
|
-
capture_output=True,
|
|
88
|
-
text=True,
|
|
89
|
-
timeout=30,
|
|
90
|
-
)
|
|
91
|
-
rendered = fix_mermaid_encoding(result.stdout)
|
|
92
|
-
except Exception:
|
|
93
|
-
rendered = source
|
|
94
|
-
|
|
95
|
-
lines: list[str] = []
|
|
96
|
-
for raw_line in rendered.split("\n"):
|
|
97
|
-
lines.append(visual_ljust(raw_line, w))
|
|
98
|
-
|
|
99
|
-
return lines
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|