amethyst-cli 0.1.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.
Files changed (47) hide show
  1. amethyst_cli-0.1.0/.github/workflows/ci.yml +132 -0
  2. amethyst_cli-0.1.0/.gitignore +23 -0
  3. amethyst_cli-0.1.0/CLAUDE.md +63 -0
  4. amethyst_cli-0.1.0/LICENSE +21 -0
  5. amethyst_cli-0.1.0/PKG-INFO +293 -0
  6. amethyst_cli-0.1.0/README.md +261 -0
  7. amethyst_cli-0.1.0/docs/documentation.md +988 -0
  8. amethyst_cli-0.1.0/pyproject.toml +82 -0
  9. amethyst_cli-0.1.0/src/amethyst/__init__.py +5 -0
  10. amethyst_cli-0.1.0/src/amethyst/__main__.py +6 -0
  11. amethyst_cli-0.1.0/src/amethyst/cli.py +644 -0
  12. amethyst_cli-0.1.0/src/amethyst/config.py +387 -0
  13. amethyst_cli-0.1.0/src/amethyst/document.py +228 -0
  14. amethyst_cli-0.1.0/src/amethyst/errors.py +66 -0
  15. amethyst_cli-0.1.0/src/amethyst/ooxml.py +549 -0
  16. amethyst_cli-0.1.0/src/amethyst/parse/__init__.py +20 -0
  17. amethyst_cli-0.1.0/src/amethyst/parse/assets.py +106 -0
  18. amethyst_cli-0.1.0/src/amethyst/parse/frontmatter.py +62 -0
  19. amethyst_cli-0.1.0/src/amethyst/parse/markdown.py +49 -0
  20. amethyst_cli-0.1.0/src/amethyst/remote.py +236 -0
  21. amethyst_cli-0.1.0/src/amethyst/render/__init__.py +42 -0
  22. amethyst_cli-0.1.0/src/amethyst/render/base.py +85 -0
  23. amethyst_cli-0.1.0/src/amethyst/render/docx.py +1060 -0
  24. amethyst_cli-0.1.0/src/amethyst/render/furniture.py +112 -0
  25. amethyst_cli-0.1.0/src/amethyst/render/highlight.py +315 -0
  26. amethyst_cli-0.1.0/src/amethyst/render/html.py +266 -0
  27. amethyst_cli-0.1.0/src/amethyst/render/pdf.py +219 -0
  28. amethyst_cli-0.1.0/src/amethyst/theme/__init__.py +493 -0
  29. amethyst_cli-0.1.0/src/amethyst/theme/builtin/academic.toml +45 -0
  30. amethyst_cli-0.1.0/src/amethyst/theme/builtin/css/base.css +361 -0
  31. amethyst_cli-0.1.0/src/amethyst/theme/builtin/default.toml +42 -0
  32. amethyst_cli-0.1.0/src/amethyst/theme/builtin/github.toml +44 -0
  33. amethyst_cli-0.1.0/src/amethyst/theme/to_css.py +182 -0
  34. amethyst_cli-0.1.0/src/amethyst/theme/to_docx.py +651 -0
  35. amethyst_cli-0.1.0/tests/conftest.py +110 -0
  36. amethyst_cli-0.1.0/tests/fixtures/assets/amethyst.png +0 -0
  37. amethyst_cli-0.1.0/tests/fixtures/kitchen-sink.md +137 -0
  38. amethyst_cli-0.1.0/tests/test_cli.py +642 -0
  39. amethyst_cli-0.1.0/tests/test_config.py +235 -0
  40. amethyst_cli-0.1.0/tests/test_furniture.py +104 -0
  41. amethyst_cli-0.1.0/tests/test_highlight.py +134 -0
  42. amethyst_cli-0.1.0/tests/test_parse.py +386 -0
  43. amethyst_cli-0.1.0/tests/test_remote.py +222 -0
  44. amethyst_cli-0.1.0/tests/test_render_docx.py +918 -0
  45. amethyst_cli-0.1.0/tests/test_render_pdf.py +577 -0
  46. amethyst_cli-0.1.0/tests/test_theme.py +284 -0
  47. amethyst_cli-0.1.0/uv.lock +1461 -0
