document-sanity 0.2.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.
- document_sanity-0.2.2/.claude-plugin/plugin.json +13 -0
- document_sanity-0.2.2/.gitignore +11 -0
- document_sanity-0.2.2/LICENSE +21 -0
- document_sanity-0.2.2/PKG-INFO +394 -0
- document_sanity-0.2.2/README.md +364 -0
- document_sanity-0.2.2/docs/README.md +17 -0
- document_sanity-0.2.2/docs/bibliography.md +125 -0
- document_sanity-0.2.2/docs/drawio-figures.md +141 -0
- document_sanity-0.2.2/docs/figures.md +143 -0
- document_sanity-0.2.2/docs/html-multi-plot-standard.md +243 -0
- document_sanity-0.2.2/docs/html-viewer.md +194 -0
- document_sanity-0.2.2/docs/preview-blocks.md +134 -0
- document_sanity-0.2.2/docs/references-and-labels.md +133 -0
- document_sanity-0.2.2/docs/variables-and-provenance.md +135 -0
- document_sanity-0.2.2/docs/word.md +155 -0
- document_sanity-0.2.2/pyproject.toml +99 -0
- document_sanity-0.2.2/scripts/sync_template.py +84 -0
- document_sanity-0.2.2/skills/document-sanity/SKILL.md +104 -0
- document_sanity-0.2.2/skills/document-sanity/modes/_shared.md +94 -0
- document_sanity-0.2.2/skills/document-sanity/modes/check.md +48 -0
- document_sanity-0.2.2/skills/document-sanity/modes/cite.md +66 -0
- document_sanity-0.2.2/skills/document-sanity/modes/deploy.md +76 -0
- document_sanity-0.2.2/skills/document-sanity/modes/figures.md +97 -0
- document_sanity-0.2.2/skills/document-sanity/modes/import.md +61 -0
- document_sanity-0.2.2/skills/document-sanity/modes/init.md +79 -0
- document_sanity-0.2.2/skills/document-sanity/modes/iterate.md +74 -0
- document_sanity-0.2.2/skills/document-sanity/modes/onboard.md +127 -0
- document_sanity-0.2.2/skills/document-sanity/modes/variables.md +82 -0
- document_sanity-0.2.2/skills/document-sanity/modes/version.md +48 -0
- document_sanity-0.2.2/skills/document-sanity/modes/word.md +94 -0
- document_sanity-0.2.2/src/document_sanity/__init__.py +3 -0
- document_sanity-0.2.2/src/document_sanity/__main__.py +7 -0
- document_sanity-0.2.2/src/document_sanity/bib.py +250 -0
- document_sanity-0.2.2/src/document_sanity/build.py +637 -0
- document_sanity-0.2.2/src/document_sanity/check.py +308 -0
- document_sanity-0.2.2/src/document_sanity/cli.py +1021 -0
- document_sanity-0.2.2/src/document_sanity/docx_builder.py +422 -0
- document_sanity-0.2.2/src/document_sanity/docx_content.py +406 -0
- document_sanity-0.2.2/src/document_sanity/docx_extract.py +321 -0
- document_sanity-0.2.2/src/document_sanity/docx_layout.py +257 -0
- document_sanity-0.2.2/src/document_sanity/docx_math.py +135 -0
- document_sanity-0.2.2/src/document_sanity/docx_styles.py +164 -0
- document_sanity-0.2.2/src/document_sanity/docx_template.py +293 -0
- document_sanity-0.2.2/src/document_sanity/docx_xml.py +381 -0
- document_sanity-0.2.2/src/document_sanity/drawio_fig.py +473 -0
- document_sanity-0.2.2/src/document_sanity/figure_crop.py +121 -0
- document_sanity-0.2.2/src/document_sanity/figure_manifest.py +124 -0
- document_sanity-0.2.2/src/document_sanity/html_builder.py +1265 -0
- document_sanity-0.2.2/src/document_sanity/init_project.py +644 -0
- document_sanity-0.2.2/src/document_sanity/labels.py +55 -0
- document_sanity-0.2.2/src/document_sanity/latex2md.py +554 -0
- document_sanity-0.2.2/src/document_sanity/make_version.py +85 -0
- document_sanity-0.2.2/src/document_sanity/manifest.py +630 -0
- document_sanity-0.2.2/src/document_sanity/md2docx.py +765 -0
- document_sanity-0.2.2/src/document_sanity/md2html.py +659 -0
- document_sanity-0.2.2/src/document_sanity/md2latex.py +556 -0
- document_sanity-0.2.2/src/document_sanity/pages.py +266 -0
- document_sanity-0.2.2/src/document_sanity/plotly_html.py +352 -0
- document_sanity-0.2.2/src/document_sanity/preview.py +739 -0
- document_sanity-0.2.2/src/document_sanity/project.py +61 -0
- document_sanity-0.2.2/src/document_sanity/site.py +279 -0
- document_sanity-0.2.2/src/document_sanity/template/.gitignore +36 -0
- document_sanity-0.2.2/src/document_sanity/template/AGENTS.md +45 -0
- document_sanity-0.2.2/src/document_sanity/template/Makefile +72 -0
- document_sanity-0.2.2/src/document_sanity/template/README.md +234 -0
- document_sanity-0.2.2/src/document_sanity/template/pyproject.toml +31 -0
- document_sanity-0.2.2/src/document_sanity/template/src/manuscript/docs/discussion.md +19 -0
- document_sanity-0.2.2/src/document_sanity/template/src/manuscript/docs/introduction.md +33 -0
- document_sanity-0.2.2/src/document_sanity/template/src/manuscript/docs/methods.md +95 -0
- document_sanity-0.2.2/src/document_sanity/template/src/manuscript/docs/results.md +42 -0
- document_sanity-0.2.2/src/document_sanity/template/src/manuscript/figures/example/example.png +0 -0
- document_sanity-0.2.2/src/document_sanity/template/src/manuscript/figures/example/example.svg +16 -0
- document_sanity-0.2.2/src/document_sanity/template/src/manuscript/manifest.yaml +112 -0
- document_sanity-0.2.2/src/document_sanity/template/src/manuscript/references.bib +20 -0
- document_sanity-0.2.2/src/document_sanity/template/src/manuscript/tables/subgroup_summary.csv +6 -0
- document_sanity-0.2.2/src/document_sanity/template/src/proposal/docs/0_cover.md +19 -0
- document_sanity-0.2.2/src/document_sanity/template/src/proposal/docs/1_introduction.md +25 -0
- document_sanity-0.2.2/src/document_sanity/template/src/proposal/docs/2_aims.md +28 -0
- document_sanity-0.2.2/src/document_sanity/template/src/proposal/docs/3_approach.md +74 -0
- document_sanity-0.2.2/src/document_sanity/template/src/proposal/docs/4_plan.md +35 -0
- document_sanity-0.2.2/src/document_sanity/template/src/proposal/docs/5_impact.md +30 -0
- document_sanity-0.2.2/src/document_sanity/template/src/proposal/figures/architecture/architecture.png +0 -0
- document_sanity-0.2.2/src/document_sanity/template/src/proposal/manifest.yaml +99 -0
- document_sanity-0.2.2/src/document_sanity/template/src/proposal/references.bib +28 -0
- document_sanity-0.2.2/src/document_sanity/template/src/proposal/tables/milestones.csv +7 -0
- document_sanity-0.2.2/src/document_sanity/template/src/proposal/tables/risk_register.tsv +6 -0
- document_sanity-0.2.2/src/document_sanity/template/styles/article.css +30 -0
- document_sanity-0.2.2/src/document_sanity/template/styles/article.yaml +99 -0
- document_sanity-0.2.2/src/document_sanity/template/templates/article.docx +0 -0
- document_sanity-0.2.2/src/document_sanity/template/templates/article.tex +57 -0
- document_sanity-0.2.2/src/document_sanity/template/templates/logo.png +0 -0
- document_sanity-0.2.2/src/document_sanity/template/templates/proposal.tex +62 -0
- document_sanity-0.2.2/src/document_sanity/template/templates/watermark.png +0 -0
- document_sanity-0.2.2/src/document_sanity/template/uv.lock +348 -0
- document_sanity-0.2.2/src/document_sanity/template-source.json +4 -0
- document_sanity-0.2.2/src/document_sanity/variable_processor.py +374 -0
- document_sanity-0.2.2/src/document_sanity/verbatim.py +61 -0
- document_sanity-0.2.2/src/document_sanity/version.py +176 -0
- document_sanity-0.2.2/tests/conftest.py +63 -0
- document_sanity-0.2.2/tests/test_check.py +134 -0
- document_sanity-0.2.2/tests/test_code_verbatim.py +201 -0
- document_sanity-0.2.2/tests/test_docx_layout.py +219 -0
- document_sanity-0.2.2/tests/test_docx_math.py +94 -0
- document_sanity-0.2.2/tests/test_drawio_fig.py +391 -0
- document_sanity-0.2.2/tests/test_html_front_matter.py +54 -0
- document_sanity-0.2.2/tests/test_html_tables.py +35 -0
- document_sanity-0.2.2/tests/test_init.py +422 -0
- document_sanity-0.2.2/tests/test_manifest_abstract.py +59 -0
- document_sanity-0.2.2/tests/test_md2docx.py +87 -0
- document_sanity-0.2.2/tests/test_md2html.py +66 -0
- document_sanity-0.2.2/tests/test_md2latex.py +34 -0
- document_sanity-0.2.2/tests/test_notice.py +59 -0
- document_sanity-0.2.2/tests/test_pages.py +201 -0
- document_sanity-0.2.2/tests/test_resolve_figure.py +56 -0
- document_sanity-0.2.2/tests/test_skill.py +82 -0
- document_sanity-0.2.2/tests/test_variable_processor.py +119 -0
- document_sanity-0.2.2/uv.lock +491 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "document-sanity",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Papers and proposals from markdown and one manifest.yaml per document: PDF via LaTeX, an interactive HTML viewer and Word, every number with provenance. /document-sanity [init | onboard | iterate | figures | variables | cite | word | version | import | check | deploy]. Part of Sanitism.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "yakaboskic",
|
|
7
|
+
"url": "https://github.com/yakaboskic"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://sanitism.dev",
|
|
10
|
+
"repository": "https://github.com/yakaboskic/document-sanity",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": ["latex", "markdown", "word", "docx", "manuscripts", "provenance", "sanitism"]
|
|
13
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 yakaboskic
|
|
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,394 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: document-sanity
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: Build PDF (LaTeX), interactive HTML, and Word (.docx) documents from a single Markdown source of truth — with variable substitution, figure management, and versioned builds.
|
|
5
|
+
Project-URL: Homepage, https://sanitism.dev
|
|
6
|
+
Project-URL: Documentation, https://github.com/yakaboskic/document-sanity/tree/main/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/yakaboskic/document-sanity
|
|
8
|
+
Project-URL: Issues, https://github.com/yakaboskic/document-sanity/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/yakaboskic/document-sanity/releases
|
|
10
|
+
Project-URL: Demo, https://document.sanitism.dev
|
|
11
|
+
Author: Chase Yakaboski
|
|
12
|
+
License-Expression: MIT
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Keywords: docx,latex,manuscripts,markdown,provenance,sanitism,word
|
|
15
|
+
Classifier: Development Status :: 4 - Beta
|
|
16
|
+
Classifier: Environment :: Console
|
|
17
|
+
Classifier: Intended Audience :: Science/Research
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering
|
|
22
|
+
Classifier: Topic :: Text Processing :: Markup :: LaTeX
|
|
23
|
+
Classifier: Topic :: Text Processing :: Markup :: Markdown
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: coolname>=4.0.0
|
|
26
|
+
Requires-Dist: numpy>=1.24
|
|
27
|
+
Requires-Dist: pillow>=10.0.0
|
|
28
|
+
Requires-Dist: pyyaml>=6.0
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
<a href="https://sanitism.dev"><img src="https://sanitism.dev/media/one-number-poster.jpg" alt="One number in manifest.yaml and a sentence in docs/results.md build into LaTeX, PDF, Word, HTML, slides and a blog post, each showing 12,453." width="100%"></a>
|
|
32
|
+
|
|
33
|
+
# document-sanity
|
|
34
|
+
|
|
35
|
+
**Write a paper, proposal or report once in Markdown. Get LaTeX, a PDF, a Word file and an interactive HTML page, with every number traced to the script that made it.**
|
|
36
|
+
|
|
37
|
+
Part of [Sanitism](https://sanitism.dev): built for autists to communicate their work, sanely. The sibling for talks is [presentation-sanity](https://github.com/yakaboskic/presentation-sanity).
|
|
38
|
+
|
|
39
|
+
**See it:** [the starter paper](https://document.sanitism.dev/manuscript/), [its PDF](https://document.sanitism.dev/manuscript/manuscript.pdf), [its Word file](https://document.sanitism.dev/manuscript/manuscript.docx) and [a proposal](https://document.sanitism.dev/proposal/), all built from [document-sanity-template](https://github.com/yakaboskic/document-sanity-template).
|
|
40
|
+
|
|
41
|
+
## Start a project
|
|
42
|
+
|
|
43
|
+
<img src="https://sanitism.dev/figures/two-ways-in.svg" alt="A template repo leads two ways. Use this template on GitHub gives the project a repo with history. Running uvx init gives a plain folder with no git. Both arrive at the same project." width="100%">
|
|
44
|
+
|
|
45
|
+
**Just give me a folder.** No clone, no git. It needs [uv](https://docs.astral.sh/uv/):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uvx document-sanity init my-paper --title "My paper"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cd my-paper
|
|
53
|
+
uv sync # the pinned document-sanity; writes uv.lock (commit it)
|
|
54
|
+
make html VERSION=manuscript # → out/manuscript/html/index.html
|
|
55
|
+
make pdf VERSION=manuscript # → out/manuscript/pdf/main.pdf (needs TeX)
|
|
56
|
+
uv run document-sanity check # every target, before you share
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Keep it in git.** On [document-sanity-template](https://github.com/yakaboskic/document-sanity-template), click **Use this template**, clone your copy and run `python init.py`. It is a thin shim over the same `init`, so both ways give you the same project: the starters you asked for, pinned to this release, with the [agent skill](https://github.com/yakaboskic/document-sanity#agent-skill) installed.
|
|
60
|
+
|
|
61
|
+
<details>
|
|
62
|
+
<summary><b>What <code>init</code> does, flag by flag</b></summary>
|
|
63
|
+
|
|
64
|
+
`init` copies the snapshot of [`document-sanity-template`](https://github.com/yakaboskic/document-sanity-template) that ships inside this release, so a project made by vX.Y.Z is the template vX.Y.Z was released with, and its dependency line is pinned back to vX.Y.Z.
|
|
65
|
+
|
|
66
|
+
- **Answers** are prompted for, or given as flags: `--name` (default: the folder's name), `--title`, `--author` and `--email` (default: your git config), `--description`, and `--starter` (`manuscript`, `proposal`, or `both`, the default). `--yes` takes the defaults for anything unset. The name goes into `pyproject.toml` and `README.md`; the title and author into each kept starter's `manifest.yaml` (the lines the template marks `TEMPLATE:`); the starters you don't keep are left out.
|
|
67
|
+
- **The folder** can be new, empty, or an existing one to knit into (`init .`). Everything is assembled in memory first. If any file would be replaced, `init` lists them and writes nothing; `--force` overwrites them. A folder that already has documents under `src/` is refused outright (`skills` refreshes the skill; `new-version` adds a document).
|
|
68
|
+
- **`--git`** adds `git init` and a first commit. Without it there's no `.git`.
|
|
69
|
+
- **`uv.lock`** is left out, `--in-place` included: the template's lock is one release behind the snapshot's pin, and renaming the project renames its root package. The project's first `uv sync` writes a lock for the pinned tag. Commit it; the Pages workflow installs with `--locked`.
|
|
70
|
+
- **`--from gh:owner/repo[@ref]`** starts from a GitHub tarball instead, e.g. your lab's fork of the template. No git binary needed; `GH_TOKEN` (or `GITHUB_TOKEN`) reaches a private fork. A fork's own pin is left alone.
|
|
71
|
+
- **The agent skill** goes into `.claude/skills/document-sanity/` and `.agents/skills/document-sanity/` unless `--no-skills`.
|
|
72
|
+
|
|
73
|
+
</details>
|
|
74
|
+
|
|
75
|
+
## What goes in, what comes out
|
|
76
|
+
|
|
77
|
+
<img src="https://sanitism.dev/figures/document-pipeline.svg" alt="What you write: manifest.yaml, docs/*.md, figures, tables and refs.bib, and templates. One document-sanity check builds every target. What your readers get: LaTeX, PDF, Word, and interactive HTML where 12,453 traces back to data/qc.csv and scripts/qc.py." width="100%">
|
|
78
|
+
|
|
79
|
+
You write plain text that diffs well. One `manifest.yaml` per document holds the title, authors, section order and every number with its source. The same Markdown sections then print four ways:
|
|
80
|
+
|
|
81
|
+
- **LaTeX.** A clean, compilation-ready `out/<doc>/latex/main.tex` from your own `templates/<name>.tex`, for the journal or Overleaf.
|
|
82
|
+
- **PDF.** That LaTeX compiled with your class and bib style.
|
|
83
|
+
- **Word.** A `.docx` built on your own Word template: its headers, footers, theme and styles are kept, and tables, headings and figures stay together across page breaks.
|
|
84
|
+
- **Interactive HTML.** A single page with a contents sidebar, KaTeX math, a linked bibliography, and numbers that open their provenance when clicked.
|
|
85
|
+
|
|
86
|
+
| Command | Output | When |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| `document-sanity check [DOC…]` | every target, plus broken references | Before you share. Exits non-zero on anything to fix. |
|
|
89
|
+
| `document-sanity build --compile` | `out/<doc>/latex/`, `out/<doc>/pdf/main.pdf` | The LaTeX tree and the PDF. |
|
|
90
|
+
| `document-sanity word` | `out/<doc>/word/main.docx` | The Word file. |
|
|
91
|
+
| `document-sanity html --open` | `out/<doc>/html/index.html` | The interactive viewer. |
|
|
92
|
+
| `document-sanity preview` | preview blocks in each `.md` | Makes ```` ```latex ```` blocks readable on GitHub. |
|
|
93
|
+
| `document-sanity new-version` | a copied `src/<doc>/` | A new version is a new folder. |
|
|
94
|
+
| `document-sanity site` / `pages init` | `site/`, a Pages workflow | Publish every document ([below](https://github.com/yakaboskic/document-sanity#publish-on-github-pages)). |
|
|
95
|
+
|
|
96
|
+
<details>
|
|
97
|
+
<summary><b>Every command</b></summary>
|
|
98
|
+
|
|
99
|
+
| Command | Output | When to use |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| `document-sanity init [DIR]` | A project from the template | Start a new project ([above](https://github.com/yakaboskic/document-sanity#start-a-project)). |
|
|
102
|
+
| `document-sanity build --compile` | `out/<doc>/pdf/main.pdf` | Build the LaTeX tree and compile the PDF. |
|
|
103
|
+
| `document-sanity html --open` | `out/<doc>/html/index.html` | Interactive viewer: TOC sidebar, KaTeX math, clickable variables with a provenance panel, hyperlinked bibliography. |
|
|
104
|
+
| `document-sanity word` | `out/<doc>/word/main.docx` | Word document from a `.docx` template in `templates/`. Styles are extracted from the template, figures are embedded, citations render as a numbered References list, and tables, headings and figures are kept together across page breaks (`--no-layout` skips that). |
|
|
105
|
+
| `document-sanity word --extract-styles` | `out/<doc>/word/styles.json` | Dump the template's extracted styles to hand-tune before the next build. |
|
|
106
|
+
| `document-sanity preview` | Inline preview blocks in each `.md` | Make ```` ```latex ```` figure/equation/table blocks render as `` / `$$...$$` / `\|cell\|` so GitHub shows something meaningful. Idempotent; hash-tagged. |
|
|
107
|
+
| `document-sanity preview --check` | Exit 1 if stale | CI-friendly. |
|
|
108
|
+
| `document-sanity check [DOC…]` | Exit 1 on anything to fix | Every target (PDF, HTML, Word) of every document, plus `preview --check` and unresolved variables / citations / `\ref`s ([below](https://github.com/yakaboskic/document-sanity#check-before-you-share)). |
|
|
109
|
+
| `document-sanity site [DOC…]` | `site/` | Every document's HTML viewer, PDF and Word, with an index page. What the Pages workflow publishes. |
|
|
110
|
+
| `document-sanity pages init` | `.github/workflows/pages.yml` | Publish on GitHub Pages (public! it asks first). |
|
|
111
|
+
| `document-sanity new-version` | Copied `src/<doc>/` | Snapshot a document into a new version folder. |
|
|
112
|
+
| `document-sanity import` | New `src/<doc>/` tree | Convert an existing `\input{}`-style LaTeX project into Markdown sources. |
|
|
113
|
+
| `document-sanity skills` | `.claude/skills/`, `.agents/skills/` | Install or refresh the [agent skill](https://github.com/yakaboskic/document-sanity#agent-skill). |
|
|
114
|
+
| `document-sanity convert file.md/.tex` | The other format | One-off md↔tex conversion (useful for scripting). |
|
|
115
|
+
| `document-sanity drawio sync <fig-id>` | Refreshed `figures/<id>/<id>.png` | Re-import regenerated source assets into a draw.io-composed figure and re-export the PNG ([docs](https://github.com/yakaboskic/document-sanity/blob/main/docs/drawio-figures.md)). |
|
|
116
|
+
| `document-sanity drawio status <fig-id>` | Exit 1 if stale | CI-friendly staleness check for composed figures. |
|
|
117
|
+
|
|
118
|
+
</details>
|
|
119
|
+
|
|
120
|
+
## A project, folder by folder
|
|
121
|
+
|
|
122
|
+
This is what `init` writes. Every document is a folder under `src/`, and versions are folders too.
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
my-paper/
|
|
126
|
+
├── pyproject.toml # pins document-sanity to a release
|
|
127
|
+
├── Makefile # install, latex, pdf, html, word, check, site, …
|
|
128
|
+
├── AGENTS.md # points coding agents at the skill
|
|
129
|
+
├── templates/
|
|
130
|
+
│ ├── article.tex # LaTeX template (the manuscript's look)
|
|
131
|
+
│ ├── proposal.tex # the proposal's: same packages, unnumbered sections
|
|
132
|
+
│ └── article.docx # Word template: header logo, page-of-N, watermark, styles
|
|
133
|
+
├── styles/
|
|
134
|
+
│ ├── article.yaml # Word styles, round-tripped from the template
|
|
135
|
+
│ └── article.css # extra HTML styles
|
|
136
|
+
├── src/
|
|
137
|
+
│ ├── manuscript/ # a paper: title block, abstract, IMRaD sections
|
|
138
|
+
│ │ ├── manifest.yaml # the one source of configuration
|
|
139
|
+
│ │ ├── docs/*.md # sections, in manifest order
|
|
140
|
+
│ │ ├── figures/<id>/ # one folder per figure, any formats
|
|
141
|
+
│ │ ├── tables/ # CSV, TSV, Markdown or LaTeX tables
|
|
142
|
+
│ │ └── references.bib
|
|
143
|
+
│ └── proposal/ # a proposal: hand-numbered sections, its own cover
|
|
144
|
+
└── out/ # generated, gitignored
|
|
145
|
+
└── manuscript/{latex,pdf,html,word}/
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## The manifest
|
|
149
|
+
|
|
150
|
+
One file per document. This is the starter manuscript's, shortened:
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
metadata:
|
|
154
|
+
title: "A Starter Manuscript Built with document-sanity"
|
|
155
|
+
authors:
|
|
156
|
+
- { name: "First Author", email: "first.author@example.edu", affiliations: [1] }
|
|
157
|
+
affiliations:
|
|
158
|
+
1: { department: "Department of Research", institution: "Example University" }
|
|
159
|
+
abstract: |
|
|
160
|
+
We analyzed {{NUM_SAMPLES:,}} samples and found a significant effect
|
|
161
|
+
($p = {{PVAL:.2e}}$, R<sup>2</sup> = {{R2:.2f}}).
|
|
162
|
+
template: article # templates/article.tex (LaTeX, PDF)
|
|
163
|
+
word_template: article # templates/article.docx (Word)
|
|
164
|
+
# notice: | # optional banner above the first section, in every
|
|
165
|
+
# Distribution limited. # output (CUI, distribution statements)
|
|
166
|
+
|
|
167
|
+
sections:
|
|
168
|
+
- docs/introduction.md
|
|
169
|
+
- docs/methods.md
|
|
170
|
+
- docs/results.md
|
|
171
|
+
- docs/discussion.md
|
|
172
|
+
- _bibliography # inserts the reference list here
|
|
173
|
+
|
|
174
|
+
variables:
|
|
175
|
+
NUM_SAMPLES: 1247 # the simple form
|
|
176
|
+
PVAL: # the full form: the HTML viewer shows where it came from
|
|
177
|
+
value: 0.0087
|
|
178
|
+
provenance:
|
|
179
|
+
description: P-value of the primary test, after Benjamini–Hochberg adjustment.
|
|
180
|
+
source: data/analysis_results.parquet
|
|
181
|
+
data: [data/raw_measurements.csv, data/phenotype_metadata.tsv]
|
|
182
|
+
command: python scripts/primary_test.py --out data/analysis_results.parquet
|
|
183
|
+
updated: "2026-04-21"
|
|
184
|
+
|
|
185
|
+
figures:
|
|
186
|
+
example: { width: "\\textwidth" } # picks figures/example/example.{pdf,png,svg,html} per target
|
|
187
|
+
|
|
188
|
+
tables:
|
|
189
|
+
subgroup_summary: { source: tables/subgroup_summary.csv, format: csv }
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Writing in Markdown
|
|
193
|
+
|
|
194
|
+
````markdown
|
|
195
|
+
We analyzed {{NUM_SAMPLES:,}} samples ($p = {{PVAL:.2e}}$), summarized in Table \ref{tab:subgroups}.
|
|
196
|
+
|
|
197
|
+
{{tab:subgroup_summary}}
|
|
198
|
+
|
|
199
|
+
```latex
|
|
200
|
+
\begin{figure}[h!]
|
|
201
|
+
\centering
|
|
202
|
+
{{fig:example}}
|
|
203
|
+
\caption{The framework at a glance.}
|
|
204
|
+
\label{fig:overview}
|
|
205
|
+
\end{figure}
|
|
206
|
+
```
|
|
207
|
+
````
|
|
208
|
+
|
|
209
|
+
- **Numbers** come from `{{NAME}}` or `{{NAME:fmt}}` with Python-style specs (`:,`, `:.2f`, `:.2e`, `:.1%`). An undefined name renders as `XXXX` and is reported, never silently blank.
|
|
210
|
+
- **Figures** live in `figures/<id>/` in as many formats as you have; each target picks its best (PDF prefers vector, HTML prefers interactive HTML or SVG, Word prefers raster). See [docs/figures.md](https://github.com/yakaboskic/document-sanity/blob/main/docs/figures.md).
|
|
211
|
+
- **Tables** come from CSV, TSV, Markdown or LaTeX files via `{{tab:<id>}}`, or as plain pipe tables inline.
|
|
212
|
+
- **Citations and references** are LaTeX-shaped everywhere: `\cite{key}` against `references.bib`, and `\ref{fig:…}` / `tab:` / `eq:` labels numbered in every output ([docs](https://github.com/yakaboskic/document-sanity/blob/main/docs/references-and-labels.md)).
|
|
213
|
+
- **LaTeX where it counts.** Anything Markdown can't say goes in a ```` ```latex ```` block. `document-sanity preview` writes a readable stand-in next to each one so GitHub, VS Code and Obsidian show something real ([docs](https://github.com/yakaboskic/document-sanity/blob/main/docs/preview-blocks.md)).
|
|
214
|
+
|
|
215
|
+
## Check before you share
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
uv run document-sanity check # every document under src/
|
|
219
|
+
uv run document-sanity check manuscript # or some (inside src/<doc>/, that one)
|
|
220
|
+
uv run document-sanity check --no-pdf # no TeX here
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
For each document, in order: `preview --check` (every ```` ```latex ```` block's preview is current), the LaTeX build compiled to PDF (`!` errors in the log fail it; no TeX installed is a warning), unresolved references (undefined `{{VARIABLES}}`, `\cite` keys missing from `references.bib`, `\ref`s with no `\label`, sections listed but missing), the HTML viewer, and the Word file. Builder output is shown only for a failing step (`-v` shows everything). It exits non-zero on any error, so it works in CI too.
|
|
224
|
+
|
|
225
|
+
## Publish on GitHub Pages
|
|
226
|
+
|
|
227
|
+
> [!WARNING]
|
|
228
|
+
> A GitHub Pages site is **public**, even when the repository is private.
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
uv run document-sanity pages init # asks first (--yes once the owner agreed)
|
|
232
|
+
uv run document-sanity site # what the workflow will publish → site/
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`pages init` writes `.github/workflows/pages.yml`. On each push to `main` it checks out the repo, installs a TeX Live subset from Ubuntu's packages, runs `uv sync --locked`, runs `document-sanity site` (every document's HTML viewer, PDF and Word, with an index page) and deploys `site/`. Then turn Pages on (Settings → Pages → Source: **GitHub Actions**) and push. The [template's own demo](https://document.sanitism.dev/) is built exactly this way.
|
|
236
|
+
|
|
237
|
+
- **The privacy gate.** `pages init` asks before writing anything (with no terminal, it refuses without `--yes`). A document whose manifest sets `metadata.notice` is refused unless `--allow-notice`, and the workflow enforces the same rule on every build, so a notice added later stops the deploy instead of publishing it. Name documents to publish only those: `pages init manuscript`.
|
|
238
|
+
- **PDFs need TeX on the runner.** The apt subset adds a minute or two per run; `--no-pdf` leaves TeX out and publishes HTML and Word only.
|
|
239
|
+
- **Options:** `--branch` (default `main`), `--only-repo OWNER/NAME` (forks and template copies skip the job), `--force` to regenerate.
|
|
240
|
+
- **Any static host works.** Every link in `site/` is relative, so the same folder works on S3, Netlify or an intranet share under any URL prefix.
|
|
241
|
+
|
|
242
|
+
## Versions are folders
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
uv run document-sanity new-version --source manuscript # → MMDDYYYY-<fun-name>/
|
|
246
|
+
uv run document-sanity new-version --source manuscript --strategy date # MMDDYYYY
|
|
247
|
+
uv run document-sanity new-version --source proposal --name resubmission
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The original stays as the archived snapshot. Without `--source` it copies the latest version: the newest dated folder, or else the last name alphabetically. `build`, `html` and `preview` default to that latest version too.
|
|
251
|
+
|
|
252
|
+
**Coming from a LaTeX project?** `document-sanity import --source ./old-paper --target ./new-paper --manuscript <name> --version <ver>` walks a `versions/<name>/main_<ver>.tex` + `sections/<name>/<ver>/*.tex` layout and writes a `src/<ver>/` tree: sections become Markdown, environments that can't be translated losslessly stay in ```` ```latex ```` blocks, and variables, the bibliography and figures come along.
|
|
253
|
+
|
|
254
|
+
<details>
|
|
255
|
+
<summary><b>Word output in detail</b></summary>
|
|
256
|
+
|
|
257
|
+
Drop a `.docx` into `templates/` and point `metadata.word_template` at it.
|
|
258
|
+
|
|
259
|
+
- **Styles** are extracted from the template's `styles.xml` + `theme1.xml` (Title, Heading 1–3, Normal, accent colors) and applied to the generated body. `word --extract-styles` dumps them; pass `--styles path.json`, or `metadata.word_styles`, to override after hand-tuning.
|
|
260
|
+
- **Body only.** The template's `<w:sectPr>` (headers, footers, margins, theme refs) is kept; the body is replaced.
|
|
261
|
+
- **Variables, figures and citations** resolve as in the LaTeX build. Figures are embedded as centered drawings with captions (Word prefers `png/jpg/gif/bmp`); `\cite{key}` renders as numbered `[N]` with a References section where `_bibliography` sits.
|
|
262
|
+
- **Math** becomes native Word equations.
|
|
263
|
+
- **Layout.** Table rows don't split and header rows repeat, headings keep with the next paragraph, figures keep with their captions, and equations take the body font size. `--no-layout` skips this pass.
|
|
264
|
+
|
|
265
|
+
</details>
|
|
266
|
+
|
|
267
|
+
<details>
|
|
268
|
+
<summary><b>The HTML viewer in detail</b></summary>
|
|
269
|
+
|
|
270
|
+
`document-sanity html` writes one `index.html` (plus copied figures):
|
|
271
|
+
|
|
272
|
+
- a left contents sidebar with scroll-spy;
|
|
273
|
+
- client-side KaTeX math, with the template's `\newcommand` macros registered automatically;
|
|
274
|
+
- clickable variables: a provenance panel slides in with the description, an inputs → command → output graph and the updated date;
|
|
275
|
+
- a hyperlinked bibliography rendered from `references.bib`;
|
|
276
|
+
- figures as `<img>`, `.html` figures (Plotly) as `<iframe>`, PDFs as `<embed>`.
|
|
277
|
+
|
|
278
|
+
It is self-contained apart from the Tailwind and KaTeX CDNs. Serve it with `python3 -m http.server` from `html/`. See [docs/html-viewer.md](https://github.com/yakaboskic/document-sanity/blob/main/docs/html-viewer.md).
|
|
279
|
+
|
|
280
|
+
</details>
|
|
281
|
+
|
|
282
|
+
## Agent skill
|
|
283
|
+
|
|
284
|
+
`init` installs a skill for coding agents into `.claude/skills/document-sanity/` (Claude Code: `/document-sanity [mode]`) and `.agents/skills/document-sanity/` (other agents). Ask for the work instead of looking up flags:
|
|
285
|
+
|
|
286
|
+
| Ask | What happens |
|
|
287
|
+
|---|---|
|
|
288
|
+
| `/document-sanity onboard` | A folder of Word files, PDFs and CSVs becomes a new document. |
|
|
289
|
+
| `/document-sanity iterate` | The edit → preview → build loop. |
|
|
290
|
+
| `/document-sanity check` | Every output built, every reference resolved, before you share. |
|
|
291
|
+
| `/document-sanity deploy` | GitHub Pages, after confirming the work is meant to be public. |
|
|
292
|
+
|
|
293
|
+
The other modes: `init`, `figures` (including draw.io sync), `variables`, `cite`, `word`, `version` and `import`.
|
|
294
|
+
|
|
295
|
+
- **It explains; the CLI does.** Every mode works through commands rather than having the agent write generated files.
|
|
296
|
+
- **It finds the project** from its sentinel files (a `pyproject.toml` that depends on document-sanity, next to `src/<doc>/manifest.yaml`).
|
|
297
|
+
- **It checks the version.** When `document-sanity --version` differs from the version it was written for, it trusts `--help` over its own text.
|
|
298
|
+
- **It ends every iteration with `preview`**, runs `check` before saying done, and asks before anything public.
|
|
299
|
+
|
|
300
|
+
The wheel carries the skill; `document-sanity skills [--force]` refreshes it after an upgrade. The repo is also a Claude Code plugin (`.claude-plugin/plugin.json`).
|
|
301
|
+
|
|
302
|
+
## House rules
|
|
303
|
+
|
|
304
|
+
```yaml
|
|
305
|
+
# sanitism.yaml
|
|
306
|
+
principles:
|
|
307
|
+
one_manifest: everything else is generated, never hand-edited
|
|
308
|
+
every_number_has_a_source: the file, command and date travel with the value
|
|
309
|
+
versions_are_folders: not branches, and not final_v3_REAL.docx
|
|
310
|
+
one_repo_per_obsession: papers, proposals and talks share figures and numbers
|
|
311
|
+
same_input_same_output: a rebuild never surprises your co-authors
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Documentation
|
|
315
|
+
|
|
316
|
+
The conventions, in detail, under [`docs/`](https://github.com/yakaboskic/document-sanity/blob/main/docs):
|
|
317
|
+
|
|
318
|
+
- [Figures](https://github.com/yakaboskic/document-sanity/blob/main/docs/figures.md): one folder per figure, per-target format choice, whitespace cropping.
|
|
319
|
+
- [draw.io figures](https://github.com/yakaboskic/document-sanity/blob/main/docs/drawio-figures.md): composed figures whose source assets re-import on demand.
|
|
320
|
+
- [References & labels](https://github.com/yakaboskic/document-sanity/blob/main/docs/references-and-labels.md): `fig:` / `tab:` / `eq:` prefixes drive numbering.
|
|
321
|
+
- [Preview blocks](https://github.com/yakaboskic/document-sanity/blob/main/docs/preview-blocks.md): readable stand-ins for ```` ```latex ```` blocks on GitHub.
|
|
322
|
+
- [Variables & provenance](https://github.com/yakaboskic/document-sanity/blob/main/docs/variables-and-provenance.md): `{{VAR:fmt}}` and the provenance panel.
|
|
323
|
+
- [HTML viewer](https://github.com/yakaboskic/document-sanity/blob/main/docs/html-viewer.md) and the [multi-plot standard](https://github.com/yakaboskic/document-sanity/blob/main/docs/html-multi-plot-standard.md).
|
|
324
|
+
- [Bibliography](https://github.com/yakaboskic/document-sanity/blob/main/docs/bibliography.md) and [Word](https://github.com/yakaboskic/document-sanity/blob/main/docs/word.md).
|
|
325
|
+
|
|
326
|
+
## Install the tool directly
|
|
327
|
+
|
|
328
|
+
For local development on the tool itself:
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
git clone https://github.com/yakaboskic/document-sanity
|
|
332
|
+
cd document-sanity
|
|
333
|
+
uv sync
|
|
334
|
+
uv run document-sanity --help
|
|
335
|
+
uv run pytest
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
To iterate on the tool against a real project, clone [`document-sanity-template`](https://github.com/yakaboskic/document-sanity-template) alongside this repo and point its dependency at the local checkout:
|
|
339
|
+
|
|
340
|
+
```toml
|
|
341
|
+
[tool.uv.sources]
|
|
342
|
+
document-sanity = { path = "../document-sanity", editable = true }
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Releasing
|
|
346
|
+
|
|
347
|
+
Paper repos and the template pin release tags, so a change reaches them
|
|
348
|
+
only through a release. Each release also carries a snapshot of the template
|
|
349
|
+
(for `init`), so the two move together. Publishing a GitHub release puts the
|
|
350
|
+
version on [PyPI](https://pypi.org/project/document-sanity/), which is where
|
|
351
|
+
`uvx document-sanity` gets it. PyPI takes uploads only from `release.yml` in
|
|
352
|
+
this repo's `pypi` environment (a trusted publisher, set up once), so no
|
|
353
|
+
token is stored anywhere.
|
|
354
|
+
|
|
355
|
+
1. Bump the version in four places: `version` in `pyproject.toml`,
|
|
356
|
+
`__version__` in `src/document_sanity/__init__.py`, `metadata.version`
|
|
357
|
+
in `skills/document-sanity/SKILL.md`, and `version` in
|
|
358
|
+
`.claude-plugin/plugin.json` (`tests/test_skill.py` fails until they
|
|
359
|
+
agree). Update the tags in the snippets above and in the skill's `init`
|
|
360
|
+
mode.
|
|
361
|
+
2. In `document-sanity-template`, move the pin to the new tag (the
|
|
362
|
+
`pyproject.toml` dependency line and any version in the README), regenerate
|
|
363
|
+
the demo workflow with `document-sanity pages init --yes --force
|
|
364
|
+
--only-repo yakaboskic/document-sanity-template`, and commit.
|
|
365
|
+
3. Back here, refresh the snapshot from that commit and commit it:
|
|
366
|
+
```bash
|
|
367
|
+
uv run python scripts/sync_template.py # tracked files only, via git archive
|
|
368
|
+
uv run pytest
|
|
369
|
+
```
|
|
370
|
+
4. Push to `main`, then tag and publish:
|
|
371
|
+
```bash
|
|
372
|
+
git tag -a vX.Y.Z -m "vX.Y.Z — summary" && git push origin vX.Y.Z
|
|
373
|
+
gh release create vX.Y.Z --verify-tag --title "vX.Y.Z — summary" --notes-file notes.md
|
|
374
|
+
```
|
|
375
|
+
Publishing the release runs [`release.yml`](https://github.com/yakaboskic/document-sanity/blob/main/.github/workflows/release.yml):
|
|
376
|
+
it checks the tag is the `pyproject.toml` version, runs the tests, builds
|
|
377
|
+
the sdist and wheel, starts a project from the wheel, and uploads both to
|
|
378
|
+
PyPI. Once it's green, `uvx document-sanity@X.Y.Z --version` prints the
|
|
379
|
+
new version. If it fails, nothing was uploaded: fix it on `main`, delete
|
|
380
|
+
the release and the tag, and cut them again. PyPI never takes the same
|
|
381
|
+
version twice, so once it's up, a fix is a new patch release.
|
|
382
|
+
5. In the template, once the tag exists: `uv lock` (it resolves the new tag),
|
|
383
|
+
`make check`, commit `uv.lock`, and push. The Pages workflow (which installs
|
|
384
|
+
with `--locked`) deploys the demo. The snapshot still carries the previous
|
|
385
|
+
lock, one reason `init` leaves `uv.lock` out (in every mode): a new
|
|
386
|
+
project's first `uv sync` writes its own.
|
|
387
|
+
|
|
388
|
+
## Lineage
|
|
389
|
+
|
|
390
|
+
document-sanity grew out of [meta-sanity](https://github.com/yakaboskic/meta-sanity), which expands one YAML file into the meta files a research pipeline needs. The same idea (declare it once, generate the rest) applied to papers. The Word output started as [word-builder](https://github.com/yakaboskic/word-builder), now ported here as the `word` command.
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
<sub>Part of <a href="https://sanitism.dev">Sanitism</a>. Also in the family: <a href="https://github.com/yakaboskic/presentation-sanity">presentation-sanity</a> for talks. MIT licensed.</sub>
|