termrender 4.10.0__tar.gz → 4.10.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.
- {termrender-4.10.0 → termrender-4.10.1}/CHANGELOG.md +24 -0
- {termrender-4.10.0 → termrender-4.10.1}/PKG-INFO +1 -1
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/mermaid_flow_layout.py +31 -7
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_mermaid_er.py +60 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_mermaid_flow_corpus.py +7 -10
- {termrender-4.10.0 → termrender-4.10.1}/.github/workflows/publish.yml +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/.gitignore +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/CLAUDE.md +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/LICENSE +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/README.md +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/design.json +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/pyproject.toml +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/requirements.json +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/CLAUDE.md +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/__init__.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/__main__.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/blocks.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/emit.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/layout.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/parser.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/py.typed +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/CLAUDE.md +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/__init__.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/borders.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/charts.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/code.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/columns.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/diff.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/divider.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/mermaid.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/mermaid_class.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/mermaid_er.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/mermaid_flow.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/mermaid_flow_model.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/mermaid_flow_parser.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/mermaid_gantt.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/mermaid_journey.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/mermaid_mindmap.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/mermaid_pie.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/mermaid_prelude.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/mermaid_sequence.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/mermaid_state.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/mermaid_timeline.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/panel.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/quote.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/stat.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/table.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/text.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/timeline.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/renderers/tree.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/src/termrender/style.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/__init__.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_charts.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_cli_contract.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_column_alignment.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_diff.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_inline_badge.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_linebreak.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_mermaid_class.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_mermaid_dispatch.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_mermaid_flow.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_mermaid_flow_layout.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_mermaid_flow_model.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_mermaid_flow_parser.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_mermaid_flow_shapes.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_mermaid_gantt.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_mermaid_journey.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_mermaid_mindmap.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_mermaid_pie.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_mermaid_sequence.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_mermaid_state.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_mermaid_timeline.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_myst_gaps.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_stat.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_tasklist.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_timeline.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/tests/test_variable_colons.py +0 -0
- {termrender-4.10.0 → termrender-4.10.1}/uv.lock +0 -0
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v4.10.1 (2026-07-07)
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- **mermaid-flow**: Constant inter-layer gap regardless of node height
|
|
9
|
+
([`eb1726f`](https://github.com/crouton-labs/termrender/commit/eb1726fa4bd05a57d29026b10715edc067ac4113))
|
|
10
|
+
|
|
11
|
+
The rank-band gap override that widens spacing to fit a labeled edge scaled with visual_len(label)
|
|
12
|
+
(the label's *text width*) even on TB/BT diagrams, where the label is drawn horizontally across a
|
|
13
|
+
vertical connector and only ever needs one clear row, not one row per character. Since ER/class
|
|
14
|
+
diagrams route almost every edge through a label (relationship text, cardinality), this made
|
|
15
|
+
compartmented-node TB layouts burn ~15-17 blank connector rows per edge hop while 1-line-node
|
|
16
|
+
flowcharts stayed compact — node height was a red herring; the real driver was label width feeding
|
|
17
|
+
a row count.
|
|
18
|
+
|
|
19
|
+
_rank_gap_overrides is now direction-aware: LR/RL keeps the width-scaled gap (the transpose turns
|
|
20
|
+
that native row-band into the final horizontal run the label reads along, so it genuinely needs
|
|
21
|
+
the width). TB/BT gets a small constant, _LABELED_ROW_GAP=3, regardless of label length.
|
|
22
|
+
|
|
23
|
+
Updates the two TB labeled-edge goldens in test_mermaid_flow_corpus.py to the new compact spacing,
|
|
24
|
+
and adds a regression test in test_mermaid_er.py pinning a row budget for compartmented entities
|
|
25
|
+
so this doesn't regress.
|
|
26
|
+
|
|
27
|
+
|
|
4
28
|
## v4.10.0 (2026-07-07)
|
|
5
29
|
|
|
6
30
|
### Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: termrender
|
|
3
|
-
Version: 4.10.
|
|
3
|
+
Version: 4.10.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
|
|
@@ -831,24 +831,45 @@ _GAP_X = 3 # minimum border-to-border gap between boxes in one rank/ban
|
|
|
831
831
|
_ROW_GAP = 2 # minimum gap between rank bands
|
|
832
832
|
_COMPONENT_GUTTER = 4 # gap between independently-laid-out components
|
|
833
833
|
_LABEL_GAP_PAD = 1 # cells of breathing room either side of a label's own text
|
|
834
|
-
# when it forces a rank-band gap wider than _ROW_GAP
|
|
834
|
+
# when it forces a rank-band gap wider than _ROW_GAP
|
|
835
|
+
# along the axis the label's text actually runs.
|
|
836
|
+
_LABELED_ROW_GAP = 3 # inter-rank gap when a label sits on that band's vertical
|
|
837
|
+
# run in *final* screen space (TB/BT): one blank row, the
|
|
838
|
+
# label's own row, one more blank row. A constant, not
|
|
839
|
+
# scaled by the label's text length — the label reads
|
|
840
|
+
# horizontally on a single row regardless of how many
|
|
841
|
+
# characters it has, so the row *count* needed is fixed.
|
|
835
842
|
|
|
836
843
|
|
|
837
844
|
def _rank_gap_overrides(
|
|
838
|
-
edges: list[FlowEdge], rank_of: dict[str, int]
|
|
845
|
+
edges: list[FlowEdge], rank_of: dict[str, int], direction: Direction
|
|
839
846
|
) -> dict[int, int]:
|
|
840
847
|
"""Minimum inter-rank-band gap keyed by the *lower* rank of each
|
|
841
848
|
adjacent-rank transition, widened past ``_ROW_GAP`` wherever a labeled
|
|
842
849
|
edge directly connects that transition's two ranks. A forward edge
|
|
843
850
|
between adjacent ranks routes as a single straight run (or a Z path
|
|
844
851
|
that still crosses the same inter-rank band) whose only clear space is
|
|
845
|
-
this gap
|
|
846
|
-
|
|
847
|
-
|
|
852
|
+
this gap.
|
|
853
|
+
|
|
854
|
+
The rank axis is native rows here, pre-direction-transform (see the
|
|
855
|
+
adapter docstring) — whether that ends up as *vertical* or
|
|
856
|
+
*horizontal* space on screen depends on ``direction``. For LR/RL the
|
|
857
|
+
transpose turns this band into the final horizontal run the label
|
|
858
|
+
reads along, so it genuinely needs ``visual_len(label)`` cells of
|
|
859
|
+
width — at the base ``_ROW_GAP`` a label wider than a couple of cells
|
|
860
|
+
has nowhere to go but onto the boxes it connects (the short
|
|
861
|
+
LR/adjacent-rank clipped-label bug this closes). For TB/BT the band
|
|
862
|
+
stays vertical on screen and the label is drawn horizontally *across*
|
|
863
|
+
the connector column (:func:`_draw_label_on_segment`'s vertical-segment
|
|
864
|
+
branch) — it needs a little row headroom to have its own clear row,
|
|
865
|
+
not a row per character, so it gets the small constant
|
|
866
|
+
``_LABELED_ROW_GAP`` regardless of the label's length.
|
|
867
|
+
|
|
848
868
|
Non-adjacent-rank edges (back-edges, multi-rank spans) don't constrain
|
|
849
869
|
the gap here — they route through their own side lane, not this band.
|
|
850
870
|
"""
|
|
851
871
|
overrides: dict[int, int] = {}
|
|
872
|
+
horizontal_on_screen = direction in (Direction.LR, Direction.RL)
|
|
852
873
|
for e in edges:
|
|
853
874
|
if e.src == e.dst or not e.label:
|
|
854
875
|
continue
|
|
@@ -859,7 +880,10 @@ def _rank_gap_overrides(
|
|
|
859
880
|
lo_r, hi_r = (r_src, r_dst) if r_src <= r_dst else (r_dst, r_src)
|
|
860
881
|
if hi_r - lo_r != 1:
|
|
861
882
|
continue
|
|
862
|
-
|
|
883
|
+
if horizontal_on_screen:
|
|
884
|
+
needed = visual_len(e.label) + 2 * _LABEL_GAP_PAD
|
|
885
|
+
else:
|
|
886
|
+
needed = _LABELED_ROW_GAP
|
|
863
887
|
overrides[lo_r] = max(overrides.get(lo_r, _ROW_GAP), needed)
|
|
864
888
|
return overrides
|
|
865
889
|
|
|
@@ -922,7 +946,7 @@ def _place_nodes(
|
|
|
922
946
|
ranks[sug.grx[v].rank].append(v)
|
|
923
947
|
|
|
924
948
|
rank_of = {v.data: sug.grx[v].rank for v in comp_vertices}
|
|
925
|
-
gap_overrides = _rank_gap_overrides(edges, rank_of)
|
|
949
|
+
gap_overrides = _rank_gap_overrides(edges, rank_of, direction)
|
|
926
950
|
|
|
927
951
|
band_top_for_rank: dict[int, int] = {}
|
|
928
952
|
cursor = 0
|
|
@@ -265,6 +265,66 @@ def test_order_example_golden_topology():
|
|
|
265
265
|
assert cust_row < order_row < line_item_row
|
|
266
266
|
|
|
267
267
|
|
|
268
|
+
# --------------------------------------------------------------------------
|
|
269
|
+
# Vertical compactness (regression: constant inter-layer gap regardless of
|
|
270
|
+
# node height — see mermaid_flow_layout._LABELED_ROW_GAP)
|
|
271
|
+
# --------------------------------------------------------------------------
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def test_compartmented_entities_get_constant_gap_not_height_scaled():
|
|
275
|
+
# Two tall (compartmented) entities joined by one labeled relationship
|
|
276
|
+
# used to burn ~15-17 blank connector rows for this single edge hop —
|
|
277
|
+
# the inter-rank gap scaled with the edge label's *text length*
|
|
278
|
+
# (visual_len), not with node height, but on ER/class diagrams nearly
|
|
279
|
+
# every edge carries a label, so the effect showed up exactly when
|
|
280
|
+
# nodes were tall. The gap between adjacent-rank boxes must now stay a
|
|
281
|
+
# small constant: enough for a connector row, the label's own row, and
|
|
282
|
+
# one more connector row — independent of both the label's length and
|
|
283
|
+
# the boxes' height.
|
|
284
|
+
src = (
|
|
285
|
+
"erDiagram\n"
|
|
286
|
+
" CUSTOMER ||--o{ ORDER : places\n"
|
|
287
|
+
" CUSTOMER {\n"
|
|
288
|
+
" string name PK\n"
|
|
289
|
+
" string email\n"
|
|
290
|
+
" }\n"
|
|
291
|
+
" ORDER {\n"
|
|
292
|
+
" int orderNumber PK\n"
|
|
293
|
+
" string status\n"
|
|
294
|
+
" }\n"
|
|
295
|
+
)
|
|
296
|
+
lines = _lines(src, width=100)
|
|
297
|
+
assert _has_box_glyphs(lines)
|
|
298
|
+
joined = "\n".join(lines)
|
|
299
|
+
|
|
300
|
+
# Full row budget: two 6-row compartmented boxes plus a small constant
|
|
301
|
+
# gap between them — well under the ~27 rows the scaling bug produced.
|
|
302
|
+
assert len(lines) <= 18, f"expected a compact render, got {len(lines)} rows:\n{joined}"
|
|
303
|
+
|
|
304
|
+
# The label survives in full, uncorrupted, and every box border row is
|
|
305
|
+
# single-glyph clean (no overlapping glyphs from a collapsed gap).
|
|
306
|
+
assert "places" in joined
|
|
307
|
+
for attr in ("string name PK", "string email", "int orderNumber PK", "string status"):
|
|
308
|
+
assert attr in joined
|
|
309
|
+
|
|
310
|
+
cust_row, _ = _row_col(lines, "CUSTOMER")
|
|
311
|
+
order_row, _ = _row_col(lines, "ORDER")
|
|
312
|
+
label_row, _ = _row_col(lines, "places")
|
|
313
|
+
assert cust_row < label_row < order_row
|
|
314
|
+
|
|
315
|
+
# The connector gap between the two boxes is a small constant, not
|
|
316
|
+
# scaled by the label's width: the blank/connector run directly above
|
|
317
|
+
# and below the label row is short.
|
|
318
|
+
cust_bottom = next(
|
|
319
|
+
r for r in range(cust_row, len(lines)) if "└" in lines[r]
|
|
320
|
+
)
|
|
321
|
+
order_top = next(
|
|
322
|
+
r for r in range(order_row, -1, -1) if "┌" in lines[r]
|
|
323
|
+
)
|
|
324
|
+
gap_rows = order_top - cust_bottom - 1
|
|
325
|
+
assert gap_rows <= 5, f"inter-layer gap grew to {gap_rows} rows:\n{joined}"
|
|
326
|
+
|
|
327
|
+
|
|
268
328
|
# --------------------------------------------------------------------------
|
|
269
329
|
# Degradation: malformed input, empty body, literal-glyph sanitization
|
|
270
330
|
# --------------------------------------------------------------------------
|
|
@@ -195,9 +195,10 @@ def test_edge_style_bidirectional_golden():
|
|
|
195
195
|
|
|
196
196
|
|
|
197
197
|
def test_pipe_label_golden():
|
|
198
|
-
# The rank-band gap widens
|
|
199
|
-
# mermaid_flow_layout.
|
|
200
|
-
#
|
|
198
|
+
# The rank-band gap widens past the unlabeled _ROW_GAP to a small
|
|
199
|
+
# constant (see mermaid_flow_layout._LABELED_ROW_GAP) so the label gets
|
|
200
|
+
# its own clear row — the constant does not scale with the label's
|
|
201
|
+
# text length.
|
|
201
202
|
assert _lines("graph TD\nA-->|hi|B\n") == [
|
|
202
203
|
"┌───┐",
|
|
203
204
|
"│ A │",
|
|
@@ -205,7 +206,6 @@ def test_pipe_label_golden():
|
|
|
205
206
|
" │",
|
|
206
207
|
" hi",
|
|
207
208
|
" │",
|
|
208
|
-
" │",
|
|
209
209
|
"┌─▼─┐",
|
|
210
210
|
"│ B │",
|
|
211
211
|
"└───┘",
|
|
@@ -213,19 +213,16 @@ def test_pipe_label_golden():
|
|
|
213
213
|
|
|
214
214
|
|
|
215
215
|
def test_inline_label_golden():
|
|
216
|
-
# Same rank-band
|
|
216
|
+
# Same constant rank-band gap as above — "go now" is longer than "hi"
|
|
217
|
+
# but the vertical gap stays the same _LABELED_ROW_GAP; only the
|
|
218
|
+
# label's own row is wider.
|
|
217
219
|
assert _lines("graph TD\nA -- go now --> B\n") == [
|
|
218
220
|
"┌───┐",
|
|
219
221
|
"│ A │",
|
|
220
222
|
"└───┘",
|
|
221
223
|
" │",
|
|
222
|
-
" │",
|
|
223
|
-
" │",
|
|
224
224
|
"go now",
|
|
225
225
|
" │",
|
|
226
|
-
" │",
|
|
227
|
-
" │",
|
|
228
|
-
" │",
|
|
229
226
|
"┌─▼─┐",
|
|
230
227
|
"│ B │",
|
|
231
228
|
"└───┘",
|
|
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
|
|
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
|