pubify-pubs 1.0.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 (35) hide show
  1. pubify_pubs-1.0.0/LICENSE +21 -0
  2. pubify_pubs-1.0.0/MANIFEST.in +15 -0
  3. pubify_pubs-1.0.0/PKG-INFO +328 -0
  4. pubify_pubs-1.0.0/README.md +309 -0
  5. pubify_pubs-1.0.0/pyproject.toml +40 -0
  6. pubify_pubs-1.0.0/setup.cfg +4 -0
  7. pubify_pubs-1.0.0/src/pubify_pubs/__init__.py +25 -0
  8. pubify_pubs-1.0.0/src/pubify_pubs/assets/__init__.py +1 -0
  9. pubify_pubs-1.0.0/src/pubify_pubs/assets/init/__init__.py +1 -0
  10. pubify_pubs-1.0.0/src/pubify_pubs/assets/init/figures.py +52 -0
  11. pubify_pubs-1.0.0/src/pubify_pubs/assets/init/main.tex +25 -0
  12. pubify_pubs-1.0.0/src/pubify_pubs/cli.py +1806 -0
  13. pubify_pubs-1.0.0/src/pubify_pubs/commands/__init__.py +33 -0
  14. pubify_pubs-1.0.0/src/pubify_pubs/commands/common.py +228 -0
  15. pubify_pubs-1.0.0/src/pubify_pubs/commands/core.py +873 -0
  16. pubify_pubs-1.0.0/src/pubify_pubs/commands/registry.py +56 -0
  17. pubify_pubs-1.0.0/src/pubify_pubs/config.py +396 -0
  18. pubify_pubs-1.0.0/src/pubify_pubs/data.py +86 -0
  19. pubify_pubs-1.0.0/src/pubify_pubs/decorators.py +127 -0
  20. pubify_pubs-1.0.0/src/pubify_pubs/discovery.py +415 -0
  21. pubify_pubs-1.0.0/src/pubify_pubs/export.py +250 -0
  22. pubify_pubs-1.0.0/src/pubify_pubs/helpers.py +21 -0
  23. pubify_pubs-1.0.0/src/pubify_pubs/latex_bootstrap.py +130 -0
  24. pubify_pubs-1.0.0/src/pubify_pubs/runtime.py +672 -0
  25. pubify_pubs-1.0.0/src/pubify_pubs/shell_incremental.py +392 -0
  26. pubify_pubs-1.0.0/src/pubify_pubs/stats.py +134 -0
  27. pubify_pubs-1.0.0/src/pubify_pubs/stubs.py +172 -0
  28. pubify_pubs-1.0.0/src/pubify_pubs/tables.py +537 -0
  29. pubify_pubs-1.0.0/src/pubify_pubs/texlog.py +93 -0
  30. pubify_pubs-1.0.0/src/pubify_pubs.egg-info/SOURCES.txt +32 -0
  31. pubify_pubs-1.0.0/tests/test_cli_core.py +3637 -0
  32. pubify_pubs-1.0.0/tests/test_export.py +596 -0
  33. pubify_pubs-1.0.0/tests/test_release.py +71 -0
  34. pubify_pubs-1.0.0/tests/test_stats.py +125 -0
  35. pubify_pubs-1.0.0/tests/test_tables.py +174 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nelson V. Nunes
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,15 @@
1
+ exclude .DS_Store .coverage coverage.xml
2
+ recursive-exclude * __pycache__ *.py[cod] .DS_Store
3
+ recursive-exclude * .ipynb_checkpoints/*
4
+ recursive-exclude * build/*
5
+ prune src/*.egg-info
6
+ prune build
7
+ prune dist
8
+ prune .pytest_cache
9
+ prune .mypy_cache
10
+ prune .ruff_cache
11
+ prune htmlcov
12
+ prune .venv
13
+ prune .conda
14
+ prune .idea
15
+ prune .vscode
@@ -0,0 +1,328 @@
1
+ Metadata-Version: 2.4
2
+ Name: pubify-pubs
3
+ Version: 1.0.0
4
+ Summary: Workspace-oriented publication workflow engine with the pubs CLI
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: matplotlib>=3.8
10
+ Requires-Dist: numpy>=1.26
11
+ Requires-Dist: pubify-mpl>=1.0.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest>=8.0; extra == "dev"
14
+ Requires-Dist: build>=1.2; extra == "dev"
15
+ Requires-Dist: mkdocs>=1.6; extra == "dev"
16
+ Requires-Dist: mkdocstrings[python]>=0.30; extra == "dev"
17
+ Requires-Dist: twine>=5.0; extra == "dev"
18
+ Dynamic: license-file
19
+
20
+ # pubify-pubs
21
+
22
+ `pubify-pubs` is a local-first publication workflow package built around `pubify-mpl`.
23
+
24
+ It is meant for host workspaces that keep publications, publication-local TeX sources, and pinned inputs under version control, while the package owns the workflow around publication discovery, figure export, LaTeX builds, and publication bootstrapping.
25
+
26
+ This package does not own your publications. A host workspace does.
27
+
28
+ See [CHANGELOG.md](https://github.com/nvnunes/pubify-pubs/blob/main/CHANGELOG.md) for release history and user-visible changes, and [CONTRIBUTING.md](https://github.com/nvnunes/pubify-pubs/blob/main/CONTRIBUTING.md) for contributor and release workflow guidance.
29
+
30
+ ## Requirements
31
+
32
+ - Python 3.10+
33
+ - `pubify-mpl`
34
+ - a working LaTeX installation for `pubs <publication-id> build`
35
+
36
+ The `build` command runs `latexmk` against the publication-local TeX tree. If you export figures that use LaTeX text rendering through `pubify-mpl`, your TeX installation also needs to be available during Python-side figure export.
37
+
38
+ ## How It Works
39
+
40
+ `pubify-pubs` treats a configured host workspace as the source of truth.
41
+
42
+ - `pubify.conf` defines where publications live and where pinned publication data is stored
43
+ - each publication lives under `papers/<publication-id>/`
44
+ - `figures.py` declares loaders, figures, stats, and tables
45
+ - generated figures are exported into `tex/autofigures/`
46
+ - generated stats are written into `tex/autostats.tex`
47
+ - generated tables are written into `tex/autotables.tex`
48
+ - LaTeX builds run against the publication-local `tex/` tree
49
+
50
+ The local publication tree is canonical.
51
+
52
+ ## Quick Start
53
+
54
+ Create a workspace rooted by `pubify.conf`:
55
+
56
+ ```yaml
57
+ publications_root: papers
58
+ data_root: output/papers
59
+ preview:
60
+ publication: preview
61
+ figure: preview
62
+ ```
63
+
64
+ Initialize a new publication:
65
+
66
+ ```bash
67
+ pubs init my-paper
68
+ ```
69
+
70
+ That creates a publication skeleton like:
71
+
72
+ ```text
73
+ papers/my-paper/
74
+ figures.py
75
+ pub.yaml
76
+ tex/
77
+ main.tex
78
+ autofigures/
79
+ build/
80
+ ```
81
+
82
+ Then iterate with:
83
+
84
+ ```bash
85
+ pubs my-paper update
86
+ pubs my-paper build
87
+ ```
88
+
89
+ ## Workspace Model
90
+
91
+ A host workspace is rooted by `pubify.conf`. The package discovers that file by walking upward from the current working directory.
92
+
93
+ `publications_root` contains publication directories. `data_root` contains pinned publication-local data, typically under:
94
+
95
+ ```text
96
+ output/papers/<publication-id>/...
97
+ ```
98
+
99
+ This separation is intentional:
100
+
101
+ - publications stay under the host workspace's configured publication root
102
+ - pinned data stays under the configured data root
103
+ - package code lives independently from both
104
+
105
+ `pubify.conf` can also configure preview backends independently for publication PDFs and exported figure PDFs:
106
+
107
+ ```yaml
108
+ preview:
109
+ publication: vscode
110
+ figure: preview
111
+ ```
112
+
113
+ Supported backend values are:
114
+
115
+ - `preview`
116
+ - opens PDFs in macOS Preview via `open -a Preview`
117
+ - `vscode`
118
+ - opens PDFs in a separate VS Code window via `code -n`
119
+
120
+ If the `preview` section is omitted, both commands default to the `preview` backend.
121
+
122
+ ## Publication Layout
123
+
124
+ A typical publication contains:
125
+
126
+ ```text
127
+ papers/<publication-id>/
128
+ figures.py
129
+ pub.yaml
130
+ tex/
131
+ main.tex
132
+ autofigures/
133
+ build/
134
+ ```
135
+
136
+ `pub.yaml` owns publication-local settings such as:
137
+
138
+ - `main_tex`
139
+ - `mirror_root`
140
+ - `external_data_roots`
141
+ - `sync_excludes`
142
+ - `pubify-mpl-template`
143
+ - `pubify-mpl-defaults`
144
+
145
+ `figures.py` is the publication entrypoint. It defines:
146
+
147
+ - loaders decorated with `@data(...)` or `@external_data(...)`
148
+ - plotters decorated with `@figure`
149
+ - stats decorated with `@stat`
150
+ - tables decorated with `@table`
151
+
152
+ ## Typical Workflow
153
+
154
+ 1. Keep publication-local TeX sources under `papers/<publication-id>/tex/`.
155
+ 2. Define loaders, figure functions, stats, and tables in `figures.py`.
156
+ 3. Run `pubs <publication-id> update` to refresh package-owned TeX support files, validate the publication definition, and regenerate figures, stats, and tables.
157
+ 4. Run `pubs <publication-id> build` to validate and compile the publication.
158
+ 5. Use `pubs <publication-id> preview` or `pubs <publication-id> figure <figure-id> preview` while iterating.
159
+
160
+ To scaffold starter entrypoints directly into `figures.py`:
161
+
162
+ - `pubs <publication-id> data add <data-id>`
163
+ - `pubs <publication-id> figure add <figure-id>`
164
+ - `pubs <publication-id> stat add <stat-id>`
165
+ - `pubs <publication-id> table add <table-id>`
166
+
167
+ ## Figures, Tables, And Loaders
168
+
169
+ Prefer `@data(...)` for pinned publication-local inputs under the configured `data_root`. Use `@external_data(...)` only for explicit external roots declared in `pub.yaml`.
170
+
171
+ Host publications import from the extracted package namespace directly:
172
+
173
+ ```python
174
+ from pubify_pubs.data import load_publication_data_npz, publication_data_path, save_publication_data_npz
175
+ from pubify_pubs import TableResult
176
+ from pubify_pubs.decorators import data, external_data, figure, stat, table
177
+ from pubify_pubs.export import FigureExport, panel
178
+ ```
179
+
180
+ `@data(...)` and `@external_data(...)` both require relative paths. They reject absolute paths and path traversal.
181
+
182
+ `@figure` marks a callable as a logical publication figure. Exported figure functions typically return `FigureExport` values built from one or more panels.
183
+
184
+ ```python
185
+ return FigureExport(fig, layout="one")
186
+ return FigureExport([fig1, fig2], layout="two")
187
+ ```
188
+
189
+ Use `panel(...)` only when one panel needs extra pubify export metadata beyond the figure or axes itself, such as `subcaption_lines` or per-panel export overrides.
190
+
191
+ When a plotting library creates text artists during figure construction, build the figure under `ctx.rc` so those artists inherit publication font defaults at creation time:
192
+
193
+ ```python
194
+ @figure
195
+ def custom_map(ctx):
196
+ with ctx.rc:
197
+ fig = build_custom_map()
198
+ return fig
199
+ ```
200
+
201
+ For figure-specific cleanup that pubify still cannot discover generically, pass `prepare_export(...)` through `FigureExport(..., kwargs={...})`.
202
+
203
+ `@table` marks a callable as a logical publication table. Table functions return `TableResult(...)`, which owns logical table data and simple rendering while LaTeX keeps ownership of headers, captions, labels, rules, and layout.
204
+
205
+ ```python
206
+ @table
207
+ def tabulate_summary(ctx):
208
+ return TableResult(
209
+ [
210
+ ["Metric", "Value"],
211
+ ["Count", 3],
212
+ ["Mean", 2.00],
213
+ ],
214
+ formats=["{}", "{:.2f}"],
215
+ )
216
+ ```
217
+
218
+ Column rendering is intentionally small:
219
+
220
+ - `formats[col]`
221
+ - `None`, `""`, or `"{}"` means `str(value)` then LaTeX-escape
222
+ - ordinary format strings like `"{:.2f}"` format then escape
223
+ - `"tex"` means the value itself is already TeX and is inserted raw
224
+ - `tex_wrappers[col]`
225
+ - wrap the formatted value into raw TeX using one `@` placeholder
226
+ - `multicolumns`
227
+ - enables compact horizontal merging without changing logical width
228
+
229
+ ## Pinned Publication Data
230
+
231
+ `pubify-pubs` includes helpers for publication-owned binary data:
232
+
233
+ - `publication_data_path(...)`
234
+ - `save_publication_data_npz(...)`
235
+ - `load_publication_data_npz(...)`
236
+
237
+ `publication_data_path(...)` resolves paths under:
238
+
239
+ ```text
240
+ <data_root>/<publication-id>/...
241
+ ```
242
+
243
+ It rejects absolute paths and `..`, and it creates parent directories automatically.
244
+
245
+ Format-specific helpers should generally come in save/load pairs when `pubify-pubs` owns the format handling.
246
+
247
+ ## Generated Figures, Stats, Tables, And TeX Assets
248
+
249
+ `tex/autofigures/` is the framework-owned generated figure directory.
250
+
251
+ - generated figures from `figures.py` are exported there
252
+ - full `figure update` treats it as an authoritative snapshot and clears stale generated files first
253
+ - targeted `figure <figure-id> update` stays incremental
254
+ - TeX should reference generated figures explicitly by path such as `autofigures/<name>.pdf`
255
+
256
+ `tex/autostats.tex` is the framework-owned generated stats file.
257
+
258
+ - `stat update` rewrites it as one authoritative snapshot
259
+ - TeX should include it explicitly, for example with `\input{autostats.tex}`
260
+ - stats return either one value or a `dict[str, object]`
261
+ - generated stat macros are named `\Stat<StatId>` and `\Stat<StatId><Key>`
262
+
263
+ `tex/autotables.tex` is the framework-owned generated tables file.
264
+
265
+ - `table update` rewrites it as one authoritative snapshot
266
+ - `table <table-id> update` still rewrites the full snapshot after computing the selected table
267
+ - TeX should include it explicitly, for example with `\input{autotables.tex}`
268
+ - single-body tables emit `\Table<Id>`
269
+ - multi-body tables emit `\Table<Id>{1}`, `\Table<Id>{2}`, ...
270
+ - `update` and `build` validate logical table width against direct manuscript uses inside supported environments such as `tabular`, `tabularx`, and `longtable`
271
+
272
+ Manual and static paper assets are ordinary publication-local TeX files. They are not part of the generated export surface and do not belong in `tex/autofigures/`.
273
+
274
+ ## CLI
275
+
276
+ The installed command is `pubs`.
277
+
278
+ Top-level commands:
279
+
280
+ - `pubs list`
281
+ - `pubs init <publication-id>`
282
+
283
+ Publication commands:
284
+
285
+ - `pubs <publication-id> shell`
286
+ - `pubs <publication-id> data [list|add <data-id>]`
287
+ - `pubs <publication-id> figure [list|add <figure-id>|update|<figure-id> update|<figure-id> preview [<subfig-idx>]|<figure-id> latex [subcaption]]`
288
+ - `pubs <publication-id> stat [list|add <stat-id>|update|<stat-id> update|<stat-id> latex]`
289
+ - `pubs <publication-id> table [list|add <table-id>|update|<table-id> update|<table-id> latex]`
290
+ - `pubs <publication-id> update`
291
+ - `pubs <publication-id> build [--clear]`
292
+ - `pubs <publication-id> preview`
293
+
294
+ Optional advanced workflows:
295
+
296
+ `update` refreshes package-owned TeX support files, validates the publication definition, and regenerates figures, stats, and tables. `build` refreshes package-owned TeX support files, validates the publication definition, and then compiles the current TeX tree; it does not regenerate figures, stats, or tables, so run `update` first when generated outputs need refreshing.
297
+
298
+ `tables` is an alias for `table` in both the CLI and the publication shell.
299
+
300
+ The `latex` commands are read-only convenience helpers. They never edit manuscript files, and they print one blank line above and below the emitted snippet to make terminal selection easier. `tex` is accepted as an alias for `latex`.
301
+
302
+ ## Development
303
+
304
+ Install the package in editable mode:
305
+
306
+ ```bash
307
+ pip install -e .
308
+ ```
309
+
310
+ Run the package tests:
311
+
312
+ ```bash
313
+ pytest
314
+ ```
315
+
316
+ Build the docs site:
317
+
318
+ ```bash
319
+ mkdocs build --strict
320
+ ```
321
+
322
+ ## Development Approach
323
+
324
+ Keep publication-specific science code in host publications, not in this package.
325
+
326
+ ## License
327
+
328
+ MIT
@@ -0,0 +1,309 @@
1
+ # pubify-pubs
2
+
3
+ `pubify-pubs` is a local-first publication workflow package built around `pubify-mpl`.
4
+
5
+ It is meant for host workspaces that keep publications, publication-local TeX sources, and pinned inputs under version control, while the package owns the workflow around publication discovery, figure export, LaTeX builds, and publication bootstrapping.
6
+
7
+ This package does not own your publications. A host workspace does.
8
+
9
+ See [CHANGELOG.md](https://github.com/nvnunes/pubify-pubs/blob/main/CHANGELOG.md) for release history and user-visible changes, and [CONTRIBUTING.md](https://github.com/nvnunes/pubify-pubs/blob/main/CONTRIBUTING.md) for contributor and release workflow guidance.
10
+
11
+ ## Requirements
12
+
13
+ - Python 3.10+
14
+ - `pubify-mpl`
15
+ - a working LaTeX installation for `pubs <publication-id> build`
16
+
17
+ The `build` command runs `latexmk` against the publication-local TeX tree. If you export figures that use LaTeX text rendering through `pubify-mpl`, your TeX installation also needs to be available during Python-side figure export.
18
+
19
+ ## How It Works
20
+
21
+ `pubify-pubs` treats a configured host workspace as the source of truth.
22
+
23
+ - `pubify.conf` defines where publications live and where pinned publication data is stored
24
+ - each publication lives under `papers/<publication-id>/`
25
+ - `figures.py` declares loaders, figures, stats, and tables
26
+ - generated figures are exported into `tex/autofigures/`
27
+ - generated stats are written into `tex/autostats.tex`
28
+ - generated tables are written into `tex/autotables.tex`
29
+ - LaTeX builds run against the publication-local `tex/` tree
30
+
31
+ The local publication tree is canonical.
32
+
33
+ ## Quick Start
34
+
35
+ Create a workspace rooted by `pubify.conf`:
36
+
37
+ ```yaml
38
+ publications_root: papers
39
+ data_root: output/papers
40
+ preview:
41
+ publication: preview
42
+ figure: preview
43
+ ```
44
+
45
+ Initialize a new publication:
46
+
47
+ ```bash
48
+ pubs init my-paper
49
+ ```
50
+
51
+ That creates a publication skeleton like:
52
+
53
+ ```text
54
+ papers/my-paper/
55
+ figures.py
56
+ pub.yaml
57
+ tex/
58
+ main.tex
59
+ autofigures/
60
+ build/
61
+ ```
62
+
63
+ Then iterate with:
64
+
65
+ ```bash
66
+ pubs my-paper update
67
+ pubs my-paper build
68
+ ```
69
+
70
+ ## Workspace Model
71
+
72
+ A host workspace is rooted by `pubify.conf`. The package discovers that file by walking upward from the current working directory.
73
+
74
+ `publications_root` contains publication directories. `data_root` contains pinned publication-local data, typically under:
75
+
76
+ ```text
77
+ output/papers/<publication-id>/...
78
+ ```
79
+
80
+ This separation is intentional:
81
+
82
+ - publications stay under the host workspace's configured publication root
83
+ - pinned data stays under the configured data root
84
+ - package code lives independently from both
85
+
86
+ `pubify.conf` can also configure preview backends independently for publication PDFs and exported figure PDFs:
87
+
88
+ ```yaml
89
+ preview:
90
+ publication: vscode
91
+ figure: preview
92
+ ```
93
+
94
+ Supported backend values are:
95
+
96
+ - `preview`
97
+ - opens PDFs in macOS Preview via `open -a Preview`
98
+ - `vscode`
99
+ - opens PDFs in a separate VS Code window via `code -n`
100
+
101
+ If the `preview` section is omitted, both commands default to the `preview` backend.
102
+
103
+ ## Publication Layout
104
+
105
+ A typical publication contains:
106
+
107
+ ```text
108
+ papers/<publication-id>/
109
+ figures.py
110
+ pub.yaml
111
+ tex/
112
+ main.tex
113
+ autofigures/
114
+ build/
115
+ ```
116
+
117
+ `pub.yaml` owns publication-local settings such as:
118
+
119
+ - `main_tex`
120
+ - `mirror_root`
121
+ - `external_data_roots`
122
+ - `sync_excludes`
123
+ - `pubify-mpl-template`
124
+ - `pubify-mpl-defaults`
125
+
126
+ `figures.py` is the publication entrypoint. It defines:
127
+
128
+ - loaders decorated with `@data(...)` or `@external_data(...)`
129
+ - plotters decorated with `@figure`
130
+ - stats decorated with `@stat`
131
+ - tables decorated with `@table`
132
+
133
+ ## Typical Workflow
134
+
135
+ 1. Keep publication-local TeX sources under `papers/<publication-id>/tex/`.
136
+ 2. Define loaders, figure functions, stats, and tables in `figures.py`.
137
+ 3. Run `pubs <publication-id> update` to refresh package-owned TeX support files, validate the publication definition, and regenerate figures, stats, and tables.
138
+ 4. Run `pubs <publication-id> build` to validate and compile the publication.
139
+ 5. Use `pubs <publication-id> preview` or `pubs <publication-id> figure <figure-id> preview` while iterating.
140
+
141
+ To scaffold starter entrypoints directly into `figures.py`:
142
+
143
+ - `pubs <publication-id> data add <data-id>`
144
+ - `pubs <publication-id> figure add <figure-id>`
145
+ - `pubs <publication-id> stat add <stat-id>`
146
+ - `pubs <publication-id> table add <table-id>`
147
+
148
+ ## Figures, Tables, And Loaders
149
+
150
+ Prefer `@data(...)` for pinned publication-local inputs under the configured `data_root`. Use `@external_data(...)` only for explicit external roots declared in `pub.yaml`.
151
+
152
+ Host publications import from the extracted package namespace directly:
153
+
154
+ ```python
155
+ from pubify_pubs.data import load_publication_data_npz, publication_data_path, save_publication_data_npz
156
+ from pubify_pubs import TableResult
157
+ from pubify_pubs.decorators import data, external_data, figure, stat, table
158
+ from pubify_pubs.export import FigureExport, panel
159
+ ```
160
+
161
+ `@data(...)` and `@external_data(...)` both require relative paths. They reject absolute paths and path traversal.
162
+
163
+ `@figure` marks a callable as a logical publication figure. Exported figure functions typically return `FigureExport` values built from one or more panels.
164
+
165
+ ```python
166
+ return FigureExport(fig, layout="one")
167
+ return FigureExport([fig1, fig2], layout="two")
168
+ ```
169
+
170
+ Use `panel(...)` only when one panel needs extra pubify export metadata beyond the figure or axes itself, such as `subcaption_lines` or per-panel export overrides.
171
+
172
+ When a plotting library creates text artists during figure construction, build the figure under `ctx.rc` so those artists inherit publication font defaults at creation time:
173
+
174
+ ```python
175
+ @figure
176
+ def custom_map(ctx):
177
+ with ctx.rc:
178
+ fig = build_custom_map()
179
+ return fig
180
+ ```
181
+
182
+ For figure-specific cleanup that pubify still cannot discover generically, pass `prepare_export(...)` through `FigureExport(..., kwargs={...})`.
183
+
184
+ `@table` marks a callable as a logical publication table. Table functions return `TableResult(...)`, which owns logical table data and simple rendering while LaTeX keeps ownership of headers, captions, labels, rules, and layout.
185
+
186
+ ```python
187
+ @table
188
+ def tabulate_summary(ctx):
189
+ return TableResult(
190
+ [
191
+ ["Metric", "Value"],
192
+ ["Count", 3],
193
+ ["Mean", 2.00],
194
+ ],
195
+ formats=["{}", "{:.2f}"],
196
+ )
197
+ ```
198
+
199
+ Column rendering is intentionally small:
200
+
201
+ - `formats[col]`
202
+ - `None`, `""`, or `"{}"` means `str(value)` then LaTeX-escape
203
+ - ordinary format strings like `"{:.2f}"` format then escape
204
+ - `"tex"` means the value itself is already TeX and is inserted raw
205
+ - `tex_wrappers[col]`
206
+ - wrap the formatted value into raw TeX using one `@` placeholder
207
+ - `multicolumns`
208
+ - enables compact horizontal merging without changing logical width
209
+
210
+ ## Pinned Publication Data
211
+
212
+ `pubify-pubs` includes helpers for publication-owned binary data:
213
+
214
+ - `publication_data_path(...)`
215
+ - `save_publication_data_npz(...)`
216
+ - `load_publication_data_npz(...)`
217
+
218
+ `publication_data_path(...)` resolves paths under:
219
+
220
+ ```text
221
+ <data_root>/<publication-id>/...
222
+ ```
223
+
224
+ It rejects absolute paths and `..`, and it creates parent directories automatically.
225
+
226
+ Format-specific helpers should generally come in save/load pairs when `pubify-pubs` owns the format handling.
227
+
228
+ ## Generated Figures, Stats, Tables, And TeX Assets
229
+
230
+ `tex/autofigures/` is the framework-owned generated figure directory.
231
+
232
+ - generated figures from `figures.py` are exported there
233
+ - full `figure update` treats it as an authoritative snapshot and clears stale generated files first
234
+ - targeted `figure <figure-id> update` stays incremental
235
+ - TeX should reference generated figures explicitly by path such as `autofigures/<name>.pdf`
236
+
237
+ `tex/autostats.tex` is the framework-owned generated stats file.
238
+
239
+ - `stat update` rewrites it as one authoritative snapshot
240
+ - TeX should include it explicitly, for example with `\input{autostats.tex}`
241
+ - stats return either one value or a `dict[str, object]`
242
+ - generated stat macros are named `\Stat<StatId>` and `\Stat<StatId><Key>`
243
+
244
+ `tex/autotables.tex` is the framework-owned generated tables file.
245
+
246
+ - `table update` rewrites it as one authoritative snapshot
247
+ - `table <table-id> update` still rewrites the full snapshot after computing the selected table
248
+ - TeX should include it explicitly, for example with `\input{autotables.tex}`
249
+ - single-body tables emit `\Table<Id>`
250
+ - multi-body tables emit `\Table<Id>{1}`, `\Table<Id>{2}`, ...
251
+ - `update` and `build` validate logical table width against direct manuscript uses inside supported environments such as `tabular`, `tabularx`, and `longtable`
252
+
253
+ Manual and static paper assets are ordinary publication-local TeX files. They are not part of the generated export surface and do not belong in `tex/autofigures/`.
254
+
255
+ ## CLI
256
+
257
+ The installed command is `pubs`.
258
+
259
+ Top-level commands:
260
+
261
+ - `pubs list`
262
+ - `pubs init <publication-id>`
263
+
264
+ Publication commands:
265
+
266
+ - `pubs <publication-id> shell`
267
+ - `pubs <publication-id> data [list|add <data-id>]`
268
+ - `pubs <publication-id> figure [list|add <figure-id>|update|<figure-id> update|<figure-id> preview [<subfig-idx>]|<figure-id> latex [subcaption]]`
269
+ - `pubs <publication-id> stat [list|add <stat-id>|update|<stat-id> update|<stat-id> latex]`
270
+ - `pubs <publication-id> table [list|add <table-id>|update|<table-id> update|<table-id> latex]`
271
+ - `pubs <publication-id> update`
272
+ - `pubs <publication-id> build [--clear]`
273
+ - `pubs <publication-id> preview`
274
+
275
+ Optional advanced workflows:
276
+
277
+ `update` refreshes package-owned TeX support files, validates the publication definition, and regenerates figures, stats, and tables. `build` refreshes package-owned TeX support files, validates the publication definition, and then compiles the current TeX tree; it does not regenerate figures, stats, or tables, so run `update` first when generated outputs need refreshing.
278
+
279
+ `tables` is an alias for `table` in both the CLI and the publication shell.
280
+
281
+ The `latex` commands are read-only convenience helpers. They never edit manuscript files, and they print one blank line above and below the emitted snippet to make terminal selection easier. `tex` is accepted as an alias for `latex`.
282
+
283
+ ## Development
284
+
285
+ Install the package in editable mode:
286
+
287
+ ```bash
288
+ pip install -e .
289
+ ```
290
+
291
+ Run the package tests:
292
+
293
+ ```bash
294
+ pytest
295
+ ```
296
+
297
+ Build the docs site:
298
+
299
+ ```bash
300
+ mkdocs build --strict
301
+ ```
302
+
303
+ ## Development Approach
304
+
305
+ Keep publication-specific science code in host publications, not in this package.
306
+
307
+ ## License
308
+
309
+ MIT