icplot 0.3.3__tar.gz → 0.4.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.
- {icplot-0.3.3/src/icplot.egg-info → icplot-0.4.0}/PKG-INFO +41 -9
- {icplot-0.3.3 → icplot-0.4.0}/README.md +19 -2
- {icplot-0.3.3 → icplot-0.4.0}/pyproject.toml +48 -14
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/converter/image.py +6 -1
- icplot-0.4.0/src/icplot/renderer/config.py +73 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/renderer/formats/tex.py +3 -1
- icplot-0.4.0/src/icplot/renderer/frontmatter.py +62 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/renderer/graph/matplotlib.py +2 -2
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/renderer/render.py +86 -44
- {icplot-0.3.3 → icplot-0.4.0/src/icplot.egg-info}/PKG-INFO +41 -9
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot.egg-info/SOURCES.txt +2 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot.egg-info/requires.txt +27 -5
- icplot-0.4.0/test/test_frontmatter.py +52 -0
- icplot-0.4.0/test/test_image.py +37 -0
- icplot-0.3.3/src/icplot/renderer/config.py +0 -41
- icplot-0.3.3/test/test_image.py +0 -21
- {icplot-0.3.3 → icplot-0.4.0}/LICENSE +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/setup.cfg +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/__init__.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/charts/__init__.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/charts/gantt.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/charts/tree.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/converter/__init__.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/converter/cli.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/converter/convert.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/geometry/__init__.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/geometry/base.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/geometry/mesh/__init__.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/geometry/mesh/cell.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/geometry/mesh/edge.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/geometry/mesh/mesh.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/geometry/mesh/openfoam/__init__.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/geometry/mesh/openfoam/blockmesh.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/geometry/mesh/openfoam/foamfile.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/geometry/mesh/openfoam/polymesh.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/geometry/mesh/operations.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/geometry/mesh/shapes.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/geometry/mesh/vertex.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/geometry/mesh/vtk.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/geometry/operations.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/geometry/shapes.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/geometry/transform.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/graph/__init__.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/graph/axis.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/graph/plot.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/graph/plot_group.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/graph/series.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/main_cli.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/py.typed +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/renderer/__init__.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/renderer/charts/pgfgantt.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/renderer/charts/svggantt.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/renderer/cli.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/renderer/formats/__init__.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/renderer/formats/mermaid.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/renderer/formats/plantuml.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/renderer/graph/__init__.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/renderer/graph/color.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/renderer/graph/mpl.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/renderer/graph/renderer.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/renderer/graph/vtk.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/renderer/scene/cairo.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/renderer/video.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/scene.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/theme/__init__.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/theme/cli.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/theme/color.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/theme/templates/puml.j2 +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/theme/templates/tikz.j2 +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/theme/theme.json +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/theme/theme.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot/theme/theme_generator.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot.egg-info/dependency_links.txt +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot.egg-info/entry_points.txt +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/src/icplot.egg-info/top_level.txt +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/test/test_cairo.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/test/test_gantt_chart.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/test/test_geometry.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/test/test_line_plots.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/test/test_mermaid.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/test/test_meshing.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/test/test_openfoam.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/test/test_plot_group.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/test/test_polymesh.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/test/test_puml.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/test/test_tex.py +0 -0
- {icplot-0.3.3 → icplot-0.4.0}/test/test_tree.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: icplot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Utilities for generating plots and graphics for technical reports.
|
|
5
5
|
Author-email: "James Grogan, Irish Centre for High End Computing" <james.grogan@ichec.ie>
|
|
6
6
|
Project-URL: Repository, https://git.ichec.ie/performance/toolshed/icplot
|
|
@@ -14,11 +14,6 @@ Classifier: Topic :: System :: Distributed Computing
|
|
|
14
14
|
Requires-Python: >=3.10
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
License-File: LICENSE
|
|
17
|
-
Requires-Dist: wand
|
|
18
|
-
Requires-Dist: matplotlib
|
|
19
|
-
Requires-Dist: vtk
|
|
20
|
-
Requires-Dist: moviepy
|
|
21
|
-
Requires-Dist: scipy
|
|
22
17
|
Requires-Dist: iccore==0.2.3
|
|
23
18
|
Requires-Dist: graphviz
|
|
24
19
|
Requires-Dist: Jinja2
|
|
@@ -31,9 +26,29 @@ Requires-Dist: isort; extra == "test"
|
|
|
31
26
|
Requires-Dist: mypy; extra == "test"
|
|
32
27
|
Requires-Dist: flake8; extra == "test"
|
|
33
28
|
Requires-Dist: pylint; extra == "test"
|
|
29
|
+
Requires-Dist: types-PyYAML; extra == "test"
|
|
34
30
|
Provides-Extra: cairo
|
|
35
|
-
Requires-Dist: pycairo; extra == "cairo"
|
|
36
31
|
Requires-Dist: CairoSVG; extra == "cairo"
|
|
32
|
+
Provides-Extra: scene
|
|
33
|
+
Requires-Dist: pycairo; extra == "scene"
|
|
34
|
+
Provides-Extra: imagemagick
|
|
35
|
+
Requires-Dist: wand; extra == "imagemagick"
|
|
36
|
+
Provides-Extra: plot
|
|
37
|
+
Requires-Dist: matplotlib; extra == "plot"
|
|
38
|
+
Provides-Extra: vtk
|
|
39
|
+
Requires-Dist: vtk; extra == "vtk"
|
|
40
|
+
Provides-Extra: geometry
|
|
41
|
+
Requires-Dist: scipy; extra == "geometry"
|
|
42
|
+
Provides-Extra: video
|
|
43
|
+
Requires-Dist: moviepy; extra == "video"
|
|
44
|
+
Provides-Extra: all
|
|
45
|
+
Requires-Dist: CairoSVG; extra == "all"
|
|
46
|
+
Requires-Dist: pycairo; extra == "all"
|
|
47
|
+
Requires-Dist: wand; extra == "all"
|
|
48
|
+
Requires-Dist: matplotlib; extra == "all"
|
|
49
|
+
Requires-Dist: vtk; extra == "all"
|
|
50
|
+
Requires-Dist: scipy; extra == "all"
|
|
51
|
+
Requires-Dist: moviepy; extra == "all"
|
|
37
52
|
Dynamic: license-file
|
|
38
53
|
|
|
39
54
|
# icplot
|
|
@@ -121,13 +136,30 @@ The library supports themeing with an Angular Material 3 motivated design system
|
|
|
121
136
|
|
|
122
137
|
# Installation #
|
|
123
138
|
|
|
124
|
-
The package is available on PyPI.
|
|
139
|
+
The package is available on PyPI. The core install is deliberately light — it
|
|
140
|
+
does not pull the heavy rendering backends, so consumers that only need part of
|
|
141
|
+
the library (e.g. a docs build that just converts svg to png) stay small:
|
|
125
142
|
|
|
126
143
|
``` shell
|
|
127
144
|
pip install icplot
|
|
128
145
|
```
|
|
129
146
|
|
|
130
|
-
|
|
147
|
+
Backends are opt-in via extras:
|
|
148
|
+
|
|
149
|
+
| Extra | Pulls | Enables |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| `cairo` | CairoSVG | svg → png / pdf conversion |
|
|
152
|
+
| `scene` | pycairo | vector scene drawing (`renderer.scene.cairo`) |
|
|
153
|
+
| `imagemagick` | wand | pdf → png conversion (falls back to the `magick` CLI) |
|
|
154
|
+
| `plot` | matplotlib | 2D plotting (`renderer.graph`, `charts`) |
|
|
155
|
+
| `vtk` | vtk | 3D / mesh rendering (`geometry.mesh`) |
|
|
156
|
+
| `geometry` | scipy | geometry transforms (`geometry.operations`) |
|
|
157
|
+
| `video` | moviepy | video assembly (`renderer.video`) |
|
|
158
|
+
| `all` | all of the above | everything (previous default behaviour) |
|
|
159
|
+
|
|
160
|
+
For example, `pip install icplot[cairo]` for svg conversion, or
|
|
161
|
+
`pip install icplot[all]` for the full toolkit. The native `cairo` and
|
|
162
|
+
`imagemagick` system libraries are also required for those backends; on Mac:
|
|
131
163
|
|
|
132
164
|
``` shell
|
|
133
165
|
brew install imagemagick cairo
|
|
@@ -83,13 +83,30 @@ The library supports themeing with an Angular Material 3 motivated design system
|
|
|
83
83
|
|
|
84
84
|
# Installation #
|
|
85
85
|
|
|
86
|
-
The package is available on PyPI.
|
|
86
|
+
The package is available on PyPI. The core install is deliberately light — it
|
|
87
|
+
does not pull the heavy rendering backends, so consumers that only need part of
|
|
88
|
+
the library (e.g. a docs build that just converts svg to png) stay small:
|
|
87
89
|
|
|
88
90
|
``` shell
|
|
89
91
|
pip install icplot
|
|
90
92
|
```
|
|
91
93
|
|
|
92
|
-
|
|
94
|
+
Backends are opt-in via extras:
|
|
95
|
+
|
|
96
|
+
| Extra | Pulls | Enables |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| `cairo` | CairoSVG | svg → png / pdf conversion |
|
|
99
|
+
| `scene` | pycairo | vector scene drawing (`renderer.scene.cairo`) |
|
|
100
|
+
| `imagemagick` | wand | pdf → png conversion (falls back to the `magick` CLI) |
|
|
101
|
+
| `plot` | matplotlib | 2D plotting (`renderer.graph`, `charts`) |
|
|
102
|
+
| `vtk` | vtk | 3D / mesh rendering (`geometry.mesh`) |
|
|
103
|
+
| `geometry` | scipy | geometry transforms (`geometry.operations`) |
|
|
104
|
+
| `video` | moviepy | video assembly (`renderer.video`) |
|
|
105
|
+
| `all` | all of the above | everything (previous default behaviour) |
|
|
106
|
+
|
|
107
|
+
For example, `pip install icplot[cairo]` for svg conversion, or
|
|
108
|
+
`pip install icplot[all]` for the full toolkit. The native `cairo` and
|
|
109
|
+
`imagemagick` system libraries are also required for those backends; on Mac:
|
|
93
110
|
|
|
94
111
|
``` shell
|
|
95
112
|
brew install imagemagick cairo
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "icplot"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="James Grogan, Irish Centre for High End Computing", email="james.grogan@ichec.ie" },
|
|
6
6
|
]
|
|
@@ -16,14 +16,14 @@ classifiers = [
|
|
|
16
16
|
]
|
|
17
17
|
keywords = ["Publishing", "Technical Reports", "Graphics"]
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
# Core is deliberately light: only what every import path needs. The heavy
|
|
20
|
+
# rendering backends (matplotlib/vtk/scipy/moviepy/wand) are optional extras
|
|
21
|
+
# so consumers that only use the converter or diagram renderer (e.g. the
|
|
22
|
+
# handbook build) can install a small footprint. Use icplot[all] for the
|
|
23
|
+
# previous behaviour of pulling every backend.
|
|
24
|
+
dependencies = ["iccore==0.2.3",
|
|
25
|
+
"graphviz",
|
|
26
|
+
"Jinja2"]
|
|
27
27
|
|
|
28
28
|
[project.urls]
|
|
29
29
|
Repository = "https://git.ichec.ie/performance/toolshed/icplot"
|
|
@@ -38,14 +38,47 @@ test = [
|
|
|
38
38
|
"isort",
|
|
39
39
|
"mypy",
|
|
40
40
|
"flake8",
|
|
41
|
-
"pylint"
|
|
41
|
+
"pylint",
|
|
42
|
+
"types-PyYAML"
|
|
42
43
|
]
|
|
43
44
|
|
|
45
|
+
# svg/pdf conversion (converter.svg_to_png / svg_to_pdf). CairoSVG pulls
|
|
46
|
+
# cairocffi, which dlopens libcairo at runtime (no C build step needed).
|
|
44
47
|
cairo = [
|
|
45
|
-
"pycairo",
|
|
46
48
|
"CairoSVG"
|
|
47
49
|
]
|
|
48
50
|
|
|
51
|
+
# vector scene drawing (renderer.scene.cairo). pycairo (module `cairo`)
|
|
52
|
+
# builds against libcairo — needs the cairo headers + a C toolchain at install.
|
|
53
|
+
scene = ["pycairo"]
|
|
54
|
+
|
|
55
|
+
# raster conversion via ImageMagick bindings (converter.pdf_to_png);
|
|
56
|
+
# falls back to the `magick` CLI if wand is absent
|
|
57
|
+
imagemagick = ["wand"]
|
|
58
|
+
|
|
59
|
+
# 2D plotting backend (icplot.renderer.graph / icplot.charts)
|
|
60
|
+
plot = ["matplotlib"]
|
|
61
|
+
|
|
62
|
+
# 3D / mesh backend (icplot.geometry.mesh, vtk graph backend)
|
|
63
|
+
vtk = ["vtk"]
|
|
64
|
+
|
|
65
|
+
# geometry transforms (icplot.geometry.operations)
|
|
66
|
+
geometry = ["scipy"]
|
|
67
|
+
|
|
68
|
+
# video assembly (icplot.renderer.video)
|
|
69
|
+
video = ["moviepy"]
|
|
70
|
+
|
|
71
|
+
# convenience aggregate — every backend (pre-split behaviour)
|
|
72
|
+
all = [
|
|
73
|
+
"CairoSVG",
|
|
74
|
+
"pycairo",
|
|
75
|
+
"wand",
|
|
76
|
+
"matplotlib",
|
|
77
|
+
"vtk",
|
|
78
|
+
"scipy",
|
|
79
|
+
"moviepy"
|
|
80
|
+
]
|
|
81
|
+
|
|
49
82
|
[build-system]
|
|
50
83
|
requires = ["setuptools", "wheel"]
|
|
51
84
|
build-backend = "setuptools.build_meta"
|
|
@@ -113,12 +146,13 @@ addopts = "--cov=icplot --cov-report term --cov-fail-under=55 --cov-report xml:c
|
|
|
113
146
|
|
|
114
147
|
[tool.tox]
|
|
115
148
|
requires = ["tox"]
|
|
116
|
-
env_list = ["format_check", "lint", "style", "type", "3.10", "3.
|
|
149
|
+
env_list = ["format_check", "lint", "style", "type", "3.10", "3.14", "docs"]
|
|
117
150
|
skip_missing_interpreters = true
|
|
118
151
|
|
|
119
152
|
[tool.tox.env_run_base]
|
|
120
153
|
description = "Run Unit and Integration Tests"
|
|
121
|
-
|
|
154
|
+
# `all` so the full suite (matplotlib/vtk/scipy/moviepy backends) still runs
|
|
155
|
+
deps = [".[test, all]"]
|
|
122
156
|
commands = [["pytest", "test"]]
|
|
123
157
|
|
|
124
158
|
[tool.tox.env.format_apply]
|
|
@@ -153,7 +187,7 @@ commands = [["flake8", "src"]]
|
|
|
153
187
|
|
|
154
188
|
[tool.tox.env.type]
|
|
155
189
|
description = "Run Type Checks"
|
|
156
|
-
deps = ["mypy", ".[
|
|
190
|
+
deps = ["mypy", ".[test]"]
|
|
157
191
|
commands = [["mypy", "src"]]
|
|
158
192
|
|
|
159
193
|
[tool.tox.env.docs]
|
|
@@ -45,12 +45,17 @@ def _get_out_filename(source: Path, target: Path | None, extension: str) -> Path
|
|
|
45
45
|
return source.parent / f"{source.stem}.{extension}"
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
# The order of ImageMagick arguments was changed so that `-alpha off`
|
|
49
|
+
# is applied after loading the source file rather than before.
|
|
50
|
+
# This ensures proper handling when converting PDFs/SVGs,
|
|
51
|
+
# as some versions of ImageMagick ignore or misapply `-alpha off` if it
|
|
52
|
+
# appears before the input file.
|
|
48
53
|
def _pdf_to_png(source: Path, target: Path | None = None, resolution: int = 300):
|
|
49
54
|
|
|
50
55
|
outfile = _get_out_filename(source, target, "png")
|
|
51
56
|
os.makedirs(outfile.parent, exist_ok=True)
|
|
52
57
|
cmd = (
|
|
53
|
-
f"magick -density {resolution} -background white -alpha off {
|
|
58
|
+
f"magick -density {resolution} -background white {source} -alpha off {outfile}"
|
|
54
59
|
)
|
|
55
60
|
subprocess.run(cmd, shell=True, check=True)
|
|
56
61
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TexConfig(BaseModel, frozen=True):
|
|
10
|
+
"""
|
|
11
|
+
This holds settings for the Tex build
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
build_engine: str = "pdflatex"
|
|
15
|
+
nonstopmode: bool = True
|
|
16
|
+
use_woff_fonts: bool = True
|
|
17
|
+
templates: list[Path] = []
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PlantUmlConfig(BaseModel, frozen=True):
|
|
21
|
+
config_path: Path | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MermaidConfig(BaseModel, frozen=True):
|
|
25
|
+
browser_config: Path | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class RenderConfig(BaseModel, frozen=True):
|
|
29
|
+
tex: TexConfig = TexConfig()
|
|
30
|
+
plantuml: PlantUmlConfig = PlantUmlConfig()
|
|
31
|
+
mermaid: MermaidConfig = MermaidConfig()
|
|
32
|
+
extra_config: Path | None = None
|
|
33
|
+
input_formats: list[str] = []
|
|
34
|
+
output_formats: list[str] = []
|
|
35
|
+
excludes: list[str] = []
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def apply_frontmatter(config: "RenderConfig", frontmatter: dict) -> "RenderConfig":
|
|
39
|
+
"""
|
|
40
|
+
Return a copy of *config* with per-file overrides from *frontmatter* applied.
|
|
41
|
+
|
|
42
|
+
Recognised keys
|
|
43
|
+
---------------
|
|
44
|
+
theme : str
|
|
45
|
+
``"none"`` — suppress the global plantuml config_path for this file.
|
|
46
|
+
Any other value (or absent) — leave config_path unchanged.
|
|
47
|
+
meta : dict
|
|
48
|
+
Stored for future use (SVG metadata injection). Currently a no-op.
|
|
49
|
+
"""
|
|
50
|
+
if not frontmatter:
|
|
51
|
+
return config
|
|
52
|
+
|
|
53
|
+
updated_plantuml = config.plantuml
|
|
54
|
+
|
|
55
|
+
theme = frontmatter.get("theme")
|
|
56
|
+
if theme == "none":
|
|
57
|
+
updated_plantuml = config.plantuml.model_copy(update={"config_path": None})
|
|
58
|
+
elif theme not in (None, "platform"):
|
|
59
|
+
logger.warning("Unknown frontmatter theme value %r — ignoring", theme)
|
|
60
|
+
|
|
61
|
+
if "meta" in frontmatter:
|
|
62
|
+
logger.debug("Frontmatter meta present — SVG injection not yet implemented")
|
|
63
|
+
|
|
64
|
+
return config.model_copy(update={"plantuml": updated_plantuml})
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class RenderContext(BaseModel, frozen=True):
|
|
68
|
+
config: RenderConfig
|
|
69
|
+
source: Path
|
|
70
|
+
output_type: str
|
|
71
|
+
build_dir: Path
|
|
72
|
+
output_dir: Path | None = None
|
|
73
|
+
clear_workdir: bool = True
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
This module has functionality for publishing Tex documents
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import hashlib
|
|
5
6
|
import logging
|
|
6
7
|
import os
|
|
7
8
|
import shutil
|
|
@@ -90,7 +91,8 @@ def render(ctx: RenderContext):
|
|
|
90
91
|
ctx.config.tex.build_engine,
|
|
91
92
|
)
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
path_hash = hashlib.md5(str(ctx.source).encode()).hexdigest()[:8]
|
|
95
|
+
work_dir = ctx.build_dir / f"{ctx.source.stem}_{path_hash}"
|
|
94
96
|
work_dir.mkdir(exist_ok=True, parents=True)
|
|
95
97
|
|
|
96
98
|
err = _run_commands(ctx, work_dir)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Parse icplot frontmatter from diagram source files.
|
|
3
|
+
|
|
4
|
+
Frontmatter is embedded in a PlantUML block comment immediately after @startuml:
|
|
5
|
+
|
|
6
|
+
@startuml
|
|
7
|
+
/' icplot
|
|
8
|
+
theme: none
|
|
9
|
+
meta:
|
|
10
|
+
description: My diagram
|
|
11
|
+
'/
|
|
12
|
+
|
|
13
|
+
The block is valid PlantUML (a block comment) and is ignored by the renderer.
|
|
14
|
+
icplot reads it before invoking the renderer to control per-file behaviour.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
import re
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
import yaml
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
_OPEN = re.compile(r"^\s*/'[ \t]*icplot[ \t]*$")
|
|
26
|
+
_CLOSE = re.compile(r"^\s*'/\s*$")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def parse(path: Path) -> dict:
|
|
30
|
+
"""
|
|
31
|
+
Return the icplot frontmatter from *path* as a dict.
|
|
32
|
+
|
|
33
|
+
Returns an empty dict if no frontmatter block is present or if the block
|
|
34
|
+
cannot be parsed as YAML.
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
38
|
+
except OSError as exc:
|
|
39
|
+
logger.warning("Could not read %s for frontmatter: %s", path, exc)
|
|
40
|
+
return {}
|
|
41
|
+
|
|
42
|
+
in_block = False
|
|
43
|
+
block_lines: list[str] = []
|
|
44
|
+
|
|
45
|
+
for line in lines:
|
|
46
|
+
if not in_block:
|
|
47
|
+
if _OPEN.match(line):
|
|
48
|
+
in_block = True
|
|
49
|
+
else:
|
|
50
|
+
if _CLOSE.match(line):
|
|
51
|
+
break
|
|
52
|
+
block_lines.append(line)
|
|
53
|
+
|
|
54
|
+
if not block_lines:
|
|
55
|
+
return {}
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
result = yaml.safe_load("\n".join(block_lines))
|
|
59
|
+
return result if isinstance(result, dict) else {}
|
|
60
|
+
except yaml.YAMLError as exc:
|
|
61
|
+
logger.warning("Could not parse icplot frontmatter in %s: %s", path, exc)
|
|
62
|
+
return {}
|
|
@@ -2,9 +2,9 @@ import logging
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import cast
|
|
4
4
|
|
|
5
|
-
from icplot.graph.series import
|
|
5
|
+
from icplot.graph.series import LinePlotSeries # NOQA
|
|
6
|
+
from icplot.graph.series import (
|
|
6
7
|
ImageSeries,
|
|
7
|
-
LinePlotSeries,
|
|
8
8
|
PlotSeries,
|
|
9
9
|
ScatterPlotSeries,
|
|
10
10
|
)
|
|
@@ -5,42 +5,72 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
5
5
|
from functools import partial
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
|
-
from .config import RenderConfig, RenderContext
|
|
8
|
+
from .config import RenderConfig, RenderContext, apply_frontmatter
|
|
9
9
|
from .formats.mermaid import is_supported as mmd_supported
|
|
10
10
|
from .formats.mermaid import render as render_mmd
|
|
11
11
|
from .formats.plantuml import is_supported as puml_supported
|
|
12
12
|
from .formats.plantuml import render as render_puml
|
|
13
13
|
from .formats.tex import is_supported as tex_supported
|
|
14
14
|
from .formats.tex import render as render_tex
|
|
15
|
+
from .frontmatter import parse as parse_frontmatter
|
|
15
16
|
|
|
16
17
|
logger = logging.getLogger(__name__)
|
|
17
18
|
|
|
18
|
-
|
|
19
19
|
_ENVIRONMENT_CHECKS = {
|
|
20
20
|
"tex": tex_supported,
|
|
21
21
|
"puml": puml_supported,
|
|
22
22
|
"mmd": mmd_supported,
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
_RENDERERS = {
|
|
25
|
+
_RENDERERS = {
|
|
26
|
+
"tex": render_tex,
|
|
27
|
+
"puml": render_puml,
|
|
28
|
+
"mmd": render_mmd,
|
|
29
|
+
}
|
|
26
30
|
|
|
27
31
|
_FORMATS = ["puml", "mmd", "tex"]
|
|
28
32
|
|
|
29
|
-
_DEFAULT_OUTPUT_FORMATS = {
|
|
33
|
+
_DEFAULT_OUTPUT_FORMATS = {
|
|
34
|
+
"tex": ["svg"],
|
|
35
|
+
"puml": ["png", "svg"],
|
|
36
|
+
"mmd": ["svg"],
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _fail_task(ctx: RenderContext, reason: str):
|
|
41
|
+
"""Return failure reason"""
|
|
42
|
+
logger.debug("Fail task triggered for %s: %s", ctx.source, reason)
|
|
43
|
+
return reason
|
|
30
44
|
|
|
31
45
|
|
|
32
46
|
def run_tasks(tasks: list) -> list:
|
|
47
|
+
"""
|
|
48
|
+
Execute render tasks and collect failures.
|
|
49
|
+
Returns a list of dicts: {"source": Path, "error": str}
|
|
50
|
+
"""
|
|
33
51
|
errors = []
|
|
52
|
+
|
|
34
53
|
with ThreadPoolExecutor() as executor:
|
|
35
54
|
futures = {}
|
|
36
55
|
for t in tasks:
|
|
37
56
|
ctx = t.args[0]
|
|
38
57
|
logger.info("Queuing: %s -> %s", ctx.source.name, ctx.output_type)
|
|
39
58
|
futures[executor.submit(t)] = t
|
|
59
|
+
|
|
40
60
|
for future in as_completed(futures):
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
61
|
+
task = futures[future]
|
|
62
|
+
ctx = task.args[0]
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
result = future.result()
|
|
66
|
+
|
|
67
|
+
# Some renderers may return error messages instead of raising
|
|
68
|
+
if result:
|
|
69
|
+
errors.append({"source": ctx.source, "error": str(result)})
|
|
70
|
+
|
|
71
|
+
except Exception as e:
|
|
72
|
+
errors.append({"source": ctx.source, "error": str(e)})
|
|
73
|
+
|
|
44
74
|
return errors
|
|
45
75
|
|
|
46
76
|
|
|
@@ -57,35 +87,24 @@ def find_files(root: Path, extension: str, excludes: list[str] | None) -> list[P
|
|
|
57
87
|
|
|
58
88
|
|
|
59
89
|
def _get_extension(path: Path) -> str:
|
|
60
|
-
return path.suffix.
|
|
90
|
+
return path.suffix.lstrip(".")
|
|
61
91
|
|
|
62
92
|
|
|
63
93
|
def _collect_sources(root: Path, formats: list[str], excludes: list[str]) -> list[Path]:
|
|
64
94
|
files: list[Path] = []
|
|
65
95
|
if root.is_file():
|
|
66
96
|
logger.info("Using source: %s", root)
|
|
67
|
-
|
|
68
|
-
if suffix not in _ENVIRONMENT_CHECKS:
|
|
69
|
-
logger.warning("Unknown extension .%s — no renderer registered", suffix)
|
|
70
|
-
return files
|
|
71
|
-
if _ENVIRONMENT_CHECKS[suffix]():
|
|
72
|
-
files.append(root)
|
|
73
|
-
else:
|
|
74
|
-
logger.warning(
|
|
75
|
-
"Render environment doesn't support requested extension: %s", root
|
|
76
|
-
)
|
|
97
|
+
files.append(root)
|
|
77
98
|
else:
|
|
78
99
|
logger.info("Searching for sources in: %s", root)
|
|
79
100
|
if not formats:
|
|
80
101
|
formats = _FORMATS
|
|
81
102
|
for f in formats:
|
|
82
103
|
if f not in _ENVIRONMENT_CHECKS:
|
|
83
|
-
logger.warning("Unknown format requested: %s —
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
else:
|
|
88
|
-
logger.debug("Skipping .%s — renderer not available in environment", f)
|
|
104
|
+
logger.warning("Unknown format requested: %s — including anyway", f)
|
|
105
|
+
|
|
106
|
+
files.extend(find_files(root, f, excludes))
|
|
107
|
+
|
|
89
108
|
return files
|
|
90
109
|
|
|
91
110
|
|
|
@@ -97,27 +116,49 @@ def _setup_tasks(
|
|
|
97
116
|
for f in sources:
|
|
98
117
|
|
|
99
118
|
ext = _get_extension(f)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
output_formats
|
|
119
|
+
|
|
120
|
+
output_formats = (
|
|
121
|
+
config.output_formats
|
|
122
|
+
if config.output_formats
|
|
123
|
+
else _DEFAULT_OUTPUT_FORMATS.get(ext, ["png"])
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
file_config = apply_frontmatter(config, parse_frontmatter(f))
|
|
104
127
|
|
|
105
128
|
for fmt in output_formats:
|
|
129
|
+
ctx = RenderContext(
|
|
130
|
+
config=file_config,
|
|
131
|
+
source=f,
|
|
132
|
+
build_dir=build_dir,
|
|
133
|
+
output_dir=output_dir,
|
|
134
|
+
output_type=fmt,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# No renderer, return error
|
|
106
138
|
if ext not in _RENDERERS:
|
|
107
|
-
logger.warning("No renderer for
|
|
139
|
+
logger.warning("No renderer for .%s — reporting error for %s", ext, f)
|
|
140
|
+
tasks.append(
|
|
141
|
+
partial(_fail_task, ctx, f"No renderer for extension: {ext}")
|
|
142
|
+
)
|
|
108
143
|
continue
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
source=f,
|
|
115
|
-
build_dir=build_dir,
|
|
116
|
-
output_dir=output_dir,
|
|
117
|
-
output_type=fmt,
|
|
118
|
-
),
|
|
144
|
+
|
|
145
|
+
# Environment not supported, return error
|
|
146
|
+
if not _ENVIRONMENT_CHECKS.get(ext, lambda: False)():
|
|
147
|
+
logger.warning(
|
|
148
|
+
"Renderer unavailable for .%s — reporting error for %s", ext, f
|
|
119
149
|
)
|
|
120
|
-
|
|
150
|
+
tasks.append(
|
|
151
|
+
partial(
|
|
152
|
+
_fail_task,
|
|
153
|
+
ctx,
|
|
154
|
+
f"Environment not supported for extension: {ext}",
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
# Normal rendering
|
|
160
|
+
tasks.append(partial(_RENDERERS[ext], ctx))
|
|
161
|
+
|
|
121
162
|
return tasks
|
|
122
163
|
|
|
123
164
|
|
|
@@ -127,12 +168,11 @@ def render(
|
|
|
127
168
|
config: RenderConfig,
|
|
128
169
|
output_dir: Path | None = None,
|
|
129
170
|
):
|
|
130
|
-
|
|
131
171
|
sources = _collect_sources(root, config.input_formats, config.excludes)
|
|
132
172
|
|
|
133
173
|
if not sources:
|
|
134
174
|
logger.warning("No renderable sources found under: %s", root)
|
|
135
|
-
return
|
|
175
|
+
return []
|
|
136
176
|
|
|
137
177
|
# There can be multiple tasks per source, if there are multiple output formats
|
|
138
178
|
tasks = _setup_tasks(sources, config, build_dir, output_dir)
|
|
@@ -143,7 +183,9 @@ def render(
|
|
|
143
183
|
succeeded = len(tasks) - len(errors)
|
|
144
184
|
if errors:
|
|
145
185
|
logger.error("%d/%d task(s) failed:", len(errors), len(tasks))
|
|
146
|
-
for
|
|
147
|
-
logger.error(" %s",
|
|
186
|
+
for err in errors:
|
|
187
|
+
logger.error(" %s: %s", err["source"].relative_to(root), err["error"])
|
|
148
188
|
else:
|
|
149
189
|
logger.info("Done — %d/%d task(s) succeeded", succeeded, len(tasks))
|
|
190
|
+
|
|
191
|
+
return errors
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: icplot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Utilities for generating plots and graphics for technical reports.
|
|
5
5
|
Author-email: "James Grogan, Irish Centre for High End Computing" <james.grogan@ichec.ie>
|
|
6
6
|
Project-URL: Repository, https://git.ichec.ie/performance/toolshed/icplot
|
|
@@ -14,11 +14,6 @@ Classifier: Topic :: System :: Distributed Computing
|
|
|
14
14
|
Requires-Python: >=3.10
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
License-File: LICENSE
|
|
17
|
-
Requires-Dist: wand
|
|
18
|
-
Requires-Dist: matplotlib
|
|
19
|
-
Requires-Dist: vtk
|
|
20
|
-
Requires-Dist: moviepy
|
|
21
|
-
Requires-Dist: scipy
|
|
22
17
|
Requires-Dist: iccore==0.2.3
|
|
23
18
|
Requires-Dist: graphviz
|
|
24
19
|
Requires-Dist: Jinja2
|
|
@@ -31,9 +26,29 @@ Requires-Dist: isort; extra == "test"
|
|
|
31
26
|
Requires-Dist: mypy; extra == "test"
|
|
32
27
|
Requires-Dist: flake8; extra == "test"
|
|
33
28
|
Requires-Dist: pylint; extra == "test"
|
|
29
|
+
Requires-Dist: types-PyYAML; extra == "test"
|
|
34
30
|
Provides-Extra: cairo
|
|
35
|
-
Requires-Dist: pycairo; extra == "cairo"
|
|
36
31
|
Requires-Dist: CairoSVG; extra == "cairo"
|
|
32
|
+
Provides-Extra: scene
|
|
33
|
+
Requires-Dist: pycairo; extra == "scene"
|
|
34
|
+
Provides-Extra: imagemagick
|
|
35
|
+
Requires-Dist: wand; extra == "imagemagick"
|
|
36
|
+
Provides-Extra: plot
|
|
37
|
+
Requires-Dist: matplotlib; extra == "plot"
|
|
38
|
+
Provides-Extra: vtk
|
|
39
|
+
Requires-Dist: vtk; extra == "vtk"
|
|
40
|
+
Provides-Extra: geometry
|
|
41
|
+
Requires-Dist: scipy; extra == "geometry"
|
|
42
|
+
Provides-Extra: video
|
|
43
|
+
Requires-Dist: moviepy; extra == "video"
|
|
44
|
+
Provides-Extra: all
|
|
45
|
+
Requires-Dist: CairoSVG; extra == "all"
|
|
46
|
+
Requires-Dist: pycairo; extra == "all"
|
|
47
|
+
Requires-Dist: wand; extra == "all"
|
|
48
|
+
Requires-Dist: matplotlib; extra == "all"
|
|
49
|
+
Requires-Dist: vtk; extra == "all"
|
|
50
|
+
Requires-Dist: scipy; extra == "all"
|
|
51
|
+
Requires-Dist: moviepy; extra == "all"
|
|
37
52
|
Dynamic: license-file
|
|
38
53
|
|
|
39
54
|
# icplot
|
|
@@ -121,13 +136,30 @@ The library supports themeing with an Angular Material 3 motivated design system
|
|
|
121
136
|
|
|
122
137
|
# Installation #
|
|
123
138
|
|
|
124
|
-
The package is available on PyPI.
|
|
139
|
+
The package is available on PyPI. The core install is deliberately light — it
|
|
140
|
+
does not pull the heavy rendering backends, so consumers that only need part of
|
|
141
|
+
the library (e.g. a docs build that just converts svg to png) stay small:
|
|
125
142
|
|
|
126
143
|
``` shell
|
|
127
144
|
pip install icplot
|
|
128
145
|
```
|
|
129
146
|
|
|
130
|
-
|
|
147
|
+
Backends are opt-in via extras:
|
|
148
|
+
|
|
149
|
+
| Extra | Pulls | Enables |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| `cairo` | CairoSVG | svg → png / pdf conversion |
|
|
152
|
+
| `scene` | pycairo | vector scene drawing (`renderer.scene.cairo`) |
|
|
153
|
+
| `imagemagick` | wand | pdf → png conversion (falls back to the `magick` CLI) |
|
|
154
|
+
| `plot` | matplotlib | 2D plotting (`renderer.graph`, `charts`) |
|
|
155
|
+
| `vtk` | vtk | 3D / mesh rendering (`geometry.mesh`) |
|
|
156
|
+
| `geometry` | scipy | geometry transforms (`geometry.operations`) |
|
|
157
|
+
| `video` | moviepy | video assembly (`renderer.video`) |
|
|
158
|
+
| `all` | all of the above | everything (previous default behaviour) |
|
|
159
|
+
|
|
160
|
+
For example, `pip install icplot[cairo]` for svg conversion, or
|
|
161
|
+
`pip install icplot[all]` for the full toolkit. The native `cairo` and
|
|
162
|
+
`imagemagick` system libraries are also required for those backends; on Mac:
|
|
131
163
|
|
|
132
164
|
``` shell
|
|
133
165
|
brew install imagemagick cairo
|
|
@@ -43,6 +43,7 @@ src/icplot/graph/series.py
|
|
|
43
43
|
src/icplot/renderer/__init__.py
|
|
44
44
|
src/icplot/renderer/cli.py
|
|
45
45
|
src/icplot/renderer/config.py
|
|
46
|
+
src/icplot/renderer/frontmatter.py
|
|
46
47
|
src/icplot/renderer/render.py
|
|
47
48
|
src/icplot/renderer/video.py
|
|
48
49
|
src/icplot/renderer/charts/pgfgantt.py
|
|
@@ -67,6 +68,7 @@ src/icplot/theme/theme_generator.py
|
|
|
67
68
|
src/icplot/theme/templates/puml.j2
|
|
68
69
|
src/icplot/theme/templates/tikz.j2
|
|
69
70
|
test/test_cairo.py
|
|
71
|
+
test/test_frontmatter.py
|
|
70
72
|
test/test_gantt_chart.py
|
|
71
73
|
test/test_geometry.py
|
|
72
74
|
test/test_image.py
|
|
@@ -1,16 +1,31 @@
|
|
|
1
|
+
iccore==0.2.3
|
|
2
|
+
graphviz
|
|
3
|
+
Jinja2
|
|
4
|
+
|
|
5
|
+
[all]
|
|
6
|
+
CairoSVG
|
|
7
|
+
pycairo
|
|
1
8
|
wand
|
|
2
9
|
matplotlib
|
|
3
10
|
vtk
|
|
4
|
-
moviepy
|
|
5
11
|
scipy
|
|
6
|
-
|
|
7
|
-
graphviz
|
|
8
|
-
Jinja2
|
|
12
|
+
moviepy
|
|
9
13
|
|
|
10
14
|
[cairo]
|
|
11
|
-
pycairo
|
|
12
15
|
CairoSVG
|
|
13
16
|
|
|
17
|
+
[geometry]
|
|
18
|
+
scipy
|
|
19
|
+
|
|
20
|
+
[imagemagick]
|
|
21
|
+
wand
|
|
22
|
+
|
|
23
|
+
[plot]
|
|
24
|
+
matplotlib
|
|
25
|
+
|
|
26
|
+
[scene]
|
|
27
|
+
pycairo
|
|
28
|
+
|
|
14
29
|
[test]
|
|
15
30
|
pytest
|
|
16
31
|
pytest-cov
|
|
@@ -20,3 +35,10 @@ isort
|
|
|
20
35
|
mypy
|
|
21
36
|
flake8
|
|
22
37
|
pylint
|
|
38
|
+
types-PyYAML
|
|
39
|
+
|
|
40
|
+
[video]
|
|
41
|
+
moviepy
|
|
42
|
+
|
|
43
|
+
[vtk]
|
|
44
|
+
vtk
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from iccore.test_utils import get_test_data_dir
|
|
4
|
+
|
|
5
|
+
from icplot.renderer.config import PlantUmlConfig, RenderConfig, apply_frontmatter
|
|
6
|
+
from icplot.renderer.frontmatter import parse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_parse_returns_dict():
|
|
10
|
+
fm = parse(get_test_data_dir() / "test_frontmatter.puml")
|
|
11
|
+
assert fm["theme"] == "none"
|
|
12
|
+
assert fm["meta"]["description"] == "Frontmatter parse test"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_parse_no_frontmatter():
|
|
16
|
+
fm = parse(get_test_data_dir() / "test.puml")
|
|
17
|
+
assert fm == {}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_parse_missing_file():
|
|
21
|
+
fm = parse(Path("/nonexistent/file.puml"))
|
|
22
|
+
assert fm == {}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_apply_theme_none_clears_config_path(tmp_path):
|
|
26
|
+
config_file = tmp_path / "theme.puml"
|
|
27
|
+
config_file.touch()
|
|
28
|
+
config = RenderConfig(plantuml=PlantUmlConfig(config_path=config_file))
|
|
29
|
+
|
|
30
|
+
result = apply_frontmatter(config, {"theme": "none"})
|
|
31
|
+
|
|
32
|
+
assert result.plantuml.config_path is None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_apply_empty_frontmatter_leaves_config_unchanged(tmp_path):
|
|
36
|
+
config_file = tmp_path / "theme.puml"
|
|
37
|
+
config_file.touch()
|
|
38
|
+
config = RenderConfig(plantuml=PlantUmlConfig(config_path=config_file))
|
|
39
|
+
|
|
40
|
+
result = apply_frontmatter(config, {})
|
|
41
|
+
|
|
42
|
+
assert result.plantuml.config_path == config_file
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_apply_meta_only_leaves_config_unchanged(tmp_path):
|
|
46
|
+
config_file = tmp_path / "theme.puml"
|
|
47
|
+
config_file.touch()
|
|
48
|
+
config = RenderConfig(plantuml=PlantUmlConfig(config_path=config_file))
|
|
49
|
+
|
|
50
|
+
result = apply_frontmatter(config, {"meta": {"description": "test"}})
|
|
51
|
+
|
|
52
|
+
assert result.plantuml.config_path == config_file
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from iccore.test_utils import get_test_output_dir
|
|
6
|
+
|
|
7
|
+
from icplot.converter.image import _pdf_to_png, svg_to_pdf, svg_to_png
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_test_data_dir():
|
|
11
|
+
return Path(__file__).parent / "data"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_convert_svg():
|
|
15
|
+
|
|
16
|
+
source = get_test_data_dir() / "test.svg"
|
|
17
|
+
|
|
18
|
+
png_out = Path(os.getcwd()) / "out.png"
|
|
19
|
+
svg_to_png(source, png_out)
|
|
20
|
+
png_out.unlink()
|
|
21
|
+
|
|
22
|
+
pdf_out = Path(os.getcwd()) / "out.pdf"
|
|
23
|
+
svg_to_pdf(source, pdf_out)
|
|
24
|
+
pdf_out.unlink()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_convert_pdf():
|
|
28
|
+
|
|
29
|
+
source = get_test_data_dir() / "test.pdf"
|
|
30
|
+
output_dir = get_test_output_dir()
|
|
31
|
+
target = output_dir / "test.png"
|
|
32
|
+
|
|
33
|
+
_pdf_to_png(source, target)
|
|
34
|
+
|
|
35
|
+
assert target.exists()
|
|
36
|
+
assert target.stat().st_size > 0
|
|
37
|
+
target.unlink()
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
from pydantic import BaseModel
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class TexConfig(BaseModel, frozen=True):
|
|
7
|
-
"""
|
|
8
|
-
This holds settings for the Tex build
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
build_engine: str = "pdflatex"
|
|
12
|
-
nonstopmode: bool = True
|
|
13
|
-
use_woff_fonts: bool = True
|
|
14
|
-
templates: list[Path] = []
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class PlantUmlConfig(BaseModel, frozen=True):
|
|
18
|
-
config_path: Path | None = None
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class MermaidConfig(BaseModel, frozen=True):
|
|
22
|
-
browser_config: Path | None = None
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class RenderConfig(BaseModel, frozen=True):
|
|
26
|
-
tex: TexConfig = TexConfig()
|
|
27
|
-
plantuml: PlantUmlConfig = PlantUmlConfig()
|
|
28
|
-
mermaid: MermaidConfig = MermaidConfig()
|
|
29
|
-
extra_config: Path | None = None
|
|
30
|
-
input_formats: list[str] = []
|
|
31
|
-
output_formats: list[str] = []
|
|
32
|
-
excludes: list[str] = []
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class RenderContext(BaseModel, frozen=True):
|
|
36
|
-
config: RenderConfig
|
|
37
|
-
source: Path
|
|
38
|
-
output_type: str
|
|
39
|
-
build_dir: Path
|
|
40
|
-
output_dir: Path | None = None
|
|
41
|
-
clear_workdir: bool = True
|
icplot-0.3.3/test/test_image.py
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
from icplot.converter.image import svg_to_pdf, svg_to_png
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def get_test_data_dir():
|
|
8
|
-
return Path(__file__).parent / "data"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def test_convert_svg():
|
|
12
|
-
|
|
13
|
-
source = get_test_data_dir() / "test.svg"
|
|
14
|
-
|
|
15
|
-
png_out = Path(os.getcwd()) / "out.png"
|
|
16
|
-
svg_to_png(source, png_out)
|
|
17
|
-
png_out.unlink()
|
|
18
|
-
|
|
19
|
-
pdf_out = Path(os.getcwd()) / "out.pdf"
|
|
20
|
-
svg_to_pdf(source, pdf_out)
|
|
21
|
-
pdf_out.unlink()
|
|
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
|