text-compositor 0.4.1__tar.gz → 0.4.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.
- {text_compositor-0.4.1 → text_compositor-0.4.2}/PKG-INFO +4 -4
- {text_compositor-0.4.1 → text_compositor-0.4.2}/README.md +3 -3
- {text_compositor-0.4.1 → text_compositor-0.4.2}/pyproject.toml +1 -1
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_bundled_diagram_tools.py +6 -0
- text_compositor-0.4.2/tests/test_cli_single_markdown.py +129 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_emphasis_lint.py +1 -1
- text_compositor-0.4.2/tests/test_html_line_numbers.py +94 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_html_output.py +4 -4
- text_compositor-0.4.2/tests/test_vega_render.py +366 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_worker.py +64 -6
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/api.py +1 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/build.py +67 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/config.py +1 -1
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/deps.py +46 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/env_check.py +1 -1
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/host_renderers.py +13 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/html_output.py +42 -6
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/mermaid.py +61 -24
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/project.py +2 -1
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/renderer.py +5 -1
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/renderer_diagrams.py +111 -2
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/renderer_layout.py +4 -4
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/renderer_tokens.py +1 -1
- text_compositor-0.4.2/text_compositor/vega_render.py +140 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/worker.py +16 -5
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor.egg-info/PKG-INFO +4 -4
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor.egg-info/SOURCES.txt +4 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/LICENSE +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/setup.cfg +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_aggregate_table.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_api.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_asset_resolution.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_bundled_typst_assets.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_cetz_render.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_check_pins.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_common_template.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_config.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_config_key_validation.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_csv_table.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_diagnostics.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_diagram_cache_key.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_diagram_trim.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_distribution.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_doc_badges.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_download.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_front_matter.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_graphviz_render.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_incremental.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_java_encoding.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_layout_blocks.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_line_mapping.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_markdown_basics.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_math_render.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_mermaid_resident.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_pandoc_compat.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_paper_template.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_pikchr_render.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_plantuml_batch.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_renderer_structure.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_revision_history.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_script_references.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_sections.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_soft_break_hints.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_structurizr_render.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_svg_trim.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_table_cell_style.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_variables.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_viewer_version.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/tests/test_watch.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/__init__.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/cetz_render.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/changes.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/chapters.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/compiler.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/diagnostics.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/document.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/emphasis_lint.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/graphviz_render.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/log.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/math_render.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/pikchr_render.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/renderer_inline.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/renderer_tables.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/svg_trim.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/templates/_common.typ +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/templates/paper.typ +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/templates/slide.typ +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/templates/template.typ +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/typst_literal.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor/typst_runtime.py +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor.egg-info/dependency_links.txt +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor.egg-info/entry_points.txt +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor.egg-info/requires.txt +0 -0
- {text_compositor-0.4.1 → text_compositor-0.4.2}/text_compositor.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: text-compositor
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: A tool for composing multiple text files into a PDF document.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Repository, https://github.com/tokudiro/text-compositor
|
|
@@ -87,13 +87,13 @@ This is only needed if your source documents use ` ```mermaid ` fences (or speci
|
|
|
87
87
|
|
|
88
88
|
```bash
|
|
89
89
|
pipx install "text-compositor[mermaid]"
|
|
90
|
-
# or, if already installed: pipx inject text-compositor playwright==1.
|
|
90
|
+
# or, if already installed: pipx inject text-compositor playwright==1.63.0
|
|
91
91
|
```
|
|
92
92
|
|
|
93
93
|
With Option B (clone and run directly):
|
|
94
94
|
|
|
95
95
|
```bash
|
|
96
|
-
pip install playwright==1.
|
|
96
|
+
pip install playwright==1.63.0
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
- **A Google Chrome or Microsoft Edge installation already present on your system** (no new download by default; it's auto-detected and reused at build time)
|
|
@@ -175,7 +175,7 @@ with Session() as session: # reuses the Mermaid browser and the Typst compiler
|
|
|
175
175
|
print(result.ok, result.pdf_path, [d.message for d in result.diagnostics])
|
|
176
176
|
```
|
|
177
177
|
|
|
178
|
-
`session.render_html("doc.md")` (experimental, [#161](https://github.com/tokudiro/text-compositor/issues/161)) writes a self-contained HTML file instead. Diagrams (Mermaid, PlantUML, D2, Structurizr, Graphviz, Pikchr, CeTZ, Fletcher, timeliney, `svg`) become images next to it. `typst-exec` and raw HTML are not supported yet and are shown as code with a warning. Graphviz, Pikchr, CeTZ, Fletcher and timeliney are drawn with the Typst packages `diagraph`, `kip`, `cetz`, `fletcher` and `timeliney`, the same as in the PDF (Graphviz's `shape=record` and a graph-level `label` cannot be drawn and produce a warning).
|
|
178
|
+
`session.render_html("doc.md")` (experimental, [#161](https://github.com/tokudiro/text-compositor/issues/161)) writes a self-contained HTML file instead. Diagrams (Mermaid, PlantUML, D2, Structurizr, Graphviz, Pikchr, CeTZ, Fletcher, timeliney, Vega-Lite, Vega, `svg`) become images next to it. `typst-exec` and raw HTML are not supported yet and are shown as code with a warning. Graphviz, Pikchr, CeTZ, Fletcher and timeliney are drawn with the Typst packages `diagraph`, `kip`, `cetz`, `fletcher` and `timeliney`, the same as in the PDF (Graphviz's `shape=record` and a graph-level `label` cannot be drawn and produce a warning).
|
|
179
179
|
|
|
180
180
|
See [sample/text-compositor.config.yaml](sample/text-compositor.config.yaml) for how to write the config file, and the [usage guide](doc/usage/) for details on `document:`/`plugins:`, front matter, Marp directives, and more. The Markdown files listed in `chapters` are concatenated in order to produce the PDF.
|
|
181
181
|
|
|
@@ -69,13 +69,13 @@ This is only needed if your source documents use ` ```mermaid ` fences (or speci
|
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
71
|
pipx install "text-compositor[mermaid]"
|
|
72
|
-
# or, if already installed: pipx inject text-compositor playwright==1.
|
|
72
|
+
# or, if already installed: pipx inject text-compositor playwright==1.63.0
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
With Option B (clone and run directly):
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
|
-
pip install playwright==1.
|
|
78
|
+
pip install playwright==1.63.0
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
- **A Google Chrome or Microsoft Edge installation already present on your system** (no new download by default; it's auto-detected and reused at build time)
|
|
@@ -157,7 +157,7 @@ with Session() as session: # reuses the Mermaid browser and the Typst compiler
|
|
|
157
157
|
print(result.ok, result.pdf_path, [d.message for d in result.diagnostics])
|
|
158
158
|
```
|
|
159
159
|
|
|
160
|
-
`session.render_html("doc.md")` (experimental, [#161](https://github.com/tokudiro/text-compositor/issues/161)) writes a self-contained HTML file instead. Diagrams (Mermaid, PlantUML, D2, Structurizr, Graphviz, Pikchr, CeTZ, Fletcher, timeliney, `svg`) become images next to it. `typst-exec` and raw HTML are not supported yet and are shown as code with a warning. Graphviz, Pikchr, CeTZ, Fletcher and timeliney are drawn with the Typst packages `diagraph`, `kip`, `cetz`, `fletcher` and `timeliney`, the same as in the PDF (Graphviz's `shape=record` and a graph-level `label` cannot be drawn and produce a warning).
|
|
160
|
+
`session.render_html("doc.md")` (experimental, [#161](https://github.com/tokudiro/text-compositor/issues/161)) writes a self-contained HTML file instead. Diagrams (Mermaid, PlantUML, D2, Structurizr, Graphviz, Pikchr, CeTZ, Fletcher, timeliney, Vega-Lite, Vega, `svg`) become images next to it. `typst-exec` and raw HTML are not supported yet and are shown as code with a warning. Graphviz, Pikchr, CeTZ, Fletcher and timeliney are drawn with the Typst packages `diagraph`, `kip`, `cetz`, `fletcher` and `timeliney`, the same as in the PDF (Graphviz's `shape=record` and a graph-level `label` cannot be drawn and produce a warning).
|
|
161
161
|
|
|
162
162
|
See [sample/text-compositor.config.yaml](sample/text-compositor.config.yaml) for how to write the config file, and the [usage guide](doc/usage/) for details on `document:`/`plugins:`, front matter, Marp directives, and more. The Markdown files listed in `chapters` are concatenated in order to produce the PDF.
|
|
163
163
|
|
|
@@ -143,3 +143,9 @@ def test_build_dist_pins_the_same_java_plantuml_d2_structurizr_and_mermaid_as_th
|
|
|
143
143
|
|
|
144
144
|
assert deps_mod.MERMAID_JS_URL in script
|
|
145
145
|
assert deps_mod.MERMAID_JS_SHA256 in script
|
|
146
|
+
|
|
147
|
+
# Vega・Vega-Lite(#351)
|
|
148
|
+
assert deps_mod.VEGA_JS_URL in script
|
|
149
|
+
assert deps_mod.VEGA_JS_SHA256 in script
|
|
150
|
+
assert deps_mod.VEGA_LITE_JS_URL in script
|
|
151
|
+
assert deps_mod.VEGA_LITE_JS_SHA256 in script
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""CLI: 単一のMarkdownファイルを直接指定してPDFにする(`text-compositor FILE.md`、#179)のテスト。"""
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
import text_compositor.build as build_mod
|
|
8
|
+
from text_compositor import log
|
|
9
|
+
import text_compositor.project as project_mod
|
|
10
|
+
from text_compositor.build import _single_markdown_output, parse_args
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture(autouse=True)
|
|
14
|
+
def _restore_verbosity():
|
|
15
|
+
"""`build()`は、-q/-vを、プロセスグローバルへ反映する。他のテストへ、残さない。"""
|
|
16
|
+
yield
|
|
17
|
+
log.set_verbosity(False, False)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def parse(monkeypatch, *argv):
|
|
21
|
+
monkeypatch.setattr(sys, "argv", ["text-compositor", *argv])
|
|
22
|
+
return parse_args()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestParseArgs:
|
|
26
|
+
def test_a_single_file_with_output_and_template(self, monkeypatch):
|
|
27
|
+
args = parse(monkeypatch, "doc.md", "-o", "out.pdf", "-t", "slide", "-q", "--keep-temp")
|
|
28
|
+
assert (args.markdown, args.output, args.template, args.quiet, args.keep_temp) == ("doc.md", "out.pdf", "slide", True, True)
|
|
29
|
+
|
|
30
|
+
def test_existing_usage_without_a_file_is_unchanged(self, monkeypatch):
|
|
31
|
+
args = parse(monkeypatch, "--config", "c.yaml", "--if-changed")
|
|
32
|
+
assert args.markdown is None and args.config == "c.yaml" and args.if_changed
|
|
33
|
+
|
|
34
|
+
@pytest.mark.parametrize("flags", [
|
|
35
|
+
["--config", "c.yaml"], ["--config-list", "l.txt"], ["--check-env"], ["--watch"], ["--if-changed"],
|
|
36
|
+
["--clean"], ["--clean-cache"],
|
|
37
|
+
])
|
|
38
|
+
def test_options_that_need_a_config_cannot_be_combined_with_a_file(self, monkeypatch, capsys, flags):
|
|
39
|
+
with pytest.raises(SystemExit) as e:
|
|
40
|
+
parse(monkeypatch, "doc.md", *flags)
|
|
41
|
+
assert e.value.code == 2
|
|
42
|
+
assert "同時に指定できません" in capsys.readouterr().err
|
|
43
|
+
|
|
44
|
+
@pytest.mark.parametrize("flags", [["-o", "out.pdf"], ["-t", "slide"]])
|
|
45
|
+
def test_output_and_template_need_a_file(self, monkeypatch, capsys, flags):
|
|
46
|
+
with pytest.raises(SystemExit) as e:
|
|
47
|
+
parse(monkeypatch, *flags)
|
|
48
|
+
assert e.value.code == 2 and "FILE.md" in capsys.readouterr().err
|
|
49
|
+
|
|
50
|
+
def test_only_markdown_files_are_accepted(self, monkeypatch, capsys):
|
|
51
|
+
with pytest.raises(SystemExit) as e:
|
|
52
|
+
parse(monkeypatch, "notes.txt")
|
|
53
|
+
assert e.value.code == 2
|
|
54
|
+
assert parse(monkeypatch, "A.MD").markdown == "A.MD" and parse(monkeypatch, "a.markdown").markdown == "a.markdown"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TestOutputPath:
|
|
58
|
+
def test_the_default_is_next_to_the_manuscript(self, tmp_path):
|
|
59
|
+
md = str(tmp_path / "sub" / "note.md")
|
|
60
|
+
assert _single_markdown_output(md, None) == str(tmp_path / "sub" / "note.pdf")
|
|
61
|
+
|
|
62
|
+
def test_an_explicit_file_and_a_folder(self, tmp_path, monkeypatch):
|
|
63
|
+
monkeypatch.chdir(tmp_path)
|
|
64
|
+
md = str(tmp_path / "note.md")
|
|
65
|
+
assert _single_markdown_output(md, "out.pdf") == str(tmp_path / "out.pdf")
|
|
66
|
+
(tmp_path / "existing").mkdir()
|
|
67
|
+
assert _single_markdown_output(md, "existing") == str(tmp_path / "existing" / "note.pdf")
|
|
68
|
+
assert _single_markdown_output(md, "new" + os.sep) == str(tmp_path / "new" / "note.pdf")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class TestBuildSingleMarkdown:
|
|
72
|
+
@pytest.fixture
|
|
73
|
+
def calls(self, monkeypatch):
|
|
74
|
+
recorded = []
|
|
75
|
+
monkeypatch.setattr(project_mod, "_build_project", lambda *a, **k: recorded.append((a, k)))
|
|
76
|
+
return recorded
|
|
77
|
+
|
|
78
|
+
def run(self, tmp_path, *argv, monkeypatch):
|
|
79
|
+
monkeypatch.setattr(build_mod, "check_typst_version", lambda *a: None)
|
|
80
|
+
monkeypatch.setattr(build_mod, "ensure_fonts", lambda: "fonts")
|
|
81
|
+
monkeypatch.setattr(sys, "argv", ["text-compositor", *argv])
|
|
82
|
+
build_mod.build()
|
|
83
|
+
|
|
84
|
+
def test_it_builds_the_api_config_and_writes_next_to_the_manuscript(self, tmp_path, calls, monkeypatch):
|
|
85
|
+
md = tmp_path / "note.md"
|
|
86
|
+
md.write_text("# T\n", encoding="utf-8")
|
|
87
|
+
self.run(tmp_path, str(md), monkeypatch=monkeypatch)
|
|
88
|
+
(args, kwargs), = calls
|
|
89
|
+
project_dir, config, chapters = args[3], args[4], args[5]
|
|
90
|
+
assert project_dir == str(tmp_path) and chapters == ["note.md"]
|
|
91
|
+
assert config["document"]["cover"] == "markdown" and config["document"]["title"] == "note" # APIと同じ既定
|
|
92
|
+
assert config["template"]["path"] == "template"
|
|
93
|
+
assert kwargs["out_pdf"] == str(tmp_path / "note.pdf")
|
|
94
|
+
|
|
95
|
+
def test_the_template_name_and_a_typ_path_relative_to_the_cwd(self, tmp_path, calls, monkeypatch):
|
|
96
|
+
md = tmp_path / "note.md"
|
|
97
|
+
md.write_text("# T\n", encoding="utf-8")
|
|
98
|
+
monkeypatch.chdir(tmp_path)
|
|
99
|
+
self.run(tmp_path, "note.md", "-t", "paper", monkeypatch=monkeypatch)
|
|
100
|
+
self.run(tmp_path, "note.md", "-t", "my/own.typ", "-o", "build/o.pdf", monkeypatch=monkeypatch)
|
|
101
|
+
assert calls[0][0][4]["template"]["path"] == "paper"
|
|
102
|
+
assert calls[1][0][4]["template"]["path"] == str(tmp_path / "my" / "own.typ")
|
|
103
|
+
assert calls[1][1]["out_pdf"] == str(tmp_path / "build" / "o.pdf") and (tmp_path / "build").is_dir()
|
|
104
|
+
|
|
105
|
+
def test_a_missing_file_and_an_unknown_template_fail_with_exit_code_1(self, tmp_path, calls, monkeypatch, capsys):
|
|
106
|
+
with pytest.raises(SystemExit) as e:
|
|
107
|
+
self.run(tmp_path, str(tmp_path / "nope.md"), monkeypatch=monkeypatch)
|
|
108
|
+
assert e.value.code == 1 and "Markdown file not found" in capsys.readouterr().out
|
|
109
|
+
md = tmp_path / "note.md"
|
|
110
|
+
md.write_text("# T\n", encoding="utf-8")
|
|
111
|
+
with pytest.raises(SystemExit) as e:
|
|
112
|
+
self.run(tmp_path, str(md), "-t", "nosuch", monkeypatch=monkeypatch)
|
|
113
|
+
assert e.value.code == 1 and "Unknown template" in capsys.readouterr().out
|
|
114
|
+
assert calls == []
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_a_real_pdf_is_built_without_a_config(tmp_path, monkeypatch):
|
|
118
|
+
"""実際に、configなしで、PDFができる(Typstとフォントが、ある環境)。先頭の見出しは、表紙に落とされず、本文に残る。"""
|
|
119
|
+
from text_compositor.deps import ensure_fonts
|
|
120
|
+
try:
|
|
121
|
+
ensure_fonts()
|
|
122
|
+
except SystemExit:
|
|
123
|
+
pytest.skip("fonts are not available")
|
|
124
|
+
md = tmp_path / "note.md"
|
|
125
|
+
md.write_text("# 見出し\n\n本文です。\n", encoding="utf-8")
|
|
126
|
+
monkeypatch.setattr(sys, "argv", ["text-compositor", str(md), "-q"])
|
|
127
|
+
build_mod.build()
|
|
128
|
+
pdf = tmp_path / "note.pdf"
|
|
129
|
+
assert pdf.is_file() and pdf.read_bytes().startswith(b"%PDF")
|
|
@@ -93,7 +93,7 @@ class TestHtmlOutput:
|
|
|
93
93
|
def test_the_text_is_still_shown(self, tmp_path):
|
|
94
94
|
result, _ = _html_warnings(tmp_path, "これは**「重要」**です。\n")
|
|
95
95
|
with open(result.html_path, encoding="utf-8") as f:
|
|
96
|
-
assert
|
|
96
|
+
assert '<p data-line="1">これは**「重要」**です。</p>' in f.read()
|
|
97
97
|
|
|
98
98
|
def test_a_table_cell_and_a_list_item_are_checked(self, tmp_path):
|
|
99
99
|
text = "| a | b |\n| - | - |\n| **「x」**です | y |\n\n- 項目**(仮)**です\n"
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""HTML出力の、ブロック要素の`data-line`(原稿の行番号。Viewerが、ツールバーに出す。#328・#357)のテスト。"""
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from text_compositor.api import render_html
|
|
7
|
+
from text_compositor.html_output import DOCUMENT_CSS, _with_data_line
|
|
8
|
+
|
|
9
|
+
PLAIN = {"mermaid": False, "plantuml": False, "d2": False}
|
|
10
|
+
|
|
11
|
+
DOC = """# 見出し
|
|
12
|
+
|
|
13
|
+
段落その1です。
|
|
14
|
+
続きの行。
|
|
15
|
+
|
|
16
|
+
- 項目A
|
|
17
|
+
- 項目B
|
|
18
|
+
- 入れ子B1
|
|
19
|
+
|
|
20
|
+
1. 番号1
|
|
21
|
+
|
|
22
|
+
ゆるい段落
|
|
23
|
+
|
|
24
|
+
> 引用の段落
|
|
25
|
+
|
|
26
|
+
| a | b |
|
|
27
|
+
| - | - |
|
|
28
|
+
| 1 | 2 |
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
print(1)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
$$
|
|
35
|
+
a=b
|
|
36
|
+
$$
|
|
37
|
+
|
|
38
|
+
最後の段落。
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def lines_by_tag(tmp_path, markdown):
|
|
43
|
+
md = tmp_path / "doc.md"
|
|
44
|
+
md.write_text(markdown, encoding="utf-8")
|
|
45
|
+
result = render_html(str(md), plugins=PLAIN)
|
|
46
|
+
assert result.ok, result.diagnostics
|
|
47
|
+
html = open(result.html_path, encoding="utf-8").read()
|
|
48
|
+
body = html[html.index("<body"):]
|
|
49
|
+
return [(m.group(1), int(m.group(2))) for m in re.finditer(r'<(\w+)[^>]*\sdata-line="(\d+)"', body)], body
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_block_elements_carry_their_manuscript_line(tmp_path):
|
|
53
|
+
found, _ = lines_by_tag(tmp_path, DOC)
|
|
54
|
+
assert found == [
|
|
55
|
+
("h1", 1), ("p", 3),
|
|
56
|
+
("li", 6), ("li", 7), ("li", 8), # 緊密なリスト: 項目が持つ(中の段落は、隠れている)
|
|
57
|
+
("p", 10), ("p", 12), # ゆるいリスト: 項目ではなく、中の段落が持つ
|
|
58
|
+
("p", 14), # 引用: 中の段落が持つ
|
|
59
|
+
("table", 16), ("pre", 20), ("div", 24), # 表・フェンス・数式
|
|
60
|
+
("p", 28),
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_a_diagram_fence_carries_the_line_on_its_wrapper(tmp_path):
|
|
65
|
+
svg = tmp_path / "x.svg"
|
|
66
|
+
svg.write_text('<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"></svg>', encoding="utf-8")
|
|
67
|
+
found, body = lines_by_tag(tmp_path, "# T\n\n```svg\n" + svg.read_text(encoding="utf-8") + "\n```\n")
|
|
68
|
+
assert ("div", 3) in found and '<div data-line="3" class="diagram diagram-svg">' in body
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_a_line_is_not_added_twice_or_to_the_wrong_element(tmp_path):
|
|
72
|
+
_, body = lines_by_tag(tmp_path, DOC)
|
|
73
|
+
assert len(re.findall(r'data-line="10"', body)) == 1 and len(re.findall(r'data-line="6"', body)) == 1
|
|
74
|
+
assert "<hr" not in body or 'hr data-line' not in body
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TestWithDataLine:
|
|
78
|
+
def test_it_adds_the_line_to_the_first_tag_only(self):
|
|
79
|
+
assert _with_data_line('<div class="a"><p>x</p></div>\n', 7) == '<div data-line="7" class="a"><p>x</p></div>\n'
|
|
80
|
+
|
|
81
|
+
@pytest.mark.parametrize("html,line", [('<pre>x</pre>', None), ('<pre>x</pre>', 0), ('text', 3), ('', 3)])
|
|
82
|
+
def test_it_leaves_html_without_a_line_or_a_leading_tag_alone(self, html, line):
|
|
83
|
+
assert _with_data_line(html, line) == html
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class TestNoBadgeCss:
|
|
87
|
+
"""行番号は、ツールバーに出す(#357)。文書のCSSには、バッジ(疑似要素)を、持たせない。"""
|
|
88
|
+
|
|
89
|
+
def test_the_document_css_has_no_badge(self):
|
|
90
|
+
assert "data-line-badge" not in DOCUMENT_CSS and "attr(data-line)" not in DOCUMENT_CSS
|
|
91
|
+
|
|
92
|
+
def test_the_document_still_has_no_script(self, tmp_path):
|
|
93
|
+
_, body = lines_by_tag(tmp_path, DOC)
|
|
94
|
+
assert "<script" not in body
|
|
@@ -76,7 +76,7 @@ class TestApi:
|
|
|
76
76
|
assert result.html_path == str(tmp_path / ".text-compositor" / "preview.html")
|
|
77
77
|
assert html.startswith("<!DOCTYPE html>")
|
|
78
78
|
assert "<title>見出し</title>" in html
|
|
79
|
-
assert
|
|
79
|
+
assert '<h1 data-line="1">見出し</h1>' in html
|
|
80
80
|
assert "<script" not in html
|
|
81
81
|
# スクリプトは、ブラウザ側でも禁止する(Viewerは、JavaScriptを有効にしたビューで開く)
|
|
82
82
|
assert "script-src 'none'" in html and "object-src 'none'" in html
|
|
@@ -142,7 +142,7 @@ class TestMarkdown:
|
|
|
142
142
|
assert "<strong>b</strong>" in b and "<em>i</em>" in b and "<s>s</s>" in b and "<code>c</code>" in b
|
|
143
143
|
assert '<a href="https://example.com">l</a>' in b
|
|
144
144
|
assert "<ul>" in b and "<ol>" in b
|
|
145
|
-
assert '<pre><code class="language-python">print(1)\n</code></pre>'
|
|
145
|
+
assert re.search(r'<pre data-line="\d+"><code class="language-python">print\(1\)\n</code></pre>', b)
|
|
146
146
|
|
|
147
147
|
def test_line_breaks_are_kept_like_the_pdf(self, tmp_path):
|
|
148
148
|
_, html = convert(tmp_path, "one\ntwo\n")
|
|
@@ -467,12 +467,12 @@ class TestFences:
|
|
|
467
467
|
|
|
468
468
|
def test_a_disabled_plugin_falls_back_to_code_without_a_warning(self, tmp_path):
|
|
469
469
|
result, html = convert(tmp_path, "```mermaid\ngraph TD\n A-->B\n```\n\n```plantuml\n@startuml\n@enduml\n```\n\n```d2\na -> b\n```\n")
|
|
470
|
-
assert
|
|
470
|
+
assert len(re.findall(r"<pre[ >]", body(html))) == 3 and "<img" not in html
|
|
471
471
|
assert not result.warnings
|
|
472
472
|
|
|
473
473
|
def test_other_languages_are_plain_code_blocks(self, tmp_path):
|
|
474
474
|
_, html = convert(tmp_path, "```\n<b>&\n```\n")
|
|
475
|
-
assert
|
|
475
|
+
assert '<pre data-line="1"><code><b>&\n</code></pre>' in html
|
|
476
476
|
|
|
477
477
|
def test_a_diagram_source_file_is_a_page_with_that_diagram(self, tmp_path):
|
|
478
478
|
result, html = convert(tmp_path, "graph TD\n A-->B\n", name="flow.mmd")
|