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.
- amethyst_cli-0.1.0/.github/workflows/ci.yml +132 -0
- amethyst_cli-0.1.0/.gitignore +23 -0
- amethyst_cli-0.1.0/CLAUDE.md +63 -0
- amethyst_cli-0.1.0/LICENSE +21 -0
- amethyst_cli-0.1.0/PKG-INFO +293 -0
- amethyst_cli-0.1.0/README.md +261 -0
- amethyst_cli-0.1.0/docs/documentation.md +988 -0
- amethyst_cli-0.1.0/pyproject.toml +82 -0
- amethyst_cli-0.1.0/src/amethyst/__init__.py +5 -0
- amethyst_cli-0.1.0/src/amethyst/__main__.py +6 -0
- amethyst_cli-0.1.0/src/amethyst/cli.py +644 -0
- amethyst_cli-0.1.0/src/amethyst/config.py +387 -0
- amethyst_cli-0.1.0/src/amethyst/document.py +228 -0
- amethyst_cli-0.1.0/src/amethyst/errors.py +66 -0
- amethyst_cli-0.1.0/src/amethyst/ooxml.py +549 -0
- amethyst_cli-0.1.0/src/amethyst/parse/__init__.py +20 -0
- amethyst_cli-0.1.0/src/amethyst/parse/assets.py +106 -0
- amethyst_cli-0.1.0/src/amethyst/parse/frontmatter.py +62 -0
- amethyst_cli-0.1.0/src/amethyst/parse/markdown.py +49 -0
- amethyst_cli-0.1.0/src/amethyst/remote.py +236 -0
- amethyst_cli-0.1.0/src/amethyst/render/__init__.py +42 -0
- amethyst_cli-0.1.0/src/amethyst/render/base.py +85 -0
- amethyst_cli-0.1.0/src/amethyst/render/docx.py +1060 -0
- amethyst_cli-0.1.0/src/amethyst/render/furniture.py +112 -0
- amethyst_cli-0.1.0/src/amethyst/render/highlight.py +315 -0
- amethyst_cli-0.1.0/src/amethyst/render/html.py +266 -0
- amethyst_cli-0.1.0/src/amethyst/render/pdf.py +219 -0
- amethyst_cli-0.1.0/src/amethyst/theme/__init__.py +493 -0
- amethyst_cli-0.1.0/src/amethyst/theme/builtin/academic.toml +45 -0
- amethyst_cli-0.1.0/src/amethyst/theme/builtin/css/base.css +361 -0
- amethyst_cli-0.1.0/src/amethyst/theme/builtin/default.toml +42 -0
- amethyst_cli-0.1.0/src/amethyst/theme/builtin/github.toml +44 -0
- amethyst_cli-0.1.0/src/amethyst/theme/to_css.py +182 -0
- amethyst_cli-0.1.0/src/amethyst/theme/to_docx.py +651 -0
- amethyst_cli-0.1.0/tests/conftest.py +110 -0
- amethyst_cli-0.1.0/tests/fixtures/assets/amethyst.png +0 -0
- amethyst_cli-0.1.0/tests/fixtures/kitchen-sink.md +137 -0
- amethyst_cli-0.1.0/tests/test_cli.py +642 -0
- amethyst_cli-0.1.0/tests/test_config.py +235 -0
- amethyst_cli-0.1.0/tests/test_furniture.py +104 -0
- amethyst_cli-0.1.0/tests/test_highlight.py +134 -0
- amethyst_cli-0.1.0/tests/test_parse.py +386 -0
- amethyst_cli-0.1.0/tests/test_remote.py +222 -0
- amethyst_cli-0.1.0/tests/test_render_docx.py +918 -0
- amethyst_cli-0.1.0/tests/test_render_pdf.py +577 -0
- amethyst_cli-0.1.0/tests/test_theme.py +284 -0
- 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
|
+
[](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).
|