Metadata-Version: 2.4
Name: pubify-pubs
Version: 1.0.3
Summary: Workspace-oriented publication workflow engine with the pubs CLI
License-Expression: MIT
Project-URL: Documentation, https://nvnunes.github.io/pubify-pubs/
Project-URL: Repository, https://github.com/nvnunes/pubify-pubs
Project-URL: Changelog, https://github.com/nvnunes/pubify-pubs/blob/main/CHANGELOG.md
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: matplotlib>=3.8
Requires-Dist: numpy>=1.26
Requires-Dist: pubify-mpl>=1.0.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: mkdocs>=1.6; extra == "dev"
Requires-Dist: mkdocstrings[python]>=0.30; extra == "dev"
Requires-Dist: twine>=5.0; extra == "dev"
Dynamic: license-file

# pubify-pubs

`pubify-pubs` is a local-first publication workflow package built around `pubify-mpl`.

It is meant for host workspaces that keep publications, publication-local TeX sources, and pinned inputs under version control, while the package owns the generic workflow around publication discovery, figure export, LaTeX builds, and publication bootstrapping.

This package does not own your publications. A host workspace does.

## Project Docs

- [Documentation home](https://nvnunes.github.io/pubify-pubs/)
- [Architecture](https://nvnunes.github.io/pubify-pubs/architecture/)
- [Development setup](https://nvnunes.github.io/pubify-pubs/development/)
- [Testing and validation](https://nvnunes.github.io/pubify-pubs/testing/)
- [API reference](https://nvnunes.github.io/pubify-pubs/api/)
- [Contributing](https://github.com/nvnunes/pubify-pubs/blob/main/CONTRIBUTING.md)
- [Changelog](https://github.com/nvnunes/pubify-pubs/blob/main/CHANGELOG.md)

## Requirements

- Python 3.10+
- `pubify-mpl`
- a working LaTeX installation for `pubs <publication-id> build`

The build command runs `latexmk` against the publication-local TeX tree. If exported figures use LaTeX text rendering through `pubify-mpl`, LaTeX must also be available during Python-side figure export.

## How It Works

`pubify-pubs` treats a configured host workspace as the source of truth.

- `pubify.yaml` defines where publications live and where pinned publication data is stored
- each publication lives under `papers/<publication-id>/`
- `figures.py` declares loaders, figures, stats, and tables
- generated figures are exported into `tex/autofigures/`
- generated stats are written into `tex/autostats.tex`
- generated tables are written into `tex/autotables.tex`
- LaTeX builds run against the publication-local `tex/` tree

The local publication tree is canonical.

## Quick Start

Initialize a workspace:

```bash
pubs init
```

That writes `pubify.yaml` like:

```yaml
publications_root: papers
data_root: ""
preview:
  publication: preview
  figure: preview
```

Then initialize a new publication:

```bash
pubs init my-paper
```

That creates a publication skeleton like:

```text
papers/my-paper/
  figures.py
  pub.yaml
  tex/
    main.tex
    autofigures/
    build/
```

Then iterate with:

```bash
pubs my-paper update
pubs my-paper build
```

`pubs init` also creates a minimal shared `AGENTS.md` under the configured
`publications_root`, which is `papers/AGENTS.md` by default.

## Workspace Model

A host workspace is rooted by `pubify.yaml`. The package discovers that file by walking upward from the current working directory.

`publications_root` contains publication directories. When `data_root` is blank, pinned publication-local data defaults to:

```text
papers/<publication-id>/data/...
```

If you want a shared workspace-level data area instead, set `data_root` explicitly, for example:

```yaml
data_root: output/papers
```

Then pinned publication-local data resolves under:

```text
output/papers/<publication-id>/...
```

This flexibility is intentional:

- publications stay under the host workspace's configured publication root
- pinned data can stay publication-local by default or under a configured shared data root
- package code lives independently from both

`pubify.yaml` can also configure preview backends independently for publication PDFs and exported figure PDFs:

```yaml
preview:
  publication: vscode
  figure: preview
```

Supported backend values are:

- `preview`
  - opens PDFs in macOS Preview via `open -a Preview`
- `vscode`
  - opens PDFs in a separate VS Code window via `code -n`

If the `preview` section is omitted, both commands default to the `preview` backend.

## Publication Layout

A typical publication contains:

```text
papers/<publication-id>/
  figures.py
  pub.yaml
  tex/
    main.tex
    autofigures/
    build/
```

`pub.yaml` owns publication-local settings such as:

- `main_tex`
- `mirror_root`
- `external_data_roots`
- `sync_excludes`
- `pubify-mpl-template`
- `pubify-mpl-defaults`

`figures.py` is the publication entrypoint. It defines:

- loaders decorated with `@data(...)` or `@external_data(...)`
- plotters decorated with `@figure`
- stats decorated with `@stat`
- tables decorated with `@table`

The publication-local `tex/` tree is canonical. `tex/autofigures/` is the framework-owned generated figure directory, and `tex/build/` is the local build output directory.

## Typical Workflow

1. Keep publication-local TeX sources under `papers/<publication-id>/tex/`.
2. Define loaders, figure functions, stats, and tables in `figures.py`.
3. Run `pubs <publication-id> update` to refresh package-owned TeX support files, validate the publication definition, and regenerate figures, stats, and tables.
4. Run `pubs <publication-id> build` to validate and compile the publication.
5. Use `pubs <publication-id> preview` or `pubs <publication-id> figure <figure-id> preview` while iterating.

To scaffold starter entrypoints directly into `figures.py`:

- `pubs <publication-id> data add <data-id>`
- `pubs <publication-id> figure add <figure-id>`
- `pubs <publication-id> stat add <stat-id>`
- `pubs <publication-id> table add <table-id>`

## Figures, Tables, And Loaders

Prefer `@data(...)` for pinned publication-local inputs under the configured `data_root`. Use `@external_data(...)` only for explicit external roots declared in `pub.yaml`.

Both data decorators require relative paths. They reject absolute paths and path traversal.

Host publications import from the extracted package namespace directly:

```python
from pubify_pubs import TableResult
from pubify_pubs.data import (
    load_publication_data_npz,
    publication_data_path,
    save_publication_data_npz,
)
from pubify_pubs.decorators import data, external_data, figure, stat, table
from pubify_pubs.export import FigureExport, panel
```

`@figure` marks a callable as a logical publication figure. Exported figure functions may return:

- a Matplotlib `Figure`
- a Matplotlib `Axes`
- a sequence of figures or axes
- a `FigureExport` value for explicit multi-panel control

`FigureExport` accepts a single Matplotlib `Figure` or `Axes`, a list or tuple of them, one `panel(...)`, or a list or tuple of `panel(...)` values.

Exported figure functions commonly return `FigureExport` values built from one or more panels:

```python
return FigureExport(fig, layout="onewide")
return FigureExport([fig1, fig2], layout="twowide")
```

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.

When a plotting library creates text artists during figure construction, use `ctx.rc` so those artists are born under the publication construction-time font defaults:

```python
@figure
def custom_map(ctx):
    with ctx.rc:
        fig = build_custom_map()
    return fig
```

For figure-specific cleanup that pubify still cannot discover generically, pass `prepare_export(...)` through `FigureExport(..., kwargs={...})`.

`@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.

```python
@table
def tabulate_summary(ctx):
    return TableResult(
        [
            ["Metric", "Value"],
            ["Count", 3],
            ["Mean", 2.00],
        ],
        formats=["{}", "{:.2f}"],
    )
```

Column rendering is intentionally small:

- `formats[col]`
  - `None`, `""`, or `"{}"` means `str(value)` then LaTeX-escape
  - ordinary format strings like `"{:.2f}"` format then escape
  - `"tex"` means the value itself is already TeX and is inserted raw
- `tex_wrappers[col]`
  - wrap the formatted value into raw TeX using one `@` placeholder
- `multicolumns`
  - enables compact horizontal merging without changing logical width

## Pinned Publication Data

`pubify-pubs` includes helpers for publication-owned binary data:

- `publication_data_path(...)`
- `save_publication_data_npz(...)`
- `load_publication_data_npz(...)`

These helpers resolve data under:

```text
<data_root>/<publication-id>/...
```

`publication_data_path(...)` resolves paths under that root. It rejects absolute paths and `..`, and it creates parent directories automatically.

Format-specific helpers should generally come in save/load pairs when `pubify-pubs` owns the format handling.

## Generated Figures, Stats, Tables, And TeX Assets

`tex/autofigures/` is the framework-owned generated figure directory.

- generated figures from `figures.py` are exported there
- full `figure update` treats it as an authoritative snapshot and clears stale generated files first
- targeted `figure <figure-id> update` stays incremental
- TeX should reference generated figures explicitly by path such as `autofigures/<name>.pdf`

`tex/autostats.tex` is the framework-owned generated stats file.

- `stat update` rewrites it as one authoritative snapshot
- TeX should include it explicitly, for example with `\input{autostats.tex}`
- stats return either one value or a `dict[str, object]`
- generated stat macros are named `\Stat<StatId>` and `\Stat<StatId><Key>`

`tex/autotables.tex` is the framework-owned generated tables file.

- `table update` rewrites it as one authoritative snapshot
- `table <table-id> update` still rewrites the full snapshot after computing the selected table
- TeX should include it explicitly, for example with `\input{autotables.tex}`
- single-body tables emit `\Table<Id>`
- multi-body tables emit `\Table<Id>{1}`, `\Table<Id>{2}`, ...
- `update` and `build` validate logical table width against direct manuscript uses inside supported environments such as `tabular`, `tabularx`, and `longtable`
- `build` validates and compiles the current TeX tree, but does not regenerate figures, stats, or tables

Manual and static publication assets remain ordinary publication-local TeX files. They do not belong in `tex/autofigures/`.

## CLI Overview

The installed command is `pubs`.

Top-level commands:

- `pubs list`
- `pubs init`
- `pubs init <publication-id>`

Publication commands:

- `pubs <publication-id> shell`
- `pubs <publication-id> data [list|add <data-id>]`
- `pubs <publication-id> figure [list|add <figure-id>|update|<figure-id> update|<figure-id> preview [<subfig-idx>]|<figure-id> latex [subcaption]]`
- `pubs <publication-id> stat [list|add <stat-id>|update|<stat-id> update|<stat-id> latex]`
- `pubs <publication-id> table [list|add <table-id>|update|<table-id> update|<table-id> latex]`
- `pubs <publication-id> update`
- `pubs <publication-id> build [--clear]`
- `pubs <publication-id> preview`

Optional advanced workflows:

`update` refreshes package-owned TeX support files, validates the publication definition, and regenerates figures, stats, and tables. `build` validates and compiles the current publication-local TeX tree; it does not regenerate figures, stats, or tables, so run `update` first when generated outputs need refreshing.

`tables` is an alias for `table` in both the CLI and the publication shell.

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`.

## Python API Overview

The public Python API is intentionally small. Host publications import from the `pubify_pubs.*` namespace directly:

```python
from pubify_pubs import TableResult
from pubify_pubs.data import (
    load_publication_data_npz,
    publication_data_path,
    save_publication_data_npz,
)
from pubify_pubs.decorators import data, external_data, figure, stat, table
from pubify_pubs.export import FigureExport, panel
from pubify_pubs.discovery import find_workspace_root
```

Use the [API reference](https://nvnunes.github.io/pubify-pubs/api/) for the docstring-driven reference pages.

## Development

Use the repo-local `./.conda` environment by default for Python commands, test runs, and docs builds. The durable contributor workflow lives in the [development setup](https://nvnunes.github.io/pubify-pubs/development/) and [testing](https://nvnunes.github.io/pubify-pubs/testing/) docs.

## License

MIT
