termrender 4.9.1__tar.gz → 4.10.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-4.9.1 → termrender-4.10.0}/CHANGELOG.md +32 -0
- {termrender-4.9.1 → termrender-4.10.0}/PKG-INFO +4 -5
- {termrender-4.9.1 → termrender-4.10.0}/README.md +3 -3
- {termrender-4.9.1 → termrender-4.10.0}/pyproject.toml +0 -6
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/CLAUDE.md +3 -9
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/borders.py +4 -3
- termrender-4.10.0/src/termrender/renderers/mermaid.py +117 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_mermaid_dispatch.py +116 -58
- termrender-4.10.0/uv.lock +70 -0
- termrender-4.9.1/scripts/build-mermaid-ascii.sh +0 -36
- termrender-4.9.1/scripts/mermaid_flow_parity.py +0 -168
- termrender-4.9.1/src/termrender/_bin/mermaid-ascii-darwin-arm64 +0 -0
- termrender-4.9.1/src/termrender/_mermaid_bin.py +0 -41
- termrender-4.9.1/src/termrender/renderers/mermaid.py +0 -214
- termrender-4.9.1/tests/test_mermaid_compat.py +0 -151
- termrender-4.9.1/uv.lock +0 -94
- {termrender-4.9.1 → termrender-4.10.0}/.github/workflows/publish.yml +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/.gitignore +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/CLAUDE.md +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/LICENSE +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/design.json +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/requirements.json +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/CLAUDE.md +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/__init__.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/__main__.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/blocks.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/emit.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/layout.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/parser.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/py.typed +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/__init__.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/charts.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/code.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/columns.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/diff.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/divider.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/mermaid_class.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/mermaid_er.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/mermaid_flow.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/mermaid_flow_layout.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/mermaid_flow_model.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/mermaid_flow_parser.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/mermaid_gantt.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/mermaid_journey.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/mermaid_mindmap.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/mermaid_pie.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/mermaid_prelude.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/mermaid_sequence.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/mermaid_state.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/mermaid_timeline.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/panel.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/quote.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/stat.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/table.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/text.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/timeline.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/renderers/tree.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/src/termrender/style.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/__init__.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_charts.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_cli_contract.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_column_alignment.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_diff.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_inline_badge.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_linebreak.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_mermaid_class.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_mermaid_er.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_mermaid_flow.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_mermaid_flow_corpus.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_mermaid_flow_layout.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_mermaid_flow_model.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_mermaid_flow_parser.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_mermaid_flow_shapes.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_mermaid_gantt.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_mermaid_journey.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_mermaid_mindmap.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_mermaid_pie.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_mermaid_sequence.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_mermaid_state.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_mermaid_timeline.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_myst_gaps.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_stat.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_tasklist.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_timeline.py +0 -0
- {termrender-4.9.1 → termrender-4.10.0}/tests/test_variable_colons.py +0 -0
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v4.10.0 (2026-07-07)
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
- **mermaid**: Native dispatch for flowchart/class/state/er, drop vendored Go binary
|
|
9
|
+
([`6935f30`](https://github.com/crouton-labs/termrender/commit/6935f30a1dccfefcc34344de02191f8481d53668))
|
|
10
|
+
|
|
11
|
+
Wire the four previously-unwired native renderers into mermaid.py's first-line dispatcher:
|
|
12
|
+
flowchart/graph -> mermaid_flow.render_flowchart, classDiagram -> mermaid_class.render_class,
|
|
13
|
+
stateDiagram/-v2 -> mermaid_state.render_state, erDiagram -> mermaid_er.render_er. Every mermaid
|
|
14
|
+
type termrender renders is now native Python; no diagram type shells out anymore.
|
|
15
|
+
|
|
16
|
+
Unrecognized/exotic types (sankey, C4, gitgraph, block, packet, kanban, quadrantChart, ...) degrade
|
|
17
|
+
in the dispatcher itself to a raw echo of the source with box-drawing/geometric glyphs stripped,
|
|
18
|
+
preserving the contract the crouter attach viewer relies on to detect render failure by glyph
|
|
19
|
+
absence.
|
|
20
|
+
|
|
21
|
+
Delete the vendored Go path entirely: src/termrender/_bin/ (binary), src/termrender/_mermaid_bin.py,
|
|
22
|
+
scripts/build-mermaid-ascii.sh, the mermaid-ascii runtime dependency and hatch artifacts stanza in
|
|
23
|
+
pyproject.toml (relocked via uv.lock), and scripts/mermaid_flow_parity.py (a standalone dev-only
|
|
24
|
+
harness comparing native output against the now-deleted binary).
|
|
25
|
+
|
|
26
|
+
tests/test_mermaid_compat.py pinned Go-era preprocessing (preprocess_mermaid_for_ascii) that no
|
|
27
|
+
longer exists; deleted wholesale since dispatch/prelude contracts it also touched already live in
|
|
28
|
+
test_mermaid_dispatch.py. That file's routing tests are updated for the four newly-wired types and
|
|
29
|
+
gain dispatch tests for the exotic raw-echo path (including a no-native-renderer-called assertion
|
|
30
|
+
and a glyph-range sweep pinning the no-box-glyph contract).
|
|
31
|
+
|
|
32
|
+
Swept src/termrender/renderers/CLAUDE.md and README.md to current-state only; no remaining
|
|
33
|
+
references to mermaid-ascii/_mermaid_bin/the Go binary anywhere in the tree.
|
|
34
|
+
|
|
35
|
+
|
|
4
36
|
## v4.9.1 (2026-07-07)
|
|
5
37
|
|
|
6
38
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: termrender
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.10.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
|
|
@@ -22,7 +22,6 @@ Classifier: Topic :: Terminals
|
|
|
22
22
|
Classifier: Topic :: Text Processing :: Markup :: Markdown
|
|
23
23
|
Requires-Python: >=3.10
|
|
24
24
|
Requires-Dist: grandalf>=0.8
|
|
25
|
-
Requires-Dist: mermaid-ascii>=1.2
|
|
26
25
|
Requires-Dist: mistune>=3.0
|
|
27
26
|
Requires-Dist: pygments>=2.0
|
|
28
27
|
Description-Content-Type: text/markdown
|
|
@@ -163,7 +162,7 @@ That entire output — the nested borders, the tree guide lines, the side-by-sid
|
|
|
163
162
|
pip install termrender
|
|
164
163
|
```
|
|
165
164
|
|
|
166
|
-
Python 3.10+. Three dependencies: [mistune](https://github.com/lepture/mistune) (markdown parsing), [pygments](https://pygments.org/) (syntax highlighting), [
|
|
165
|
+
Python 3.10+. Three dependencies: [mistune](https://github.com/lepture/mistune) (markdown parsing), [pygments](https://pygments.org/) (syntax highlighting), [grandalf](https://github.com/bdcht/grandalf) (mermaid diagram layout).
|
|
167
166
|
|
|
168
167
|
|
|
169
168
|
## Usage
|
|
@@ -409,7 +408,7 @@ Horizontal rules with optional centered labels.
|
|
|
409
408
|
|
|
410
409
|
### Mermaid diagrams
|
|
411
410
|
|
|
412
|
-
Renders mermaid flowcharts as ASCII art
|
|
411
|
+
Renders mermaid diagrams (flowcharts, class/state/ER diagrams, pie/gantt/sequence/mindmap/journey/timeline charts) natively as ASCII/unicode art. Use the `:::mermaid` directive, or a standard GFM ` ```mermaid ` fenced code block — both render the same diagram.
|
|
413
412
|
|
|
414
413
|
**Input:**
|
|
415
414
|
```markdown
|
|
@@ -421,7 +420,7 @@ graph LR
|
|
|
421
420
|
:::
|
|
422
421
|
```
|
|
423
422
|
|
|
424
|
-
The diagram gets rendered as ASCII art inline in the terminal output
|
|
423
|
+
The diagram gets rendered as ASCII art inline in the terminal output, no external binary required.
|
|
425
424
|
|
|
426
425
|
|
|
427
426
|
### Nesting
|
|
@@ -134,7 +134,7 @@ That entire output — the nested borders, the tree guide lines, the side-by-sid
|
|
|
134
134
|
pip install termrender
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
-
Python 3.10+. Three dependencies: [mistune](https://github.com/lepture/mistune) (markdown parsing), [pygments](https://pygments.org/) (syntax highlighting), [
|
|
137
|
+
Python 3.10+. Three dependencies: [mistune](https://github.com/lepture/mistune) (markdown parsing), [pygments](https://pygments.org/) (syntax highlighting), [grandalf](https://github.com/bdcht/grandalf) (mermaid diagram layout).
|
|
138
138
|
|
|
139
139
|
|
|
140
140
|
## Usage
|
|
@@ -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
|
|
383
|
+
Renders mermaid diagrams (flowcharts, class/state/ER diagrams, pie/gantt/sequence/mindmap/journey/timeline charts) natively as ASCII/unicode art. 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
|
|
@@ -392,7 +392,7 @@ graph LR
|
|
|
392
392
|
:::
|
|
393
393
|
```
|
|
394
394
|
|
|
395
|
-
The diagram gets rendered as ASCII art inline in the terminal output
|
|
395
|
+
The diagram gets rendered as ASCII art inline in the terminal output, no external binary required.
|
|
396
396
|
|
|
397
397
|
|
|
398
398
|
### Nesting
|
|
@@ -28,7 +28,6 @@ classifiers = [
|
|
|
28
28
|
]
|
|
29
29
|
dependencies = [
|
|
30
30
|
"mistune>=3.0",
|
|
31
|
-
"mermaid-ascii>=1.2",
|
|
32
31
|
"pygments>=2.0",
|
|
33
32
|
"grandalf>=0.8",
|
|
34
33
|
]
|
|
@@ -46,11 +45,6 @@ source = "vcs"
|
|
|
46
45
|
|
|
47
46
|
[tool.hatch.build.targets.wheel]
|
|
48
47
|
packages = ["src/termrender"]
|
|
49
|
-
# Ship the vendored mermaid-ascii binary (built from pinned upstream master;
|
|
50
|
-
# see scripts/build-mermaid-ascii.sh). It lives inside the package dir so the
|
|
51
|
-
# default file selection includes it; artifacts ensures it survives even if
|
|
52
|
-
# VCS-ignored, and hatchling preserves its 0o755 exec bit.
|
|
53
|
-
artifacts = ["src/termrender/_bin/*"]
|
|
54
48
|
|
|
55
49
|
[tool.semantic_release]
|
|
56
50
|
version_toml = []
|
|
@@ -43,20 +43,14 @@ Attribution line is rendered for `block.attrs["author"]` **or** `block.attrs["by
|
|
|
43
43
|
|
|
44
44
|
`mermaid.py`'s `render(block, color)` (leaf signature, no `render_child` — the parent `CLAUDE.md` incorrectly lists mermaid as a Container renderer) is a thin wrapper: it reads `block.attrs["_rendered"]` if layout already precomputed it, else calls `render_mermaid_lines(source, width)`, then pads every line to `block.width` with `visual_ljust`. All the type-routing logic lives in `render_mermaid_lines`, shared with `layout.py`'s height pass (see below) so there is exactly one dispatch path, not two.
|
|
45
45
|
|
|
46
|
-
**Dispatch is by first real line, case-insensitive** (`_first_line_type`), after skipping any prelude via `mermaid_prelude.strip_prelude_lines` (see below): `pie`, `gantt`, `sequenceDiagram`, `mindmap`, `journey`, and `
|
|
46
|
+
**Dispatch is by first real line, case-insensitive** (`_first_line_type`), after skipping any prelude via `mermaid_prelude.strip_prelude_lines` (see below): `pie`, `gantt`, `sequenceDiagram`, `mindmap`, `journey`, `timeline`, `graph`/`flowchart`, `classDiagram`, `stateDiagram`/`stateDiagram-v2`, and `erDiagram` all route to dedicated native Python renderers (`mermaid_pie.py`, `mermaid_gantt.py`, `mermaid_sequence.py`, `mermaid_mindmap.py`, `mermaid_journey.py`, `mermaid_timeline.py`, `mermaid_flow.py::render_flowchart`, `mermaid_class.py::render_class`, `mermaid_state.py::render_state`, `mermaid_er.py::render_er`). Every other type (sankey, C4, gitgraph, block, packet, kanban, quadrantChart, …, and anything unrecognized) has no renderer at all and degrades in the dispatcher itself, via `_raw_echo`: the source lines verbatim, `.rstrip()`-ed, with any box-drawing/geometric glyph (`\u2500-\u259f`/`\u25a0-\u25ff`) replaced by `?`. **Do not add a bordered fallback panel for the exotic path** — crouter's attach viewer detects render failure by the *absence* of box-drawing glyphs in the output, and a decorative border would defeat that; the four flowchart-model-backed native renderers (flow/class/state/ER) uphold the identical no-glyph guarantee on their own internal degradation paths (malformed input, wrong header, zero nodes/classes/states/entities parsed, or an unexpected exception escaping layout) — see each module's own docstring.
|
|
47
47
|
|
|
48
|
-
**`mermaid_prelude.py`** — a tiny shared module (no dependency on `Block` or the rest of the pipeline) implementing `strip_prelude_lines(lines)`, used by
|
|
48
|
+
**`mermaid_prelude.py`** — a tiny shared module (no dependency on `Block` or the rest of the pipeline) implementing `strip_prelude_lines(lines)`, used by `mermaid.py`'s dispatch sniff and by every native renderer's own header check (`mermaid_sequence.py`, `mermaid_flow_parser.py`, `mermaid_class.py`, `mermaid_state.py`, `mermaid_er.py`). It skips leading blank lines, `%%` line comments, `%%{init: ...}%%` directives, and `---`-delimited YAML frontmatter, permissively: an unterminated frontmatter block or directive is left alone rather than eating real content. Prelude stripping is for *sniffing/validating the type line only* — most per-type body parsers receive the full untouched source and do their own prelude-aware header check internally (flow/class/state/ER/sequence); `mindmap`/`journey`/`timeline` are the exception, called with `mermaid.py::_stripped(source)` since those parsers don't skip prelude themselves. `mermaid_pie.py`/`mermaid_gantt.py` just skip lines that don't match their grammar — a frontmatter `title: X` line could in principle collide with a `title` grammar rule, but this is accepted as the same class of pragmatic-parsing tradeoff as any other unrecognized line.
|
|
49
49
|
|
|
50
|
-
**`
|
|
51
|
-
|
|
52
|
-
**`visual_ljust` pads but never truncates**: every output line (from any type — binary or native) is padded to `block.width` in the outer `render()`. Lines wider than `block.width` overflow without clipping. `mermaid-ascii` has no width-control flag (`--maxWidth`/`-w` do not exist — the call passes only `-f - -y 1`), so wide flowchart output silently exceeds the layout boundary; the native renderers respect `width` by construction (pie/gantt/timeline size their own bars to it; sequence/mindmap/journey have intrinsic width and may overflow, matching the binary path's contract) — see each renderer's own note below.
|
|
50
|
+
**`visual_ljust` pads but never truncates**: every output line (from any type) is padded to `block.width` in the outer `render()`. Lines wider than `block.width` overflow without clipping — pie/gantt/timeline size their own bars to `width`; sequence/mindmap/journey and the four flowchart-model-backed renderers (flow/class/state/ER) have intrinsic content-driven width and may overflow, `width` there is advisory only.
|
|
53
51
|
|
|
54
52
|
Layout (`layout.py::resolve_height`, `BlockType.MERMAID` branch) precomputes `block.attrs["_rendered"]` by calling the same `render_mermaid_lines`, so `mermaid.py`'s `render()` only recomputes when that key is absent (e.g. calling `render()` on a block that skipped the layout pass).
|
|
55
53
|
|
|
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
|
-
|
|
58
|
-
**Binary resolution**: 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).
|
|
59
|
-
|
|
60
54
|
## `mermaid_pie.py` — re-expressed as a bar chart, not a text circle
|
|
61
55
|
|
|
62
56
|
A text pie chart is worse than a labeled bar chart at the same information density (per the ratified plan), so `parse_pie` extracts `(title, items)` from the `pie [showData]` / `title …` / `"label" : value` grammar and `render` hands the items to `charts.py::render_bar` via a synthetic `Block(type=BlockType.BAR, ...)` — no duplicated bar-drawing logic. Each item's `unit` attr is set to the pre-formatted percentage string (e.g. `" (40.0%)"`) so `render_bar`'s existing value-formatting appends it for free. Called with `color=False` always — mermaid output is monochrome by design (the attach viewer relies on `--color on == --color off`). Unparseable/empty input degrades to the raw source lines, same contract as the binary path.
|
|
@@ -31,9 +31,10 @@ def render_box(
|
|
|
31
31
|
corner_v = visual_len("┌") # same as ┐, └, ┘
|
|
32
32
|
|
|
33
33
|
# Grow the box if any content line (or the title) won't fit at the
|
|
34
|
-
# requested width. mermaid
|
|
35
|
-
#
|
|
36
|
-
#
|
|
34
|
+
# requested width. Several mermaid renderers size to content rather than
|
|
35
|
+
# to width (flowchart/class/state/ER/sequence/mindmap/journey), so a
|
|
36
|
+
# child mermaid block can return lines wider than its allocated content
|
|
37
|
+
# area. Truncating would corrupt the diagram; growing keeps the box's top,
|
|
37
38
|
# bottom, and side borders aligned at the same column even if it
|
|
38
39
|
# overflows the parent's allocation.
|
|
39
40
|
content_max = visual_len("")
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Mermaid diagram renderer for termrender."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from termrender.blocks import Block
|
|
8
|
+
from termrender.renderers import (
|
|
9
|
+
mermaid_class,
|
|
10
|
+
mermaid_er,
|
|
11
|
+
mermaid_flow,
|
|
12
|
+
mermaid_gantt,
|
|
13
|
+
mermaid_journey,
|
|
14
|
+
mermaid_mindmap,
|
|
15
|
+
mermaid_pie,
|
|
16
|
+
mermaid_sequence,
|
|
17
|
+
mermaid_state,
|
|
18
|
+
mermaid_timeline,
|
|
19
|
+
)
|
|
20
|
+
from termrender.renderers.mermaid_prelude import strip_prelude_lines
|
|
21
|
+
from termrender.style import visual_ljust
|
|
22
|
+
|
|
23
|
+
# Same box-drawing/geometric glyph ranges the native renderers (mermaid_flow,
|
|
24
|
+
# mermaid_class, mermaid_state, mermaid_er) guard their own raw-echo paths
|
|
25
|
+
# with, and the same ranges the downstream crouter viewer keys on to detect
|
|
26
|
+
# render *success*. An exotic/unrecognized diagram type has no renderer at
|
|
27
|
+
# all, so its degradation lives here rather than in a shared renderer module;
|
|
28
|
+
# it must uphold the identical contract — no such glyph survives the echo,
|
|
29
|
+
# even one present verbatim in the source itself.
|
|
30
|
+
_GLYPH_RANGE_RE = re.compile("[\u2500-\u259f\u25a0-\u25ff]")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _first_line_type(source: str) -> str:
|
|
34
|
+
"""Return the lowercased first real line's leading keyword.
|
|
35
|
+
|
|
36
|
+
This is the dispatch key: mermaid identifies a diagram's type from its
|
|
37
|
+
first line (``graph``/``flowchart``, ``pie``, ``gantt``, ``sequenceDiagram``,
|
|
38
|
+
``classDiagram``, ``stateDiagram``, ``erDiagram``, …), after skipping any
|
|
39
|
+
prelude (blank lines, ``%%`` comments/directives, ``---`` YAML
|
|
40
|
+
frontmatter — see ``mermaid_prelude.py``). Returns ``""`` for a source
|
|
41
|
+
with no real content past its prelude.
|
|
42
|
+
"""
|
|
43
|
+
lines = strip_prelude_lines(source.splitlines())
|
|
44
|
+
first = next((l.strip() for l in lines if l.strip()), "")
|
|
45
|
+
return first.lower()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _stripped(source: str) -> str:
|
|
49
|
+
"""Return ``source`` with its leading prelude (comments/directives/
|
|
50
|
+
frontmatter) removed, for native renderers that don't otherwise skip it.
|
|
51
|
+
"""
|
|
52
|
+
return "\n".join(strip_prelude_lines(source.splitlines()))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _raw_echo(source: str) -> list[str]:
|
|
56
|
+
"""Degrade an exotic/unrecognized diagram type to its raw source lines.
|
|
57
|
+
|
|
58
|
+
No renderer exists for these types (sankey, C4, gitgraph, block, packet,
|
|
59
|
+
kanban, quadrantChart, …, and anything unrecognized): the source is
|
|
60
|
+
echoed back verbatim, with box-drawing/geometric glyphs neutralized so
|
|
61
|
+
the crouter viewer's "no glyphs survived" success check can't be fooled
|
|
62
|
+
by one present in the raw source itself — the same guarantee every
|
|
63
|
+
native renderer's own raw-echo path upholds.
|
|
64
|
+
"""
|
|
65
|
+
return [
|
|
66
|
+
_GLYPH_RANGE_RE.sub("?", line.rstrip()) for line in source.splitlines()
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def render_mermaid_lines(source: str, width: int) -> list[str]:
|
|
71
|
+
"""Dispatch a mermaid source to its type's renderer and return raw lines.
|
|
72
|
+
|
|
73
|
+
Every diagram type mermaid supports that termrender renders has a
|
|
74
|
+
dedicated native Python renderer: ``pie``, ``gantt``, ``sequenceDiagram``,
|
|
75
|
+
``mindmap``, ``journey``, ``timeline``, ``graph``/``flowchart``,
|
|
76
|
+
``classDiagram``, ``stateDiagram``/``stateDiagram-v2``, and ``erDiagram``.
|
|
77
|
+
Any other type (sankey, C4, gitgraph, block, packet, kanban, …, or
|
|
78
|
+
anything unrecognized) degrades to a raw echo of the source with no
|
|
79
|
+
box-drawing glyphs — see :func:`_raw_echo`. Lines are returned unpadded;
|
|
80
|
+
callers apply width padding uniformly.
|
|
81
|
+
"""
|
|
82
|
+
diagram_type = _first_line_type(source)
|
|
83
|
+
if diagram_type.startswith("pie"):
|
|
84
|
+
return mermaid_pie.render(source, width)
|
|
85
|
+
if diagram_type.startswith("gantt"):
|
|
86
|
+
return mermaid_gantt.render(source, width)
|
|
87
|
+
if diagram_type.startswith("sequencediagram"):
|
|
88
|
+
return mermaid_sequence.render_sequence(source, width)
|
|
89
|
+
if diagram_type.startswith("mindmap"):
|
|
90
|
+
return mermaid_mindmap.render(_stripped(source), width)
|
|
91
|
+
if diagram_type.startswith("journey"):
|
|
92
|
+
return mermaid_journey.render(_stripped(source), width)
|
|
93
|
+
if diagram_type.startswith("timeline"):
|
|
94
|
+
return mermaid_timeline.render(_stripped(source), width)
|
|
95
|
+
if diagram_type.startswith(("graph", "flowchart")):
|
|
96
|
+
return mermaid_flow.render_flowchart(source, width)
|
|
97
|
+
if diagram_type.startswith("classdiagram"):
|
|
98
|
+
return mermaid_class.render_class(source, width)
|
|
99
|
+
if diagram_type.startswith("statediagram"):
|
|
100
|
+
return mermaid_state.render_state(source, width)
|
|
101
|
+
if diagram_type.startswith("erdiagram"):
|
|
102
|
+
return mermaid_er.render_er(source, width)
|
|
103
|
+
return _raw_echo(source)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def render(block: Block, color: bool) -> list[str]:
|
|
107
|
+
"""Render a mermaid diagram from pre-rendered or on-the-fly ASCII output."""
|
|
108
|
+
w = block.width
|
|
109
|
+
rendered = block.attrs.get("_rendered")
|
|
110
|
+
|
|
111
|
+
if rendered is None:
|
|
112
|
+
source = block.attrs.get("source", "")
|
|
113
|
+
raw_lines = render_mermaid_lines(source, w or 60)
|
|
114
|
+
else:
|
|
115
|
+
raw_lines = rendered.split("\n")
|
|
116
|
+
|
|
117
|
+
return [visual_ljust(raw_line, w) for raw_line in raw_lines]
|
|
@@ -7,143 +7,201 @@ from termrender.renderers import mermaid
|
|
|
7
7
|
class TestMermaidDispatch(unittest.TestCase):
|
|
8
8
|
"""Routing tests for mermaid.py's first-line type dispatcher.
|
|
9
9
|
|
|
10
|
-
These mock the per-type renderers
|
|
11
|
-
|
|
10
|
+
These mock the per-type renderers so they verify *routing* only,
|
|
11
|
+
independent of the actual rendering logic (covered by
|
|
12
12
|
test_mermaid_pie.py, test_mermaid_gantt.py, test_mermaid_sequence.py,
|
|
13
13
|
test_mermaid_mindmap.py, test_mermaid_journey.py, test_mermaid_timeline.py,
|
|
14
|
-
|
|
14
|
+
test_mermaid_flow.py, test_mermaid_class.py, test_mermaid_state.py, and
|
|
15
|
+
test_mermaid_er.py).
|
|
15
16
|
"""
|
|
16
17
|
|
|
17
18
|
def test_pie_routes_to_native_pie_renderer(self):
|
|
18
19
|
with mock.patch(
|
|
19
20
|
"termrender.renderers.mermaid.mermaid_pie.render", return_value=["PIE"]
|
|
20
|
-
) as pie_mock
|
|
21
|
-
"termrender.renderers.mermaid._render_via_binary"
|
|
22
|
-
) as binary_mock:
|
|
21
|
+
) as pie_mock:
|
|
23
22
|
lines = mermaid.render_mermaid_lines('pie\n "A" : 1', 40)
|
|
24
23
|
self.assertEqual(lines, ["PIE"])
|
|
25
24
|
pie_mock.assert_called_once()
|
|
26
|
-
binary_mock.assert_not_called()
|
|
27
25
|
|
|
28
26
|
def test_gantt_routes_to_native_gantt_renderer(self):
|
|
29
27
|
with mock.patch(
|
|
30
28
|
"termrender.renderers.mermaid.mermaid_gantt.render", return_value=["GANTT"]
|
|
31
|
-
) as gantt_mock
|
|
32
|
-
"termrender.renderers.mermaid._render_via_binary"
|
|
33
|
-
) as binary_mock:
|
|
29
|
+
) as gantt_mock:
|
|
34
30
|
lines = mermaid.render_mermaid_lines("gantt\n dateFormat YYYY-MM-DD", 40)
|
|
35
31
|
self.assertEqual(lines, ["GANTT"])
|
|
36
32
|
gantt_mock.assert_called_once()
|
|
37
|
-
binary_mock.assert_not_called()
|
|
38
33
|
|
|
39
|
-
def
|
|
34
|
+
def test_graph_routes_to_native_flowchart_renderer(self):
|
|
40
35
|
with mock.patch(
|
|
41
|
-
"termrender.renderers.mermaid.
|
|
42
|
-
|
|
36
|
+
"termrender.renderers.mermaid.mermaid_flow.render_flowchart",
|
|
37
|
+
return_value=["FLOW"],
|
|
38
|
+
) as flow_mock:
|
|
43
39
|
lines = mermaid.render_mermaid_lines("graph LR\nA-->B", 40)
|
|
44
|
-
self.assertEqual(lines, ["
|
|
45
|
-
|
|
40
|
+
self.assertEqual(lines, ["FLOW"])
|
|
41
|
+
flow_mock.assert_called_once()
|
|
46
42
|
|
|
47
|
-
def
|
|
43
|
+
def test_flowchart_routes_to_native_flowchart_renderer(self):
|
|
48
44
|
with mock.patch(
|
|
49
|
-
"termrender.renderers.mermaid.
|
|
50
|
-
|
|
45
|
+
"termrender.renderers.mermaid.mermaid_flow.render_flowchart",
|
|
46
|
+
return_value=["FLOW"],
|
|
47
|
+
) as flow_mock:
|
|
51
48
|
mermaid.render_mermaid_lines("flowchart TD\nA-->B", 40)
|
|
52
|
-
|
|
49
|
+
flow_mock.assert_called_once()
|
|
50
|
+
|
|
51
|
+
def test_class_diagram_routes_to_native_class_renderer(self):
|
|
52
|
+
with mock.patch(
|
|
53
|
+
"termrender.renderers.mermaid.mermaid_class.render_class",
|
|
54
|
+
return_value=["CLASS"],
|
|
55
|
+
) as class_mock:
|
|
56
|
+
lines = mermaid.render_mermaid_lines("classDiagram\n Animal <|-- Duck", 40)
|
|
57
|
+
self.assertEqual(lines, ["CLASS"])
|
|
58
|
+
class_mock.assert_called_once()
|
|
59
|
+
|
|
60
|
+
def test_state_diagram_routes_to_native_state_renderer(self):
|
|
61
|
+
with mock.patch(
|
|
62
|
+
"termrender.renderers.mermaid.mermaid_state.render_state",
|
|
63
|
+
return_value=["STATE"],
|
|
64
|
+
) as state_mock:
|
|
65
|
+
lines = mermaid.render_mermaid_lines("stateDiagram-v2\n [*] --> Idle", 40)
|
|
66
|
+
self.assertEqual(lines, ["STATE"])
|
|
67
|
+
state_mock.assert_called_once()
|
|
68
|
+
|
|
69
|
+
def test_er_diagram_routes_to_native_er_renderer(self):
|
|
70
|
+
with mock.patch(
|
|
71
|
+
"termrender.renderers.mermaid.mermaid_er.render_er", return_value=["ER"]
|
|
72
|
+
) as er_mock:
|
|
73
|
+
lines = mermaid.render_mermaid_lines("erDiagram\n A ||--o{ B : has", 40)
|
|
74
|
+
self.assertEqual(lines, ["ER"])
|
|
75
|
+
er_mock.assert_called_once()
|
|
53
76
|
|
|
54
77
|
def test_sequence_routes_to_native_sequence_renderer(self):
|
|
55
78
|
with mock.patch(
|
|
56
79
|
"termrender.renderers.mermaid.mermaid_sequence.render_sequence",
|
|
57
80
|
return_value=["SEQ"],
|
|
58
|
-
) as seq_mock
|
|
59
|
-
"termrender.renderers.mermaid._render_via_binary"
|
|
60
|
-
) as binary_mock:
|
|
81
|
+
) as seq_mock:
|
|
61
82
|
lines = mermaid.render_mermaid_lines("sequenceDiagram\n A->>B: hi", 40)
|
|
62
83
|
self.assertEqual(lines, ["SEQ"])
|
|
63
84
|
seq_mock.assert_called_once()
|
|
64
|
-
binary_mock.assert_not_called()
|
|
65
85
|
|
|
66
86
|
def test_mindmap_routes_to_native_mindmap_renderer(self):
|
|
67
87
|
with mock.patch(
|
|
68
88
|
"termrender.renderers.mermaid.mermaid_mindmap.render", return_value=["MM"]
|
|
69
|
-
) as mm_mock
|
|
70
|
-
"termrender.renderers.mermaid._render_via_binary"
|
|
71
|
-
) as binary_mock:
|
|
89
|
+
) as mm_mock:
|
|
72
90
|
lines = mermaid.render_mermaid_lines("mindmap\n root", 40)
|
|
73
91
|
self.assertEqual(lines, ["MM"])
|
|
74
92
|
mm_mock.assert_called_once()
|
|
75
|
-
binary_mock.assert_not_called()
|
|
76
93
|
|
|
77
94
|
def test_journey_routes_to_native_journey_renderer(self):
|
|
78
95
|
with mock.patch(
|
|
79
96
|
"termrender.renderers.mermaid.mermaid_journey.render", return_value=["J"]
|
|
80
|
-
) as journey_mock
|
|
81
|
-
"termrender.renderers.mermaid._render_via_binary"
|
|
82
|
-
) as binary_mock:
|
|
97
|
+
) as journey_mock:
|
|
83
98
|
lines = mermaid.render_mermaid_lines("journey\n title My journey", 40)
|
|
84
99
|
self.assertEqual(lines, ["J"])
|
|
85
100
|
journey_mock.assert_called_once()
|
|
86
|
-
binary_mock.assert_not_called()
|
|
87
101
|
|
|
88
102
|
def test_timeline_routes_to_native_timeline_renderer(self):
|
|
89
103
|
with mock.patch(
|
|
90
104
|
"termrender.renderers.mermaid.mermaid_timeline.render", return_value=["T"]
|
|
91
|
-
) as timeline_mock
|
|
92
|
-
"termrender.renderers.mermaid._render_via_binary"
|
|
93
|
-
) as binary_mock:
|
|
105
|
+
) as timeline_mock:
|
|
94
106
|
lines = mermaid.render_mermaid_lines("timeline\n 2024 : x", 40)
|
|
95
107
|
self.assertEqual(lines, ["T"])
|
|
96
108
|
timeline_mock.assert_called_once()
|
|
97
|
-
binary_mock.assert_not_called()
|
|
98
109
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
110
|
+
def test_exotic_type_degrades_to_raw_echo(self):
|
|
111
|
+
src = "sankey-beta\n\nA,B,10\nB,C,5"
|
|
112
|
+
lines = mermaid.render_mermaid_lines(src, 40)
|
|
113
|
+
self.assertEqual(lines, src.splitlines())
|
|
114
|
+
|
|
115
|
+
def test_exotic_type_raw_echo_contains_no_box_glyphs(self):
|
|
116
|
+
# Pinning the degradation contract: the crouter viewer keys on the
|
|
117
|
+
# *absence* of box-drawing glyphs to keep the original code fence,
|
|
118
|
+
# so an exotic type's echo must never contain one — even one
|
|
119
|
+
# present verbatim in the source itself.
|
|
120
|
+
for src in (
|
|
121
|
+
"sankey-beta\nA,B,10",
|
|
122
|
+
"C4Context\n Person(a, \"A\")",
|
|
123
|
+
"gitGraph\n commit",
|
|
124
|
+
"block-beta\n columns 1\n a",
|
|
125
|
+
"packet-beta\n 0-15: \"data\"",
|
|
126
|
+
"kanban\n Todo\n Task1",
|
|
127
|
+
"totally-unknown-type\nsome line with a literal ┌ box glyph",
|
|
104
128
|
):
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
129
|
+
lines = mermaid.render_mermaid_lines(src, 40)
|
|
130
|
+
joined = "\n".join(lines)
|
|
131
|
+
for cp in range(0x2500, 0x2600):
|
|
132
|
+
self.assertNotIn(chr(cp), joined)
|
|
133
|
+
|
|
134
|
+
def test_unknown_type_routes_to_raw_echo_not_any_native_renderer(self):
|
|
135
|
+
with mock.patch(
|
|
136
|
+
"termrender.renderers.mermaid.mermaid_flow.render_flowchart"
|
|
137
|
+
) as flow_mock, mock.patch(
|
|
138
|
+
"termrender.renderers.mermaid.mermaid_class.render_class"
|
|
139
|
+
) as class_mock, mock.patch(
|
|
140
|
+
"termrender.renderers.mermaid.mermaid_state.render_state"
|
|
141
|
+
) as state_mock, mock.patch(
|
|
142
|
+
"termrender.renderers.mermaid.mermaid_er.render_er"
|
|
143
|
+
) as er_mock:
|
|
144
|
+
mermaid.render_mermaid_lines("sankey-beta\nA,B,10", 40)
|
|
145
|
+
flow_mock.assert_not_called()
|
|
146
|
+
class_mock.assert_not_called()
|
|
147
|
+
state_mock.assert_not_called()
|
|
148
|
+
er_mock.assert_not_called()
|
|
149
|
+
|
|
150
|
+
def test_flowchart_full_integration_renders_real_diagram(self):
|
|
151
|
+
# No mocking: proves the wiring actually reaches render_flowchart and
|
|
152
|
+
# produces its real box-drawing output, not just that routing occurs.
|
|
153
|
+
lines = mermaid.render_mermaid_lines("flowchart LR\n A[Start] --> B[End]", 60)
|
|
154
|
+
joined = "\n".join(lines)
|
|
155
|
+
self.assertIn("Start", joined)
|
|
156
|
+
self.assertIn("End", joined)
|
|
157
|
+
self.assertIn("┌", joined)
|
|
158
|
+
|
|
159
|
+
def test_class_diagram_full_integration_renders_real_diagram(self):
|
|
160
|
+
lines = mermaid.render_mermaid_lines("classDiagram\n class Animal", 60)
|
|
161
|
+
joined = "\n".join(lines)
|
|
162
|
+
self.assertIn("Animal", joined)
|
|
163
|
+
self.assertIn("┌", joined)
|
|
164
|
+
|
|
165
|
+
def test_state_diagram_full_integration_renders_real_diagram(self):
|
|
166
|
+
lines = mermaid.render_mermaid_lines("stateDiagram-v2\n [*] --> Idle", 60)
|
|
167
|
+
joined = "\n".join(lines)
|
|
168
|
+
self.assertIn("Idle", joined)
|
|
169
|
+
self.assertIn("┌", joined)
|
|
170
|
+
|
|
171
|
+
def test_er_diagram_full_integration_renders_real_diagram(self):
|
|
172
|
+
lines = mermaid.render_mermaid_lines("erDiagram\n CUSTOMER ||--o{ ORDER : places", 60)
|
|
173
|
+
joined = "\n".join(lines)
|
|
174
|
+
self.assertIn("CUSTOMER", joined)
|
|
175
|
+
self.assertIn("ORDER", joined)
|
|
176
|
+
self.assertIn("┌", joined)
|
|
110
177
|
|
|
111
178
|
def test_prelude_comment_before_pie_routes_natively(self):
|
|
112
179
|
with mock.patch(
|
|
113
180
|
"termrender.renderers.mermaid.mermaid_pie.render", return_value=["PIE"]
|
|
114
|
-
) as pie_mock
|
|
115
|
-
"termrender.renderers.mermaid._render_via_binary"
|
|
116
|
-
) as binary_mock:
|
|
181
|
+
) as pie_mock:
|
|
117
182
|
src = '%% a leading comment\npie\n "A" : 1'
|
|
118
183
|
lines = mermaid.render_mermaid_lines(src, 40)
|
|
119
184
|
self.assertEqual(lines, ["PIE"])
|
|
120
185
|
pie_mock.assert_called_once()
|
|
121
|
-
binary_mock.assert_not_called()
|
|
122
186
|
|
|
123
187
|
def test_prelude_init_directive_before_sequence_routes_natively(self):
|
|
124
188
|
with mock.patch(
|
|
125
189
|
"termrender.renderers.mermaid.mermaid_sequence.render_sequence",
|
|
126
190
|
return_value=["SEQ"],
|
|
127
|
-
) as seq_mock
|
|
128
|
-
"termrender.renderers.mermaid._render_via_binary"
|
|
129
|
-
) as binary_mock:
|
|
191
|
+
) as seq_mock:
|
|
130
192
|
src = '%%{init: {"theme": "dark"}}%%\nsequenceDiagram\n A->>B: hi'
|
|
131
193
|
lines = mermaid.render_mermaid_lines(src, 40)
|
|
132
194
|
self.assertEqual(lines, ["SEQ"])
|
|
133
195
|
seq_mock.assert_called_once()
|
|
134
|
-
binary_mock.assert_not_called()
|
|
135
196
|
|
|
136
197
|
def test_prelude_frontmatter_before_gantt_routes_natively(self):
|
|
137
198
|
with mock.patch(
|
|
138
199
|
"termrender.renderers.mermaid.mermaid_gantt.render", return_value=["GANTT"]
|
|
139
|
-
) as gantt_mock
|
|
140
|
-
"termrender.renderers.mermaid._render_via_binary"
|
|
141
|
-
) as binary_mock:
|
|
200
|
+
) as gantt_mock:
|
|
142
201
|
src = "---\ntitle: my diagram\n---\ngantt\n dateFormat YYYY-MM-DD"
|
|
143
202
|
lines = mermaid.render_mermaid_lines(src, 40)
|
|
144
203
|
self.assertEqual(lines, ["GANTT"])
|
|
145
204
|
gantt_mock.assert_called_once()
|
|
146
|
-
binary_mock.assert_not_called()
|
|
147
205
|
|
|
148
206
|
def test_type_sniff_is_case_insensitive(self):
|
|
149
207
|
with mock.patch(
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 3
|
|
3
|
+
requires-python = ">=3.10"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "grandalf"
|
|
7
|
+
version = "0.8"
|
|
8
|
+
source = { registry = "https://pypi.org/simple" }
|
|
9
|
+
dependencies = [
|
|
10
|
+
{ name = "pyparsing" },
|
|
11
|
+
]
|
|
12
|
+
sdist = { url = "https://files.pythonhosted.org/packages/95/0e/4ac934b416857969f9135dec17ac80660634327e003a870835dd1f382659/grandalf-0.8.tar.gz", hash = "sha256:2813f7aab87f0d20f334a3162ccfbcbf085977134a17a5b516940a93a77ea974", size = 38128, upload-time = "2023-01-26T07:37:06.668Z" }
|
|
13
|
+
wheels = [
|
|
14
|
+
{ url = "https://files.pythonhosted.org/packages/61/30/44c7eb0a952478dbb5f2f67df806686d6a7e4b19f6204e091c4f49dc7c69/grandalf-0.8-py3-none-any.whl", hash = "sha256:793ca254442f4a79252ea9ff1ab998e852c1e071b863593e5383afee906b4185", size = 41802, upload-time = "2023-01-10T15:16:19.753Z" },
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[[package]]
|
|
18
|
+
name = "mistune"
|
|
19
|
+
version = "3.2.1"
|
|
20
|
+
source = { registry = "https://pypi.org/simple" }
|
|
21
|
+
dependencies = [
|
|
22
|
+
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
|
23
|
+
]
|
|
24
|
+
sdist = { url = "https://files.pythonhosted.org/packages/ca/84/620cc3f7e3adf6f5067e10f4dbae71295d8f9e16d5d3f9ef97c40f2f592c/mistune-3.2.1.tar.gz", hash = "sha256:7c8e5501d38bac1582e067e46c8343f17d57ea1aaa735823f3aba1fd59c88a28", size = 98003, upload-time = "2026-05-03T14:33:22.312Z" }
|
|
25
|
+
wheels = [
|
|
26
|
+
{ url = "https://files.pythonhosted.org/packages/2a/7f/a946aa4f8752b37102b41e64dca18a1976ac705c3a0d1dfe74d820a02552/mistune-3.2.1-py3-none-any.whl", hash = "sha256:78cdb0ba5e938053ccf63651b352508d2efa9411dc8810bfb05f2dc5140c0048", size = 53749, upload-time = "2026-05-03T14:33:20.551Z" },
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[[package]]
|
|
30
|
+
name = "pygments"
|
|
31
|
+
version = "2.20.0"
|
|
32
|
+
source = { registry = "https://pypi.org/simple" }
|
|
33
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
|
|
34
|
+
wheels = [
|
|
35
|
+
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[[package]]
|
|
39
|
+
name = "pyparsing"
|
|
40
|
+
version = "3.3.2"
|
|
41
|
+
source = { registry = "https://pypi.org/simple" }
|
|
42
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" }
|
|
43
|
+
wheels = [
|
|
44
|
+
{ url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" },
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[[package]]
|
|
48
|
+
name = "termrender"
|
|
49
|
+
source = { editable = "." }
|
|
50
|
+
dependencies = [
|
|
51
|
+
{ name = "grandalf" },
|
|
52
|
+
{ name = "mistune" },
|
|
53
|
+
{ name = "pygments" },
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[package.metadata]
|
|
57
|
+
requires-dist = [
|
|
58
|
+
{ name = "grandalf", specifier = ">=0.8" },
|
|
59
|
+
{ name = "mistune", specifier = ">=3.0" },
|
|
60
|
+
{ name = "pygments", specifier = ">=2.0" },
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[[package]]
|
|
64
|
+
name = "typing-extensions"
|
|
65
|
+
version = "4.15.0"
|
|
66
|
+
source = { registry = "https://pypi.org/simple" }
|
|
67
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
|
68
|
+
wheels = [
|
|
69
|
+
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
|
70
|
+
]
|