termrender 4.5.0__tar.gz → 4.6.1__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.
Files changed (77) hide show
  1. {termrender-4.5.0 → termrender-4.6.1}/CHANGELOG.md +73 -0
  2. {termrender-4.5.0 → termrender-4.6.1}/PKG-INFO +1 -1
  3. termrender-4.6.1/scripts/mermaid_flow_parity.py +168 -0
  4. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/mermaid_flow.py +16 -10
  5. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/mermaid_flow_layout.py +540 -32
  6. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/mermaid_flow_parser.py +128 -59
  7. termrender-4.6.1/tests/test_mermaid_flow_corpus.py +436 -0
  8. {termrender-4.5.0 → termrender-4.6.1}/tests/test_mermaid_flow_parser.py +91 -0
  9. termrender-4.6.1/tests/test_mermaid_flow_shapes.py +313 -0
  10. {termrender-4.5.0 → termrender-4.6.1}/.github/workflows/publish.yml +0 -0
  11. {termrender-4.5.0 → termrender-4.6.1}/.gitignore +0 -0
  12. {termrender-4.5.0 → termrender-4.6.1}/CLAUDE.md +0 -0
  13. {termrender-4.5.0 → termrender-4.6.1}/LICENSE +0 -0
  14. {termrender-4.5.0 → termrender-4.6.1}/README.md +0 -0
  15. {termrender-4.5.0 → termrender-4.6.1}/design.json +0 -0
  16. {termrender-4.5.0 → termrender-4.6.1}/pyproject.toml +0 -0
  17. {termrender-4.5.0 → termrender-4.6.1}/requirements.json +0 -0
  18. {termrender-4.5.0 → termrender-4.6.1}/scripts/build-mermaid-ascii.sh +0 -0
  19. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/CLAUDE.md +0 -0
  20. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/__init__.py +0 -0
  21. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/__main__.py +0 -0
  22. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/_bin/mermaid-ascii-darwin-arm64 +0 -0
  23. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/_mermaid_bin.py +0 -0
  24. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/blocks.py +0 -0
  25. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/emit.py +0 -0
  26. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/layout.py +0 -0
  27. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/parser.py +0 -0
  28. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/py.typed +0 -0
  29. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/CLAUDE.md +0 -0
  30. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/__init__.py +0 -0
  31. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/borders.py +0 -0
  32. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/charts.py +0 -0
  33. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/code.py +0 -0
  34. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/columns.py +0 -0
  35. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/diff.py +0 -0
  36. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/divider.py +0 -0
  37. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/mermaid.py +0 -0
  38. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/mermaid_flow_model.py +0 -0
  39. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/mermaid_gantt.py +0 -0
  40. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/mermaid_journey.py +0 -0
  41. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/mermaid_mindmap.py +0 -0
  42. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/mermaid_pie.py +0 -0
  43. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/mermaid_prelude.py +0 -0
  44. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/mermaid_sequence.py +0 -0
  45. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/mermaid_timeline.py +0 -0
  46. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/panel.py +0 -0
  47. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/quote.py +0 -0
  48. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/stat.py +0 -0
  49. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/table.py +0 -0
  50. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/text.py +0 -0
  51. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/timeline.py +0 -0
  52. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/renderers/tree.py +0 -0
  53. {termrender-4.5.0 → termrender-4.6.1}/src/termrender/style.py +0 -0
  54. {termrender-4.5.0 → termrender-4.6.1}/tests/__init__.py +0 -0
  55. {termrender-4.5.0 → termrender-4.6.1}/tests/test_charts.py +0 -0
  56. {termrender-4.5.0 → termrender-4.6.1}/tests/test_cli_contract.py +0 -0
  57. {termrender-4.5.0 → termrender-4.6.1}/tests/test_column_alignment.py +0 -0
  58. {termrender-4.5.0 → termrender-4.6.1}/tests/test_diff.py +0 -0
  59. {termrender-4.5.0 → termrender-4.6.1}/tests/test_inline_badge.py +0 -0
  60. {termrender-4.5.0 → termrender-4.6.1}/tests/test_linebreak.py +0 -0
  61. {termrender-4.5.0 → termrender-4.6.1}/tests/test_mermaid_compat.py +0 -0
  62. {termrender-4.5.0 → termrender-4.6.1}/tests/test_mermaid_dispatch.py +0 -0
  63. {termrender-4.5.0 → termrender-4.6.1}/tests/test_mermaid_flow.py +0 -0
  64. {termrender-4.5.0 → termrender-4.6.1}/tests/test_mermaid_flow_layout.py +0 -0
  65. {termrender-4.5.0 → termrender-4.6.1}/tests/test_mermaid_flow_model.py +0 -0
  66. {termrender-4.5.0 → termrender-4.6.1}/tests/test_mermaid_gantt.py +0 -0
  67. {termrender-4.5.0 → termrender-4.6.1}/tests/test_mermaid_journey.py +0 -0
  68. {termrender-4.5.0 → termrender-4.6.1}/tests/test_mermaid_mindmap.py +0 -0
  69. {termrender-4.5.0 → termrender-4.6.1}/tests/test_mermaid_pie.py +0 -0
  70. {termrender-4.5.0 → termrender-4.6.1}/tests/test_mermaid_sequence.py +0 -0
  71. {termrender-4.5.0 → termrender-4.6.1}/tests/test_mermaid_timeline.py +0 -0
  72. {termrender-4.5.0 → termrender-4.6.1}/tests/test_myst_gaps.py +0 -0
  73. {termrender-4.5.0 → termrender-4.6.1}/tests/test_stat.py +0 -0
  74. {termrender-4.5.0 → termrender-4.6.1}/tests/test_tasklist.py +0 -0
  75. {termrender-4.5.0 → termrender-4.6.1}/tests/test_timeline.py +0 -0
  76. {termrender-4.5.0 → termrender-4.6.1}/tests/test_variable_colons.py +0 -0
  77. {termrender-4.5.0 → termrender-4.6.1}/uv.lock +0 -0
