termrender 4.3.1__tar.gz → 4.3.2__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.3.1 → termrender-4.3.2}/CHANGELOG.md +20 -0
- {termrender-4.3.1 → termrender-4.3.2}/PKG-INFO +1 -1
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/mermaid.py +13 -3
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/mermaid_journey.py +4 -1
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/mermaid_mindmap.py +10 -6
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/mermaid_timeline.py +4 -1
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_mermaid_dispatch.py +30 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_mermaid_journey.py +57 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_mermaid_mindmap.py +22 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_mermaid_timeline.py +28 -0
- {termrender-4.3.1 → termrender-4.3.2}/.github/workflows/publish.yml +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/.gitignore +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/CLAUDE.md +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/LICENSE +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/README.md +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/design.json +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/pyproject.toml +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/requirements.json +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/scripts/build-mermaid-ascii.sh +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/CLAUDE.md +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/__init__.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/__main__.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/_bin/mermaid-ascii-darwin-arm64 +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/_mermaid_bin.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/blocks.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/emit.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/layout.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/parser.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/py.typed +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/CLAUDE.md +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/__init__.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/borders.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/charts.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/code.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/columns.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/diff.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/divider.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/mermaid_flow_model.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/mermaid_gantt.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/mermaid_pie.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/mermaid_prelude.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/mermaid_sequence.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/panel.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/quote.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/stat.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/table.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/text.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/timeline.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/renderers/tree.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/src/termrender/style.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/__init__.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_charts.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_cli_contract.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_column_alignment.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_diff.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_inline_badge.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_linebreak.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_mermaid_compat.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_mermaid_flow_model.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_mermaid_gantt.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_mermaid_pie.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_mermaid_sequence.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_myst_gaps.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_stat.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_tasklist.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_timeline.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/tests/test_variable_colons.py +0 -0
- {termrender-4.3.1 → termrender-4.3.2}/uv.lock +0 -0
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v4.3.2 (2026-07-06)
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- **mermaid**: Strip %% comments/directives from journey/mindmap/timeline
|
|
9
|
+
([`d61f113`](https://github.com/crouton-labs/termrender/commit/d61f113cf750c9379f00825e2f6fc7b3898e7b8f))
|
|
10
|
+
|
|
11
|
+
The dispatcher (mermaid.py) only used strip_prelude_lines() for type sniffing, then passed the
|
|
12
|
+
untouched original source to the native renderers, so a leading %%{init}%% directive or a mid-body
|
|
13
|
+
%% comment leaked into rendered output as fake tree nodes, task rows, or timeline events in the
|
|
14
|
+
journey/mindmap/timeline renderers.
|
|
15
|
+
|
|
16
|
+
- mermaid.py: pass prelude-stripped source to the three native renderers; the Go-binary fallback
|
|
17
|
+
path keeps receiving the raw source untouched. - mermaid_journey.py, mermaid_mindmap.py,
|
|
18
|
+
mermaid_timeline.py: skip %%-prefixed lines anywhere in the body, not just the leading prelude,
|
|
19
|
+
mirroring mermaid_sequence.py's existing handling.
|
|
20
|
+
|
|
21
|
+
Adds regression tests pinning each confirmed repro to render nothing from the comment/directive.
|
|
22
|
+
|
|
23
|
+
|
|
4
24
|
## v4.3.1 (2026-07-06)
|
|
5
25
|
|
|
6
26
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: termrender
|
|
3
|
-
Version: 4.3.
|
|
3
|
+
Version: 4.3.2
|
|
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
|
|
@@ -139,6 +139,16 @@ def _first_line_type(source: str) -> str:
|
|
|
139
139
|
return first.lower()
|
|
140
140
|
|
|
141
141
|
|
|
142
|
+
def _stripped(source: str) -> str:
|
|
143
|
+
"""Return ``source`` with its leading prelude (comments/directives/
|
|
144
|
+
frontmatter) removed, for native renderers that don't otherwise skip it.
|
|
145
|
+
|
|
146
|
+
The Go-binary fallback path (``_render_via_binary``) must keep receiving
|
|
147
|
+
the untouched original source — its behavior stays byte-for-byte.
|
|
148
|
+
"""
|
|
149
|
+
return "\n".join(strip_prelude_lines(source.splitlines()))
|
|
150
|
+
|
|
151
|
+
|
|
142
152
|
def _render_via_binary(source: str) -> str:
|
|
143
153
|
"""Render via the vendored mermaid-ascii binary, falling back to source.
|
|
144
154
|
|
|
@@ -182,11 +192,11 @@ def render_mermaid_lines(source: str, width: int) -> list[str]:
|
|
|
182
192
|
if diagram_type.startswith("sequencediagram"):
|
|
183
193
|
return mermaid_sequence.render_sequence(source, width)
|
|
184
194
|
if diagram_type.startswith("mindmap"):
|
|
185
|
-
return mermaid_mindmap.render(source, width)
|
|
195
|
+
return mermaid_mindmap.render(_stripped(source), width)
|
|
186
196
|
if diagram_type.startswith("journey"):
|
|
187
|
-
return mermaid_journey.render(source, width)
|
|
197
|
+
return mermaid_journey.render(_stripped(source), width)
|
|
188
198
|
if diagram_type.startswith("timeline"):
|
|
189
|
-
return mermaid_timeline.render(source, width)
|
|
199
|
+
return mermaid_timeline.render(_stripped(source), width)
|
|
190
200
|
return _render_via_binary(source).split("\n")
|
|
191
201
|
|
|
192
202
|
|
|
@@ -40,7 +40,8 @@ def parse_journey(source: str) -> dict:
|
|
|
40
40
|
still captured with ``score=None``; a non-numeric score is likewise
|
|
41
41
|
tolerated (``score=None``) rather than raising. A task line before any
|
|
42
42
|
``section`` header lands in an unnamed leading section. Sections with
|
|
43
|
-
no tasks are dropped.
|
|
43
|
+
no tasks are dropped. ``%%`` comment and directive lines are skipped
|
|
44
|
+
wherever they appear in the body, not just in the leading prelude.
|
|
44
45
|
|
|
45
46
|
Returns ``{"title": str | None, "sections": [{"name": str | None,
|
|
46
47
|
"tasks": [{"name": str, "score": int | None, "actors": list[str]}]}]}``.
|
|
@@ -51,6 +52,8 @@ def parse_journey(source: str) -> dict:
|
|
|
51
52
|
for line in source.splitlines():
|
|
52
53
|
if not line.strip() or _HEADER_RE.match(line):
|
|
53
54
|
continue
|
|
55
|
+
if line.strip().startswith("%%"):
|
|
56
|
+
continue
|
|
54
57
|
|
|
55
58
|
m = _TITLE_RE.match(line)
|
|
56
59
|
if m:
|
|
@@ -64,12 +64,14 @@ def _node_label(text: str) -> str:
|
|
|
64
64
|
def parse_mindmap(source: str) -> str:
|
|
65
65
|
"""Parse mermaid mindmap syntax into ``tree.py``'s plain indented format.
|
|
66
66
|
|
|
67
|
-
Drops the ``mindmap`` header line
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
Drops the ``mindmap`` header line, any ``%%`` comment/directive line
|
|
68
|
+
(wherever it appears in the body, not just in the leading prelude), and
|
|
69
|
+
any ``::icon(...)``/``:::class`` decoration lines (termrender's tree has
|
|
70
|
+
no icon/class concept); every other line's original indentation is
|
|
71
|
+
preserved (depth comes from indentation, exactly as ``tree.py`` already
|
|
72
|
+
expects) with its node-shape delimiters stripped down to label text.
|
|
73
|
+
Returns the transformed multi-line indented string, empty if nothing
|
|
74
|
+
survived.
|
|
73
75
|
"""
|
|
74
76
|
out_lines: list[str] = []
|
|
75
77
|
for raw_line in source.splitlines():
|
|
@@ -78,6 +80,8 @@ def parse_mindmap(source: str) -> str:
|
|
|
78
80
|
if _HEADER_RE.match(raw_line):
|
|
79
81
|
continue
|
|
80
82
|
stripped = raw_line.strip()
|
|
83
|
+
if stripped.startswith("%%"):
|
|
84
|
+
continue
|
|
81
85
|
if _ICON_RE.match(stripped) or _CLASS_RE.match(stripped):
|
|
82
86
|
continue
|
|
83
87
|
indent = raw_line[: len(raw_line) - len(raw_line.lstrip(" "))]
|
|
@@ -44,7 +44,8 @@ def parse_timeline(source: str) -> dict:
|
|
|
44
44
|
|
|
45
45
|
Returns ``{"title": str | None, "sections": [{"name": str | None,
|
|
46
46
|
"entries": [{"date": str, "event": str}]}]}``. Sections with no
|
|
47
|
-
entries are dropped.
|
|
47
|
+
entries are dropped. ``%%`` comment and directive lines are skipped
|
|
48
|
+
wherever they appear in the body, not just in the leading prelude.
|
|
48
49
|
"""
|
|
49
50
|
title: str | None = None
|
|
50
51
|
sections: list[dict] = [{"name": None, "entries": []}]
|
|
@@ -53,6 +54,8 @@ def parse_timeline(source: str) -> dict:
|
|
|
53
54
|
for line in source.splitlines():
|
|
54
55
|
if not line.strip() or _HEADER_RE.match(line):
|
|
55
56
|
continue
|
|
57
|
+
if line.strip().startswith("%%"):
|
|
58
|
+
continue
|
|
56
59
|
|
|
57
60
|
m = _TITLE_RE.match(line)
|
|
58
61
|
if m:
|
|
@@ -190,6 +190,36 @@ class TestMermaidDispatch(unittest.TestCase):
|
|
|
190
190
|
self.assertIn("Launch", joined)
|
|
191
191
|
self.assertIn("●", joined)
|
|
192
192
|
|
|
193
|
+
def test_init_directive_does_not_leak_into_journey_output(self):
|
|
194
|
+
# Regression: the dispatcher must pass prelude-stripped source to
|
|
195
|
+
# the native journey renderer, not just use the prelude for sniffing.
|
|
196
|
+
src = (
|
|
197
|
+
'%%{init: {"theme": "dark"}}%%\n'
|
|
198
|
+
"journey\n"
|
|
199
|
+
" title T\n"
|
|
200
|
+
" section S\n"
|
|
201
|
+
" Task: 4: Me\n"
|
|
202
|
+
)
|
|
203
|
+
joined = "\n".join(mermaid.render_mermaid_lines(src, 60))
|
|
204
|
+
self.assertNotIn("init", joined)
|
|
205
|
+
self.assertNotIn("theme", joined)
|
|
206
|
+
self.assertNotIn("%%", joined)
|
|
207
|
+
self.assertIn("Task", joined)
|
|
208
|
+
|
|
209
|
+
def test_init_directive_does_not_leak_into_mindmap_output(self):
|
|
210
|
+
src = '%%{init: {"theme": "dark"}}%%\nmindmap\n root\n Origins\n'
|
|
211
|
+
joined = "\n".join(mermaid.render_mermaid_lines(src, 60))
|
|
212
|
+
self.assertNotIn("init", joined)
|
|
213
|
+
self.assertNotIn("%%", joined)
|
|
214
|
+
self.assertIn("Origins", joined)
|
|
215
|
+
|
|
216
|
+
def test_init_directive_does_not_leak_into_timeline_output(self):
|
|
217
|
+
src = '%%{init: {"theme": "dark"}}%%\ntimeline\n 2024 : Launch\n'
|
|
218
|
+
joined = "\n".join(mermaid.render_mermaid_lines(src, 60))
|
|
219
|
+
self.assertNotIn("init", joined)
|
|
220
|
+
self.assertNotIn("%%", joined)
|
|
221
|
+
self.assertIn("Launch", joined)
|
|
222
|
+
|
|
193
223
|
|
|
194
224
|
if __name__ == "__main__":
|
|
195
225
|
unittest.main()
|
|
@@ -59,6 +59,29 @@ class TestParseJourney(unittest.TestCase):
|
|
|
59
59
|
parsed = parse_journey("journey\n title Empty Journey\n")
|
|
60
60
|
self.assertEqual(parsed["sections"], [])
|
|
61
61
|
|
|
62
|
+
def test_init_directive_before_header_skipped(self):
|
|
63
|
+
parsed = parse_journey(
|
|
64
|
+
'%%{init: {"theme": "dark"}}%%\n'
|
|
65
|
+
"journey\n"
|
|
66
|
+
" title T\n"
|
|
67
|
+
" section S\n"
|
|
68
|
+
" Task: 4: Me\n"
|
|
69
|
+
)
|
|
70
|
+
self.assertEqual(parsed["title"], "T")
|
|
71
|
+
self.assertEqual(len(parsed["sections"]), 1)
|
|
72
|
+
self.assertEqual(parsed["sections"][0]["tasks"], [{"name": "Task", "score": 4, "actors": ["Me"]}])
|
|
73
|
+
|
|
74
|
+
def test_mid_body_comment_skipped(self):
|
|
75
|
+
parsed = parse_journey(
|
|
76
|
+
"journey\n"
|
|
77
|
+
" section S\n"
|
|
78
|
+
" Task: 4: Me\n"
|
|
79
|
+
" %% mid comment: 3: X\n"
|
|
80
|
+
" Other: 2: Me\n"
|
|
81
|
+
)
|
|
82
|
+
names = [t["name"] for t in parsed["sections"][0]["tasks"]]
|
|
83
|
+
self.assertEqual(names, ["Task", "Other"])
|
|
84
|
+
|
|
62
85
|
|
|
63
86
|
class TestRenderJourney(unittest.TestCase):
|
|
64
87
|
|
|
@@ -123,6 +146,40 @@ class TestRenderJourney(unittest.TestCase):
|
|
|
123
146
|
if ln:
|
|
124
147
|
self.assertEqual(visual_len(ln), 50)
|
|
125
148
|
|
|
149
|
+
def test_init_directive_produces_no_fake_task_row(self):
|
|
150
|
+
# Regression: a leading %%{init}%% directive must not leak into the
|
|
151
|
+
# rendered output as a garbled task row.
|
|
152
|
+
src = (
|
|
153
|
+
'%%{init: {"theme": "dark"}}%%\n'
|
|
154
|
+
"journey\n"
|
|
155
|
+
" title T\n"
|
|
156
|
+
" section S\n"
|
|
157
|
+
" Task: 4: Me\n"
|
|
158
|
+
)
|
|
159
|
+
lines = render_journey(src, width=40)
|
|
160
|
+
joined = "\n".join(lines)
|
|
161
|
+
self.assertNotIn("init", joined)
|
|
162
|
+
self.assertNotIn("theme", joined)
|
|
163
|
+
self.assertNotIn("%%", joined)
|
|
164
|
+
self.assertIn("Task", joined)
|
|
165
|
+
|
|
166
|
+
def test_mid_body_comment_produces_no_fake_task_row(self):
|
|
167
|
+
# Regression: a %% comment line anywhere in the body must not
|
|
168
|
+
# render as a task with stars.
|
|
169
|
+
src = (
|
|
170
|
+
"journey\n"
|
|
171
|
+
" section S\n"
|
|
172
|
+
" Task: 4: Me\n"
|
|
173
|
+
" %% mid comment: 3: X\n"
|
|
174
|
+
" Other: 2: Me\n"
|
|
175
|
+
)
|
|
176
|
+
lines = render_journey(src, width=40)
|
|
177
|
+
joined = "\n".join(lines)
|
|
178
|
+
self.assertNotIn("comment", joined)
|
|
179
|
+
self.assertNotIn("%%", joined)
|
|
180
|
+
self.assertIn("Task", joined)
|
|
181
|
+
self.assertIn("Other", joined)
|
|
182
|
+
|
|
126
183
|
|
|
127
184
|
if __name__ == "__main__":
|
|
128
185
|
unittest.main()
|
|
@@ -66,6 +66,18 @@ class TestParseMindmap(unittest.TestCase):
|
|
|
66
66
|
def test_only_header_yields_empty(self):
|
|
67
67
|
self.assertEqual(parse_mindmap("mindmap\n"), "")
|
|
68
68
|
|
|
69
|
+
def test_indented_comment_line_dropped(self):
|
|
70
|
+
out = parse_mindmap("mindmap\n root\n %% a comment\n Origins\n")
|
|
71
|
+
self.assertNotIn("comment", out)
|
|
72
|
+
self.assertNotIn("%%", out)
|
|
73
|
+
self.assertIn("Origins", out)
|
|
74
|
+
|
|
75
|
+
def test_init_directive_before_header_dropped(self):
|
|
76
|
+
out = parse_mindmap('%%{init: {"theme": "dark"}}%%\nmindmap\n root\n Origins\n')
|
|
77
|
+
self.assertNotIn("init", out)
|
|
78
|
+
self.assertNotIn("%%", out)
|
|
79
|
+
self.assertIn("Origins", out)
|
|
80
|
+
|
|
69
81
|
|
|
70
82
|
class TestRenderMindmap(unittest.TestCase):
|
|
71
83
|
|
|
@@ -110,6 +122,16 @@ class TestRenderMindmap(unittest.TestCase):
|
|
|
110
122
|
if ln:
|
|
111
123
|
self.assertEqual(visual_len(ln), 45)
|
|
112
124
|
|
|
125
|
+
def test_indented_comment_produces_no_fake_tree_node(self):
|
|
126
|
+
# Regression: an indented %% comment line must not render as a
|
|
127
|
+
# tree node.
|
|
128
|
+
src = "mindmap\n root\n %% a comment\n Origins\n"
|
|
129
|
+
lines = render_mindmap(src, width=40)
|
|
130
|
+
joined = "\n".join(lines)
|
|
131
|
+
self.assertNotIn("comment", joined)
|
|
132
|
+
self.assertNotIn("%%", joined)
|
|
133
|
+
self.assertIn("Origins", joined)
|
|
134
|
+
|
|
113
135
|
|
|
114
136
|
if __name__ == "__main__":
|
|
115
137
|
unittest.main()
|
|
@@ -53,6 +53,23 @@ class TestParseTimeline(unittest.TestCase):
|
|
|
53
53
|
parsed = parse_timeline("timeline\n title Empty\n")
|
|
54
54
|
self.assertEqual(parsed["sections"], [])
|
|
55
55
|
|
|
56
|
+
def test_comment_line_skipped(self):
|
|
57
|
+
parsed = parse_timeline(
|
|
58
|
+
"timeline\n 2002 : LinkedIn\n %% comment : fake\n 2004 : Facebook\n"
|
|
59
|
+
)
|
|
60
|
+
entries = parsed["sections"][0]["entries"]
|
|
61
|
+
self.assertEqual(
|
|
62
|
+
entries,
|
|
63
|
+
[{"date": "2002", "event": "LinkedIn"}, {"date": "2004", "event": "Facebook"}],
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def test_init_directive_before_header_skipped(self):
|
|
67
|
+
parsed = parse_timeline(
|
|
68
|
+
'%%{init: {"theme": "dark"}}%%\ntimeline\n 2002 : LinkedIn\n'
|
|
69
|
+
)
|
|
70
|
+
entries = parsed["sections"][0]["entries"]
|
|
71
|
+
self.assertEqual(entries, [{"date": "2002", "event": "LinkedIn"}])
|
|
72
|
+
|
|
56
73
|
|
|
57
74
|
class TestRenderTimeline(unittest.TestCase):
|
|
58
75
|
|
|
@@ -102,6 +119,17 @@ class TestRenderTimeline(unittest.TestCase):
|
|
|
102
119
|
if ln:
|
|
103
120
|
self.assertEqual(visual_len(ln), 50)
|
|
104
121
|
|
|
122
|
+
def test_comment_produces_no_fake_timeline_event(self):
|
|
123
|
+
# Regression: a %% comment line must not render as a timeline event.
|
|
124
|
+
src = "timeline\n 2002 : LinkedIn\n %% comment : fake\n 2004 : Facebook\n"
|
|
125
|
+
lines = render_timeline(src, width=40)
|
|
126
|
+
joined = "\n".join(lines)
|
|
127
|
+
self.assertNotIn("comment", joined)
|
|
128
|
+
self.assertNotIn("fake", joined)
|
|
129
|
+
self.assertNotIn("%%", joined)
|
|
130
|
+
self.assertIn("LinkedIn", joined)
|
|
131
|
+
self.assertIn("Facebook", joined)
|
|
132
|
+
|
|
105
133
|
|
|
106
134
|
if __name__ == "__main__":
|
|
107
135
|
unittest.main()
|
|
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
|