Metadata-Version: 2.4
Name: rekisteri
Version: 2026.9.27
Summary: Manage catalogs of documents organized per publication identifier stem, issue, and optionally revision.
Author-email: Stefan Hagen <stefan@hagen.link>
Maintainer-email: Stefan Hagen <stefan@hagen.link>
License-Expression: MIT
Project-URL: Documentation, https://codes.dilettant.life/docs/rekisteri
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: kaava>=2026.9.27
Requires-Dist: ruamel.yaml>=0.18.6
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Provides-Extra: pyyaml
Requires-Dist: PyYAML>=6.0.3; extra == "pyyaml"

# rekisteri

Register / registry (Finnish: rekisteri) manages catalogs of documents organised per publication identifier stem, issue, and optionally revision.

Requires Python 3.11 or later.

## Install

```
pip install rekisteri
```

## Manual

The [man page](https://codes.dilettant.life/docs/rekisteri/man/rekisteri.1) provides the CLI reference (`man rekisteri` after placing the file on your `MANPATH`)

To use `man rekisteri`, copy `docs/man/rekisteri.1` to a directory on your `MANPATH`, for example:

```
mkdir -p ~/.local/share/man/man1
cp docs/man/rekisteri.1 ~/.local/share/man/man1/
```

## Overview

`rekisteri` is for operators of document publication registries -- typically teams that publish structured document sets
(specifications, guides, manuals) and need a stable, browsable web tree alongside a machine-readable catalog.

Given a `rekisteri.yaml` configuration it:

1. **Scans** a `source/ref/` tree for documents organised by stem, issue, and
   (optionally) revision.
2. **Validates** the tree: checks metadata completeness, issue numbering consistency,
   slug uniqueness, and catalog cross-references.
3. **Publishes** a `web-parent/` tree:
   - `ref/` -- anchor copies of all documents with `index` navigation symlinks at
     issue and stem level.
   - `pub/` -- per-slug cross-reference symlinks into `ref/`, grouped by issue.
   - `reference -> ref` and `publication -> pub` alias symlinks at the root.
4. **Generates** catalog JSON files (`documents.json`, `groups.json`) at a separate
   `catalog-out` path for consumption by a side-loading web application.

## Source tree structure

### Two-level (default-depth: 2, default)

```
source/ref/
+-- <STEM>/
    +-- publication.yaml     <- stem-level metadata (title, status, ...)
    +-- <ISSUE>/
    |   +-- version.yaml     <- optional issue-level metadata overrides
    |   +-- <stem>-<some_id>-<issue>.<ext>
    +-- <ISSUE>/
        +-- ...
```

### Three-level (default-depth: 3)

```
source/ref/
+-- <STEM>/
    +-- publication.yaml
    +-- <ISSUE>/
        +-- issue.yaml
        +-- <REVISION>/
            +-- revision.yaml    <- optional revision-level metadata overrides
            +-- <stem>-<some_id>-<issue>-<revision>.<ext>
```

Issue directories contain only digits (`01`, `2`, ...).
Revision directories are any non-numeric non-hidden name (`A`, `B`, `draft`, ...).

## Quickstart

```
# Write rekisteri.yaml and publication.yaml templates into the current directory
rekisteri eject config .

# Check the tree is well-formed
rekisteri validate --config rekisteri.yaml

# Show the discovered publication tree
rekisteri config explain

# Publish the web-parent tree
rekisteri publish
```

For a dedicated feature walkthrough you can follow in minutes visit [quickstart](https://codes.dilettant.life/docs/rekisteri/quickstart/README.md).
A step-by-step guided build of a running registry is provided in the [tutorial](https://codes.dilettant.life/docs/rekisteri/tutorial/README.md).

## Configuration

`rekisteri.yaml` controls all paths and behaviour.
Run `rekisteri eject config` to write a commented template.

```yaml
catalog:
  documents-file: documents.json
  groups:
    - title: Guides          # display name for this group
      identifiers: [INTRO]   # slugs of publications belonging to this group
      groups: []             # optional nested sub-groups (unlimited depth)
  groups-file: groups.json
catalog-out: ./catalog       # path where catalog JSON files are written
default-depth: 2             # 2 (stem/issue) or 3 (stem/issue/revision)
# hash-algorithm: sha256     # sha256 sha384 sha512 blake2b blake2s blake3
# slug-pattern: null         # strip from some_id before slugifying; e.g. 'feature-' -> 'GUIDE'
# stem-pattern: null         # only stems matching this regex are scanned; e.g. '^Y-'
# strict-groups: false       # promote PUBLICATION_UNGROUPED from warning to error
supported-depths: [2]        # depths allowed in this registry; use [2, 3] for mixed
# text-extensions: []        # CRLF->LF normalised before hashing; e.g. [.txt, .json]
source-root: ./source/ref    # path to the source document tree
web-root: ./web-parent       # path where the published tree is written
```

All multi-word keys are kebab-case.
`--config` / `-c` accepts either a file path or a directory (looks for
`rekisteri.yaml` inside it).

## Metadata files

Each stem, issue, and revision may carry a YAML metadata file.
Inner files override outer ones via `dict.update()`; keys the inner file does not mention are inherited unchanged.

```yaml
abstract: ""       # short abstract
mapping: []        # list of mapping slugs from mapping.yaml (used by rekisteri map)
owner: ""          # team or person owner
status: active     # active | superseded | archived
supersedes: []     # list of STEM/ISSUE entries this publication supersedes
tags: []           # free-form tags
title: ""          # human-readable publication title (required; must not be empty)
views: []          # list of view slugs from catalog.views (used by rekisteri map/publish)
```

## Filename conventions

Documents are named `<stem>-<some_id>-<issue>.<ext>` (two-level) or
`<stem>-<some_id>-<issue>-<revision>.<ext>` (three-level), all lowercase.
The `some_id` part is extracted by stripping the stem prefix and the issue (and
revision) suffix from the filename stem.  It is then uppercased (after optional
`slug-pattern` filtering) to form the **slug** used in `pub/` and catalog.

Example: `x-guide-intro-01.pdf` in stem `X-GUIDE`, issue `01`
-> `some_id = intro` -> `slug = INTRO`.

The slug is stable as long as `some_id` is preserved across stem renames and
issue increments.

## Web-parent tree topology

After `rekisteri publish`:

```
web-parent/
+-- ref/
|   +-- <STEM>/
|       +-- index.<ext>              -> latest issue's document
|       +-- <ISSUE>/
|           +-- <doc-name>.<ext>     <- anchor copy
|           +-- index.<ext>          -> <doc-name>
+-- pub/
|   +-- <SLUG>/
|       +-- index.<ext>              -> latest issue's document
|       +-- <ISSUE>/
|           +-- <doc-name>.<ext>     -> ../../../ref/STEM/ISSUE/doc
|           +-- index.<ext>          -> <doc-name>
+-- reference                        -> ref
+-- publication                      -> pub
```

With `--copy`, `pub/` entries are real file copies instead of symlinks
(useful for Windows or archival scenarios).

## Commands

### `rekisteri config`

Inspect and check rekisteri configuration; a two-subcommand umbrella.

```
rekisteri config doctor [-c PATH]
rekisteri config explain [-c PATH]
```

`config doctor` checks the environment and configuration: Python version, config file presence, source-root existence, and web-root parent.

`config explain` prints the discovered publication tree: stems, issues (and revisions for three-level trees), documents, and slugs.

Bare `rekisteri config` (no subcommand) prints this help and exits 0.

### `rekisteri eject`

Write config templates, or install the bundled man pages; a two-subcommand umbrella.

```
rekisteri eject config [TARGET] [--overwrite]
rekisteri eject man [--man-path TREE_ROOT]
```

`eject config` writes `rekisteri.yaml`, `publication.yaml`, and `mapping.yaml` templates to a target directory. Skips existing files unless
`--overwrite` is given.

`eject man` installs every bundled man page into a man-page tree (default: `~/.local/share/man`),
distributed into `man1/`/`man3/`/`man5/`/`man7/` by section.
Unlike `eject config`, it always overwrites, to support idempotent reinstall after a package upgrade.

Bare `rekisteri eject` (no subcommand) prints this help and exits 0.

### `rekisteri publish`

Build the `web-parent/` tree from the source tree.
Creates anchor copies in `ref/`, cross-reference symlinks in `pub/`, and the `reference` / `publication` alias symlinks.

```
rekisteri publish [-c PATH] [--copy]
```

`--copy` replaces `pub/` cross-reference symlinks with real file copies.

### `rekisteri validate`

Validate the source tree and metadata.
Prints findings tagged `[error]` or `[warning]`.
Exits 1 on any error; with `--strict`, also exits 1 on warnings.

```
rekisteri validate [-c PATH] [--strict]
```

Optional: set `magic-bytes.verify: true` in `rekisteri.yaml` to also verify that each document file's leading bytes match the
expected signature for its extension.
Built-in rules cover PDF, ZIP-based office formats, PNG, JPEG, GIF, and MP4;
run `rekisteri eject config` to see the full commented template with all rules.

`HASH_MISMATCH` is also checked when annotation files contain a `hashes:` block (written by `rekisteri hash`).
Text files listed in `text-extensions:` are hashed after CRLF->LF normalisation and stored with the `algorithm+lf:hexdigest` tag.

### `rekisteri check`

Show the hash status of a single document file (read-only).
Reports file path, size in bytes, text flag, computed hash, stored hash, and status.

```
rekisteri check FILE [-c PATH]
```

Status values: `ok`, `mismatch`, `migration-pending` (hash algorithm changed),
`unregistered` (no stored hash yet).
Exits 0 on `ok`, 1 otherwise.

### `rekisteri map`

Harvest the latest active issue per stem, group documents by mapping slug, and write
`mapping.json` to `catalog-out`.
Requires `mapping-source` to be set in `rekisteri.yaml` pointing to a `mapping.yaml` file.
`publish` runs this automatically when `mapping-source` is configured.

```
rekisteri map [-c PATH]
```

## Design and requirements

Software Requirements Specification
:   REK-SRS-001 -- [requirements/](https://codes.dilettant.life/docs/rekisteri/requirements/README.md)

Software Design Description
:   REK-SDD-001 -- [design/](https://codes.dilettant.life/docs/rekisteri/design/README.md)

Both documents follow the MIL-STD-498 DID structure and are rendered into the documentation site alongside the tutorial and quickstart.
Each command also has its own component design and requirements page, e.g.
[design/eject/](https://codes.dilettant.life/docs/rekisteri/design/eject/README.md) and [requirements/eject/](https://codes.dilettant.life/docs/rekisteri/requirements/eject/README.md), linked from the umbrella documents above.

## Man pages

Unix man pages are provided for every command (section 1), the library API (section 3), the file formats (section 5), and the
concepts overview (section 7); see [man/](https://codes.dilettant.life/docs/rekisteri/man/index.html) on the documentation site or `man rekisteri` / `man rekisteri-eject` etc. once installed.

## Changes

See [releases/](https://codes.dilettant.life/docs/rekisteri/releases/README.md) for the release history, or [releases/changes/](https://codes.dilettant.life/docs/rekisteri/releases/changes/README.md) for the full detail
behind each summary.

## Complexity

The code base complexity is documented at [complexity/](https://codes.dilettant.life/docs/rekisteri/complexity/).

## Coverage

The test suite maintains 99% branch coverage.
The HTML report (if generated) is in `site/coverage/`.

## SBOM

Runtime dependency information is published in `docs/sbom/` in SPDX 3.0 (JSON-LD) and CycloneDX 1.6 (JSON) formats.
See `docs/sbom/README.md` for the component inventory and validation guide.