@@ -1,6 +1,79 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v4.6.1 (2026-07-07)
5
+
6
+ ### Bug Fixes
7
+
8
+ - **mermaid-flow**: Parse chained edges and bracket-aware tokenization
9
+ ([`5040b96`](https://github.com/crouton-labs/termrender/commit/5040b967bbe75c70c6d03d57930742ba020e3333))
10
+
11
+ - Unify edge parsing behind one bracket-depth-aware connector scanner (_scan_connectors) shared with
12
+ & fan-out splitting and ; statement splitting, replacing the single-connector _EDGE_RE. -
13
+ CRITICAL: chained edge statements (A-->B-->C) now parse as a sequence of node groups separated by
14
+ connectors, emitting one edge per adjacent triple instead of dropping the tail after the first
15
+ connector. - MAJOR: connector-looking text inside [], (), {} labels (A[Go --> Fast]) is no longer
16
+ mistaken for an edge connector. - MINOR: semicolons inside labels (A[Check; validate]) no longer
17
+ split the statement.
18
+
19
+ Adds structural tests for 2/3-link chains, mixed-style chains, chain with inline label,
20
+ connector-in-label for all three connector families, and semicolon-in-label. Adds a chained-form
21
+ golden for the labeled back-edge cycle proving identical render to the separate-line form.
22
+
23
+ ### Testing
24
+
25
+ - Add flowchart golden corpus and go-binary parity harness
26
+ ([`2a5a00a`](https://github.com/crouton-labs/termrender/commit/2a5a00aba18195eac987bc8c202a80724d6e43c9))
27
+
28
+ - tests/test_mermaid_flow_corpus.py: 26 golden-output tests pinning exact render_flowchart() lines
29
+ across all 9 node shapes, every edge style (solid/dotted/thick/headless/bidirectional), both label
30
+ forms, & fan-out, a multi-parent DAG, a labeled-back-edge cycle (the case the vendored Go binary
31
+ panics/mis-parses on), LR vs TD, subgraph + nested subgraph, a long label vs a tight width budget,
32
+ and the two degradation paths. - scripts/mermaid_flow_parity.py: standalone parity harness (not a
33
+ pytest dependency) that invokes the vendored Go binary the way termrender's real dispatch path
34
+ does (preprocess_mermaid_for_ascii + fix_mermaid_encoding) and the native renderer side by side
35
+ across 14 representative inputs, writing the comparison to the orchestrator's shared context dir.
36
+ Confirms parity-or-better plus two Go failure modes the native renderer avoids: a self-loop panic
37
+ (index out of range) and labeled-back-edge mis-parsing (label text swallowed into a phantom node
38
+ instead of a back-edge). - fix: mermaid_flow.py's module docstring described a stale prior phase
39
+ ("every shape renders as a plain rectangle", "subgraphs parse but aren't drawn as frames") — both
40
+ are false now; corrected to describe actual current behavior (distinct shape borders, framed
41
+ subgraphs with contiguous-membership flattening).
42
+
43
+ 463 tests green (437 pre-existing + 26 new corpus).
44
+
45
+
46
+ ## v4.6.0 (2026-07-06)
47
+
48
+ ### Features
49
+
50
+ - Add flowchart node shapes, subgraph frames, and direction rendering
51
+ ([`e872199`](https://github.com/crouton-labs/termrender/commit/e87219956294319feb0015d2cf4573b8cc60a0cb))
52
+
53
+ Applies and hardens the salvaged decoration work for the native mermaid flowchart renderer's layout
54
+ engine:
55
+
56
+ - Nine distinct node-shape borders (rect, round, stadium, cylinder, circle, diamond, hexagon,
57
+ subroutine, parallelogram), each reserving its full bounding box so the router treats every shape
58
+ as impassable. - Subgraph enclosure frames (left-anchored title, nested, flatten when members
59
+ aren't placed contiguously enough for a clean rect). - TB/BT/LR/RL direction handling via a
60
+ post-layout coordinate transform.
61
+
62
+ Fixes found while hardening: - draw_frame now reserves the title text cells (not the whole border)
63
+ so a cross-boundary edge crossing a frame's top border can no longer clobber a letter of the
64
+ subgraph title. - Subgraph frame sizing is now computed bottom-up (_build_subgraph_frames): a
65
+ parent subgraph's extent is built from its children's *padded frame rects* (when they get a frame)
66
+ rather than raw member extents, so a nested subgraph whose members share the same column/row span
67
+ as its parent no longer produces an identically-sized frame that overwrites the parent's border —
68
+ nesting now always has real margin. - Corrected the module docstring's stale "every node renders
69
+ as a plain rectangle, frames not yet wired" description to match the shipped shape/frame drawers.
70
+
71
+ Adds tests/test_mermaid_flow_shapes.py: real-geometry assertions for shape distinctness, a 9-shape
72
+ gallery, subgraph frame containment, nested-frame non-overlap, non-contiguous-subgraph flatten,
73
+ LR/RL/BT topology + arrowhead direction, LR same-rank spacing, and router avoidance of a diamond's
74
+ interior. Full suite: 437 passed (422 existing + 15 new), no regressions.
75
+
76
+
4
77
  ## v4.5.0 (2026-07-06)
5
78
 
6
79
  ### Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: termrender
3
- Version: 4.5.0
3
+ Version: 4.6.1
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
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env python3
2
+ """Standalone parity harness: native flowchart renderer vs. the vendored Go
3
+ binary, for a curated set of representative mermaid flowchart sources.
4
+
5
+ Not a pytest test and not imported by the test suite — the corpus tests must
6
+ not depend on the vendored ``mermaid-ascii`` binary being present on disk.
7
+ Run this once (locally, wherever the vendored binary for the host platform
8
+ exists) to (re)generate the side-by-side comparison artifact:
9
+
10
+ .venv/bin/python scripts/mermaid_flow_parity.py
11
+
12
+ Invokes the Go binary exactly the way termrender's real dispatch path does
13
+ (``mermaid.py::_render_via_binary``): run ``preprocess_mermaid_for_ascii``
14
+ (flowchart shape normalization) on the source, shell out to
15
+ ``mermaid_ascii_bin()`` with ``-f - -y 1``, and undo its Latin-1
16
+ double-encoding via ``fix_mermaid_encoding`` — so the comparison is fair
17
+ (same preprocessing termrender actually applies before falling back to the
18
+ binary today).
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import subprocess
24
+ import sys
25
+ from pathlib import Path
26
+
27
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
28
+
29
+ from termrender._mermaid_bin import mermaid_ascii_bin
30
+ from termrender.renderers.mermaid import fix_mermaid_encoding, preprocess_mermaid_for_ascii
31
+ from termrender.renderers.mermaid_flow import render_flowchart
32
+
33
+ OUTPUT_PATH = Path(
34
+ "/Users/silasrhyneer/.crouter/canvas/nodes/mr9swads-0f62d05c/context/flow-parity-sample.md"
35
+ )
36
+
37
+ WIDTH = 60
38
+
39
+ CASES: list[tuple[str, str]] = [
40
+ (
41
+ "Simple forward DAG",
42
+ "graph TD\nA-->B\nA-->C\n",
43
+ ),
44
+ (
45
+ "Multi-parent DAG",
46
+ "graph TD\nA-->C\nB-->C\n",
47
+ ),
48
+ (
49
+ "Headless edge (`---`)",
50
+ "graph TD\nA---B\n",
51
+ ),
52
+ (
53
+ "Pipe-labeled edge",
54
+ "graph TD\nA-->|hi|B\n",
55
+ ),
56
+ (
57
+ "Inline-labeled edge",
58
+ "graph TD\nA -- go now --> B\n",
59
+ ),
60
+ (
61
+ "`&` fan-out",
62
+ "graph TD\nA & B --> C & D\n",
63
+ ),
64
+ (
65
+ "Non-rect shapes (diamond, circle, cylinder)",
66
+ "graph TD\nA{Decision}-->B((Circle))\nB-->C[(DB)]\n",
67
+ ),
68
+ (
69
+ "LR direction",
70
+ "graph LR\nA-->B\nA-->C\n",
71
+ ),
72
+ (
73
+ "Subgraph frame",
74
+ "graph TD\nsubgraph zone[Zone]\nA-->B\nend\n",
75
+ ),
76
+ (
77
+ "Plain 3-cycle (no label)",
78
+ "graph TD\nA-->B\nB-->C\nC-->A\n",
79
+ ),
80
+ (
81
+ "Self-loop (Go binary PANICS: index out of range in drawLine)",
82
+ "graph TD\nA-->A\n",
83
+ ),
84
+ (
85
+ "Cycle with a LABELED back-edge (Go binary mis-parses the label as "
86
+ "a phantom node — wrong topology; this is the case the requirements "
87
+ "doc calls out as the one the Go binary cannot handle)",
88
+ "graph TD\nA-->B\nB-->C\nC-->|retry|A\n",
89
+ ),
90
+ (
91
+ "Same labeled-back-edge cycle, spaced arrows (Go binary still "
92
+ "fabricates a phantom 4th node '|retry| A' instead of a back-edge "
93
+ "into A — the label-parsing bug is not a whitespace quirk)",
94
+ "graph TD\nA --> B\nB --> C\nC --> |retry| A\n",
95
+ ),
96
+ (
97
+ "Simple forward DAG, spaced arrows (Go binary's own preferred "
98
+ "form — included so the unspaced cases above are judged fairly "
99
+ "against Go's best case, not just its worst case)",
100
+ "graph TD\nA --> B\nA --> C\n",
101
+ ),
102
+ ]
103
+
104
+
105
+ def render_via_go_binary(source: str) -> tuple[str, bool]:
106
+ """Return (rendered_text, panicked). Mirrors mermaid.py::_render_via_binary
107
+ but surfaces panic/failure explicitly instead of silently echoing source
108
+ (the harness wants to *show* the failure, not hide it)."""
109
+ try:
110
+ result = subprocess.run(
111
+ [mermaid_ascii_bin(), "-f", "-", "-y", "1"],
112
+ input=preprocess_mermaid_for_ascii(source),
113
+ capture_output=True,
114
+ text=True,
115
+ timeout=30,
116
+ )
117
+ except (FileNotFoundError, subprocess.TimeoutExpired, OSError) as exc:
118
+ return f"[harness error invoking binary: {exc}]", True
119
+
120
+ if result.returncode != 0:
121
+ stderr_tail = "\n".join(result.stderr.strip().splitlines()[:4])
122
+ return f"PANIC (exit {result.returncode}):\n{stderr_tail}", True
123
+
124
+ return fix_mermaid_encoding(result.stdout), False
125
+
126
+
127
+ def main() -> None:
128
+ sections: list[str] = [
129
+ "# Native flowchart renderer vs. Go `mermaid-ascii` — parity sample",
130
+ "",
131
+ "Generated by `scripts/mermaid_flow_parity.py`. The Go binary is",
132
+ "invoked exactly the way termrender's real dispatch path does today",
133
+ "(`mermaid.py::_render_via_binary`): flowchart shape normalization via",
134
+ "`preprocess_mermaid_for_ascii`, then `mermaid-ascii -f - -y 1`, then",
135
+ "`fix_mermaid_encoding` on the output.",
136
+ "",
137
+ ]
138
+
139
+ for title, source in CASES:
140
+ go_text, panicked = render_via_go_binary(source)
141
+ our_lines = render_flowchart(source, WIDTH)
142
+ our_text = "\n".join(our_lines)
143
+
144
+ sections.append(f"## {title}")
145
+ sections.append("")
146
+ sections.append("Mermaid source:")
147
+ sections.append("```")
148
+ sections.append(source.rstrip("\n"))
149
+ sections.append("```")
150
+ sections.append("")
151
+ sections.append(f"Go binary output{' (PANIC/failure)' if panicked else ''}:")
152
+ sections.append("```")
153
+ sections.append(go_text if go_text.strip() else "(empty)")
154
+ sections.append("```")
155
+ sections.append("")
156
+ sections.append("Native `render_flowchart` output:")
157
+ sections.append("```")
158
+ sections.append(our_text if our_text.strip() else "(empty)")
159
+ sections.append("```")
160
+ sections.append("")
161
+
162
+ OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
163
+ OUTPUT_PATH.write_text("\n".join(sections) + "\n")
164
+ print(f"wrote {OUTPUT_PATH}")
165
+
166
+
167
+ if __name__ == "__main__":
168
+ main()
@@ -25,19 +25,25 @@ Grammar supported
25
25
  Header: ``graph``/``flowchart`` + direction (``TD``/``TB``/``LR``/``RL``/
26
26
  ``BT``). Node shapes: ``[rect]``, ``(round)``, ``([stadium])``,
27
27
  ``[(cylinder)]``, ``((circle))``, ``{diamond}``, ``{{hexagon}}``,
28
- ``[[subroutine]]``, ``[/parallelogram/]`` — this phase renders every shape
29
- as a plain rectangle (distinct shape borders are a later phase; only the
30
- label text is load-bearing here). Edges: ``-->``, ``---``, ``-.->``, ``==>``
31
- (and bidirectional forms), with ``|label|`` and inline ``A -- text --> B``
32
- labels, and ``&`` fan-out. ``subgraph ... end`` blocks parse but are not yet
33
- rendered as frames (nodes inside them still place and render normally).
34
- ``class``/``classDef``/``style``/``click``/``linkStyle`` lines are ignored.
28
+ ``[[subroutine]]``, ``[/parallelogram/]`` — each renders with a distinct
29
+ shape-specific border (diamond/parallelogram slant tapers, round/stadium/
30
+ circle rounded corners, a cylinder's rounded cap over a square base, a
31
+ hexagon's cut corners, a subroutine's double-bar sides); only a shape whose
32
+ box is too small for its own drawer falls back to the plain rectangle
33
+ border. Edges: ``-->``, ``---``, ``-.->``, ``==>`` (and bidirectional
34
+ forms), with ``|label|`` and inline ``A -- text --> B`` labels, and ``&``
35
+ fan-out. ``subgraph ... end`` blocks (including nested subgraphs) render as
36
+ enclosing frames with a left-anchored title when their members place
37
+ contiguously enough for a clean rect; otherwise a block flattens (no frame,
38
+ members still placed and drawn) rather than draw a frame that would
39
+ visually claim a non-member node. ``class``/``classDef``/``style``/
40
+ ``click``/``linkStyle`` lines are ignored.
35
41
 
36
42
  Known degradations (by design, not bugs)
37
43
  -----------------------------------------
38
- - Node shapes other than a plain rectangle border are not yet visually
39
- distinct (Phase 4 of this renderer's build).
40
- - Subgraph blocks parse but are not yet drawn as enclosing frames (Phase 4).
44
+ - A subgraph whose members aren't placed contiguously enough for a clean
45
+ enclosing rect flattens (drops its frame and title) rather than draw a
46
+ frame that visually claims a non-member node.
41
47
  - See ``mermaid_flow_layout.py``'s module docstring for the edge-routing
42
48
  degradations (dense-graph crossings, label-lane overlap, CJK wrapping,
43
49
  minimum-box-size self-loop cosmetics).