@@ -0,0 +1,132 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ test:
15
+ name: Test on Python ${{ matrix.python-version }}
16
+ runs-on: ubuntu-latest
17
+ strategy:
18
+ fail-fast: false
19
+ matrix:
20
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
21
+
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ # WeasyPrint loads Pango through cffi at render time, so the PDF half of
26
+ # the suite needs it present. Without it those tests skip rather than
27
+ # fail, which would make a green run mean less than it looks like.
28
+ - name: Install Pango
29
+ run: |
30
+ sudo apt-get update
31
+ sudo apt-get install -y --no-install-recommends \
32
+ libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0
33
+
34
+ - name: Install uv
35
+ uses: astral-sh/setup-uv@v5
36
+ with:
37
+ enable-cache: true
38
+
39
+ # Before syncing, because `uv sync` would bring the lockfile up to date
40
+ # itself and there would be nothing left to catch.
41
+ - name: Check the lockfile is current
42
+ run: uv lock --check
43
+
44
+ # --frozen so a CI run installs what the lockfile says and never quietly
45
+ # resolves something newer.
46
+ - name: Install the project
47
+ run: uv sync --frozen --python ${{ matrix.python-version }}
48
+
49
+ - name: Test
50
+ run: uv run pytest -q
51
+
52
+ # The suite proves a PDF is well-formed, not that it renders at all from
53
+ # an installed console script — which is a different code path, and the
54
+ # one users take. Both formats, end to end, on every version.
55
+ - name: Convert the kitchen sink
56
+ run: |
57
+ uv run amethyst convert tests/fixtures/kitchen-sink.md \
58
+ -o kitchen-sink.pdf --toc --title-page --no-remote
59
+ uv run amethyst convert tests/fixtures/kitchen-sink.md \
60
+ -o kitchen-sink.docx --toc --title-page --no-remote
61
+ test -s kitchen-sink.pdf
62
+ test -s kitchen-sink.docx
63
+
64
+ lint:
65
+ name: Lint and types
66
+ runs-on: ubuntu-latest
67
+
68
+ steps:
69
+ - uses: actions/checkout@v4
70
+
71
+ - name: Install uv
72
+ uses: astral-sh/setup-uv@v5
73
+ with:
74
+ enable-cache: true
75
+
76
+ # No Pango here on purpose: `amethyst --help`, the linters and the type
77
+ # checker must all work on a machine that has never heard of it, because
78
+ # the DOCX half of the tool does.
79
+ - name: Install the project
80
+ run: uv sync --frozen --python 3.13
81
+
82
+ - name: ruff check
83
+ run: uv run ruff check src/ tests/
84
+
85
+ - name: ruff format
86
+ run: uv run ruff format --check src/ tests/
87
+
88
+ - name: mypy
89
+ run: uv run mypy src/amethyst
90
+
91
+ - name: The CLI loads without Pango
92
+ run: uv run amethyst --help
93
+
94
+ build:
95
+ name: Build the wheel
96
+ runs-on: ubuntu-latest
97
+
98
+ steps:
99
+ - uses: actions/checkout@v4
100
+
101
+ - name: Install uv
102
+ uses: astral-sh/setup-uv@v5
103
+
104
+ - name: Build
105
+ run: uv build
106
+
107
+ # The theme TOML and the stylesheet are package data. A wheel that left
108
+ # them out installs cleanly and fails on the first conversion, which is
109
+ # exactly the bug the src/ layout exists to catch.
110
+ - name: The wheel carries its data files
111
+ run: |
112
+ python3 -m zipfile -l dist/*.whl | tee contents.txt
113
+ grep -q 'amethyst/theme/builtin/default.toml' contents.txt
114
+ grep -q 'amethyst/theme/builtin/css/base.css' contents.txt
115
+
116
+ # From the wheel rather than the checkout, so nothing resolves against
117
+ # the source tree. DOCX because it needs no system library: this job
118
+ # deliberately has no Pango, and the conversion still has to work.
119
+ - name: The installed wheel converts
120
+ run: |
121
+ uv venv --python 3.13 /tmp/clean
122
+ VIRTUAL_ENV=/tmp/clean uv pip install dist/*.whl
123
+ cd /tmp
124
+ /tmp/clean/bin/amethyst convert \
125
+ "$GITHUB_WORKSPACE/tests/fixtures/kitchen-sink.md" \
126
+ -o /tmp/wheel.docx --toc --title-page --no-remote
127
+ test -s /tmp/wheel.docx
128
+
129
+ - uses: actions/upload-artifact@v4
130
+ with:
131
+ name: dist
132
+ path: dist/
@@ -0,0 +1,23 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ .venv/
5
+ *.egg-info/
6
+ build/
7
+ dist/
8
+
9
+ # Tooling
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ .mypy_cache/
13
+
14
+ # macOS
15
+ .DS_Store
16
+
17
+ # Runtime
18
+ *.log
19
+
20
+ # Generated output
21
+ # Amethyst emits these; add `!tests/fixtures/**` if binary fixtures are ever needed
22
+ *.pdf
23
+ *.docx
@@ -0,0 +1,63 @@
1
+ # CLAUDE.md
2
+
3
+ Amethyst is a Python CLI that turns a Markdown file into a well-typeset PDF or
4
+ Word (DOCX) document — one file in, one styled document out:
5
+
6
+ ```sh
7
+ amethyst convert notes.md -o notes.pdf
8
+ amethyst convert notes.md -f docx
9
+ ```
10
+
11
+ The point is output that looks deliberately typeset rather than printed from a
12
+ browser: good defaults with no flags required, and a shared theme so the same
13
+ source reads as the same document in both formats.
14
+
15
+ ## Commands
16
+
17
+ The venv is Python 3.13 with all dependencies installed:
18
+
19
+ ```sh
20
+ .venv/bin/python
21
+ .venv/bin/amethyst # the CLI itself, or: .venv/bin/python -m amethyst
22
+ ```
23
+
24
+ `amethyst` is only on `PATH` in a shell with the venv activated, which a tool
25
+ call does not have. A bare `amethyst ...` there fails as "command not found",
26
+ which looks like a broken install and is not one.
27
+
28
+ No `DYLD_FALLBACK_LIBRARY_PATH` export is needed any more: the PDF renderer
29
+ puts Homebrew's lib directory on the loader path itself, just before it imports
30
+ WeasyPrint. Only import `weasyprint` directly — outside Amethyst's own code —
31
+ and you will still need it:
32
+
33
+ ```sh
34
+ export DYLD_FALLBACK_LIBRARY_PATH=/opt/homebrew/lib
35
+ ```
36
+
37
+ All four of these should be clean before a commit:
38
+
39
+ ```sh
40
+ .venv/bin/python -m pytest -q
41
+ .venv/bin/ruff check src/ tests/
42
+ .venv/bin/ruff format src/ tests/
43
+ .venv/bin/mypy src/amethyst
44
+ ```
45
+
46
+ `uv.lock` is tracked, so run `uv lock` after touching `[project.dependencies]`.
47
+ `uv lock --check` tells you whether it has drifted.
48
+
49
+ ## Looking at the output
50
+
51
+ The suite proves a PDF is well-formed, not that it looks right — the two
52
+ rendering defects found while building the PDF path were both invisible to it.
53
+ So look at the pages. There is no `pdftoppm` or `pypdfium2` here, and `sips`
54
+ converts only the first page of a PDF, so split it first:
55
+
56
+ ```sh
57
+ .venv/bin/python -c "
58
+ from pypdf import PdfReader, PdfWriter
59
+ for i, page in enumerate(PdfReader('out.pdf').pages):
60
+ w = PdfWriter(); w.add_page(page); w.write(f'page{i}.pdf')
61
+ "
62
+ for f in page*.pdf; do sips -s format png "$f" --out "${f%.pdf}.png"; done
63
+ ```
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Patrik Repkovsky
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,293 @@
1
+ Metadata-Version: 2.5
2
+ Name: amethyst-cli
3
+ Version: 0.1.0
4
+ Summary: Turn a Markdown file into a well-typeset PDF or Word document.
5
+ Project-URL: Homepage, https://github.com/nestix6/Amethyst
6
+ Project-URL: Repository, https://github.com/nestix6/Amethyst
7
+ Project-URL: Issues, https://github.com/nestix6/Amethyst/issues
8
+ Author: Patrik Repkovsky
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: cli,docx,markdown,pdf,typesetting
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: linkify-it-py>=2.2
22
+ Requires-Dist: markdown-it-py>=4.0
23
+ Requires-Dist: mdit-py-plugins>=0.6
24
+ Requires-Dist: pygments>=2.17
25
+ Requires-Dist: python-docx>=1.1
26
+ Requires-Dist: pyyaml>=6.0
27
+ Requires-Dist: rich>=13.7
28
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
29
+ Requires-Dist: typer>=0.12
30
+ Requires-Dist: weasyprint>=69
31
+ Description-Content-Type: text/markdown
32
+
33
+ # Amethyst
34
+
35
+ [![CI](https://github.com/nestix6/Amethyst/actions/workflows/ci.yml/badge.svg)](https://github.com/nestix6/Amethyst/actions/workflows/ci.yml)
36
+
37
+ Turn a Markdown file into a well-typeset PDF or Word document.
38
+
39
+ ```sh
40
+ amethyst convert notes.md -o notes.pdf
41
+ amethyst convert notes.md -f docx
42
+ ```
43
+
44
+ One file in, one styled document out. No flags required, no stylesheet to
45
+ write, no reference `.docx` to maintain. The point is output that looks
46
+ deliberately typeset rather than printed from a browser — and a shared theme,
47
+ so the same source reads as the same document in both formats.
48
+
49
+ This page is the quick tour. **[Full documentation](docs/documentation.md)**
50
+ covers every option, the theme format, the configuration layers and the
51
+ architecture.
52
+
53
+ ---
54
+
55
+ ## Install
56
+
57
+ Amethyst is a Python 3.10+ package. Install it as a tool:
58
+
59
+ ```sh
60
+ uv tool install amethyst-cli
61
+ ```
62
+
63
+ or into an environment of your own with `pip install amethyst-cli`. The
64
+ package is `amethyst-cli`; the command it installs is `amethyst`.
65
+
66
+ **The PDF path needs Pango**, which is a system library rather than a Python
67
+ one. That is the only thing Amethyst cannot install for you:
68
+
69
+ ```sh
70
+ # macOS
71
+ brew install pango
72
+
73
+ # Debian / Ubuntu
74
+ sudo apt install libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0
75
+ ```
76
+
77
+ DOCX output needs none of it. If Pango is missing, `amethyst convert -f docx`
78
+ still works and `-f pdf` exits 3 with a line saying what to install, rather
79
+ than a `cffi` traceback.
80
+
81
+ ---
82
+
83
+ ## Use
84
+
85
+ ```sh
86
+ amethyst convert report.md -f pdf # writes report.pdf
87
+ amethyst convert report.md -f docx # writes report.docx
88
+ amethyst convert report.md -o ~/Desktop/review.pdf # the extension picks the format
89
+ amethyst convert report.md -f pdf --toc --title-page # contents and a cover
90
+ amethyst convert - -f pdf -o - < report.md # stdin to stdout
91
+ ```
92
+
93
+ The format has to be stated, by `-f` or by an output path that ends in `.pdf`
94
+ or `.docx` — or once, in a config file. Given `-f` and no `-o`, the output is
95
+ the input path with the extension swapped. With `-o -` the document goes to
96
+ stdout and every message steps aside to stderr, so the bytes stay clean in a
97
+ pipeline.
98
+
99
+ ### Options
100
+
101
+ | Flag | Effect |
102
+ | --- | --- |
103
+ | `-o, --output PATH` | Output file; the extension infers the format. `-` is stdout. |
104
+ | `-f, --format pdf\|docx` | Explicit format. Required when `-o` is omitted or is stdout. |
105
+ | `-t, --theme NAME\|PATH` | A builtin theme name, or a path to a theme `.toml`. |
106
+ | `--css PATH` | Extra stylesheet, appended last. PDF only. |
107
+ | `--toc` | Open the document with a table of contents. |
108
+ | `--toc-depth N` | Heading levels the contents lists. Default 3. |
109
+ | `--title-page` | Open with a title page built from the frontmatter. |
110
+ | `--title TEXT`, `--author TEXT` | Override what the frontmatter declared. |
111
+ | `--page-size TEXT` | `A4`, `Letter`, or a CSS size. Defaults to the theme's. |
112
+ | `--margin TEXT` | CSS-style: `2cm`, or `2cm 2.5cm`. Defaults to the theme's. |
113
+ | `--no-page-numbers` | Suppress the footer numbering. |
114
+ | `--no-remote` | Never download an image; leave a gap where a remote one was. |
115
+ | `--highlight-style NAME` | Any Pygments style, or `none` to leave code uncoloured. |
116
+ | `--pdf-engine NAME` | `weasyprint`. The flag exists to keep the seam visible. |
117
+ | `-q, --quiet` / `--verbose` | Errors only / detail, and tracebacks on failure. |
118
+
119
+ Two more commands:
120
+
121
+ ```sh
122
+ amethyst themes list # the builtin themes, with a line about each
123
+ amethyst themes show NAME # a theme's TOML, ready to copy and edit
124
+ amethyst init # write a starter amethyst.toml here
125
+ ```
126
+
127
+ `amethyst --version` prints the version. Exit codes are distinct so the tool
128
+ composes in scripts: **0** ok, **1** conversion failure, **2** bad usage,
129
+ **3** a missing system dependency.
130
+
131
+ ---
132
+
133
+ ## What converts
134
+
135
+ Everything below works in **both** formats unless the note says otherwise.
136
+
137
+ Headings, bold, italic, strikethrough, inline code, hard breaks, nested and
138
+ ordered lists, task lists, blockquotes, GFM tables, horizontal rules, links,
139
+ local images, remote images, fenced code with syntax highlighting, footnotes,
140
+ a table of contents, page numbers, a running head, a title page, and document
141
+ metadata. PDFs also get nested outline bookmarks, straight from the headings.
142
+
143
+ Three things are deliberately approximate:
144
+
145
+ - **Footnotes in Word** are an endnote-style list at the end of the document,
146
+ not real Word footnotes.
147
+ - **Task lists in Word** are printed ☐ / ☑ glyphs, not clickable checkboxes.
148
+ - **Raw HTML** passes through to PDF and is skipped in DOCX, with one warning
149
+ naming the line it was on.
150
+
151
+ LaTeX math and Mermaid diagrams are out of scope.
152
+
153
+ ---
154
+
155
+ ## Frontmatter
156
+
157
+ YAML frontmatter sets the document's metadata. It reaches the title page, the
158
+ running head, the PDF's info dictionary and Word's core properties.
159
+
160
+ ```markdown
161
+ ---
162
+ title: Quarterly Review
163
+ subtitle: What the numbers did
164
+ author: Ada Lovelace
165
+ date: 2026-03-31
166
+ keywords: [finance, review]
167
+ ---
168
+ ```
169
+
170
+ `title` falls back to the first `h1` if it is not declared. A `date` that is
171
+ not a real date — "Spring 2026" — still prints on the cover, but does not
172
+ reach the file's timestamp metadata.
173
+
174
+ Frontmatter may also set any option from the table above except `format`,
175
+ which has to be settled before the document is opened:
176
+
177
+ ```markdown
178
+ ---
179
+ title: Quarterly Review
180
+ theme: academic
181
+ toc: true
182
+ ---
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Configuration
188
+
189
+ Settings resolve in this order, later winning:
190
+
191
+ ```
192
+ builtin defaults → ~/.config/amethyst/config.toml → ./amethyst.toml
193
+ → the document's frontmatter → command-line flags
194
+ ```
195
+
196
+ `amethyst init` writes a starter `./amethyst.toml` with every setting listed,
197
+ defaulted and commented out. `./` means the working directory, not the
198
+ document's — `init` writes there and `convert` reads there, so the two agree.
199
+
200
+ A flag you did not type does not overrule the config file, only one you did.
201
+ And a path named inside a config file resolves against that file, so a
202
+ stylesheet named in `~/.config/amethyst/config.toml` means the one beside it.
203
+
204
+ ---
205
+
206
+ ## Themes
207
+
208
+ A theme is one TOML file declaring fonts, a type scale, colours, spacing and
209
+ page geometry. It compiles two ways — to CSS custom properties for the PDF,
210
+ and to Word style definitions for the DOCX — which is what keeps the two
211
+ outputs looking like the same document.
212
+
213
+ Three ship: `default` (a serif face and a violet accent), `academic` (a quiet
214
+ book serif, a narrow measure, generous margins) and `github` (system sans,
215
+ blue links, quiet rules).
216
+
217
+ To write your own, start from a builtin and change what you want:
218
+
219
+ ```sh
220
+ amethyst themes show default > house.toml
221
+ amethyst convert report.md -t house.toml
222
+ ```
223
+
224
+ Anything a theme leaves out is taken from `default`, so a theme can be one
225
+ section — or one line:
226
+
227
+ ```toml
228
+ description = "House style."
229
+
230
+ [fonts]
231
+ body = ["Charter", "Georgia", "serif"]
232
+
233
+ [colors]
234
+ accent = "#0b6e4f"
235
+
236
+ [type]
237
+ size = 10.5
238
+
239
+ [page]
240
+ size = "Letter"
241
+ margin = "1in 1.25in"
242
+ ```
243
+
244
+ Sizes are numbers, not CSS lengths: `size = 11` is points, and everything else
245
+ (`headings`, `spacing`, `code`, `title`) is a multiple of it — so changing
246
+ `size` alone rescales the whole document. Colours are hex, because a Word
247
+ style cannot take any other notation.
248
+
249
+ ---
250
+
251
+ ## Remote images
252
+
253
+ An image with an `http(s)` URL is downloaded once, before either renderer
254
+ starts, and cached under `~/.cache/amethyst/images` — `XDG_CACHE_HOME` is
255
+ honoured. A second conversion of the same document makes no request.
256
+ `--no-remote` turns it off, and a fetch that fails warns and leaves a gap
257
+ rather than stopping the conversion.
258
+
259
+ Amethyst opens a socket in exactly one place, and only for this.
260
+
261
+ ---
262
+
263
+ ## Development
264
+
265
+ ```sh
266
+ uv sync
267
+ uv run amethyst convert tests/fixtures/kitchen-sink.md -o out.pdf
268
+ ```
269
+
270
+ Four checks, all of which should be clean:
271
+
272
+ ```sh
273
+ uv run pytest -q
274
+ uv run ruff check src/ tests/
275
+ uv run ruff format --check src/ tests/
276
+ uv run mypy src/amethyst
277
+ ```
278
+
279
+ `tests/fixtures/kitchen-sink.md` exercises every feature in the table above
280
+ and is converted to both formats by the suite. The suite is offline by
281
+ construction — a test that reaches the network fails rather than waiting on a
282
+ socket — and PDF tests skip with a clear message when Pango is absent.
283
+
284
+ The one thing tests cannot check is whether a page *looks* right: extracted
285
+ text and page counts cannot see layout. Both rendering defects found while
286
+ building the PDF path passed every assertion. So look at the output after
287
+ changing any CSS.
288
+
289
+ ---
290
+
291
+ ## Licence
292
+
293
+ MIT. See [LICENSE](LICENSE).