Metadata-Version: 2.4
Name: nide
Version: 2026.7.14
Summary: A bound volume (Finnish: nide) - assemble markdown text files into gfm-plus, html, and pdf channel outputs.
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/nide
Keywords: developer-tools,documentation,markdown
Classifier: Development Status :: 3 - Alpha
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
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: jsonschema>=4.26.0
Requires-Dist: kaava>=2026.6.20
Requires-Dist: python-jsonpath>=2.0.2
Requires-Dist: PyYAML>=6.0.3
Requires-Dist: vyyhti>=2026.6.21
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Dynamic: license-file

# nide

A bound volume (Finnish: nide) — assemble Markdown source files into gfm-plus, html, pdf, and typst channel outputs.

Requires Python 3.11 or later.

## Install

```
pip install nide
```

## Manual

The [man page](docs/nide.1) provides the CLI reference (`man nide` after placing the file on your `MANPATH`):

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

## Quickstart

Run `nide` with no arguments for a usage summary, or `nide --version` to check the installed version.
For a dedicated feature walkthrough you can follow in minutes visit [quickstart](quickstart/README.md).
A step-by-step guided build of a document pipeline is provided in the [tutorial](tutorial/README.md).
Some commented real-world configurations are provided in the [example](example/README.md) documentation.

## Pipeline

Two-pass workflow:

```
nide harvest              →  build/section-*.json, build/example-*.json

nide assemble             →  build/ir.json          (Pass 1: channel-neutral IR)
nide render -t html       →  build/html.md, build/toc-mint.json
pandoc                    →  build/tmp.html
nide inject               →  build/injected.html
tidy                      →  delivery HTML

nide render -t gfm-plus   →  build/gfm-plus.md

nide render -t typst      →  build/typst.md, build/nide-columns.lua
pandoc -t typst           →  build/typst.typ
typst compile             →  delivery PDF           (recommended PDF path)

nide render -t pdf        →  build/pdf.md           (legacy: liitos/LaTeX path)
liitos                    →  delivery PDF
```

The compat one-pass form combines Pass 1 + Pass 2 into a single command
(`nide assemble -t html|gfm-plus|pdf`) for existing workflows.

## Commands

`nide` has eight subcommands.  Every name accepts any unambiguous prefix —
`nide a` = assemble, `nide r` = render, `nide ej` = eject, `nide ex` = explain, etc.

**`nide assemble`** (Pass 1) splices a list of Markdown source files into a single
channel-neutral intermediate representation (IR) and writes it to `build/ir.json`:

- Citation definition files are expanded into formatted inline citation entries.
- Glossary content is captured per source file for channel-specific rendering.
- Section headings are numbered and stored with all metadata needed by any channel.
- Cross-references (`[sec](#label)`, `[cite](#label)`, `[tab](#label)`,
  `[fig](#label)`, `[eg](#label)`) are resolved from JSON look-up tables produced
  by `nide harvest`.

When called with `-t CHANNEL`, `nide assemble` also runs Pass 2 immediately
(compat shortcut).

**`nide render`** (Pass 2) reads `build/ir.json` and renders one or more channels.
Accepts a comma-separated list: `nide render -t gfm-plus,html,typst,pdf`.

- `html`: numbers headings with inline anchors; expands glossary into `<dl>` elements;
  writes `build/html.md` and `build/toc-mint.json`.
- `gfm-plus`: numbers headings with inline anchors; passes glossary through verbatim;
  writes `build/gfm-plus.md`.
- `typst`: converts headings, code blocks, anchors, and cross-references to raw typst
  blocks; writes `build/typst.md` and `build/nide-columns.lua`.
  Recommended path for PDF output: `pandoc -t typst … | typst compile`.
- `pdf` *(deprecated)*: attaches the nide-generated pandoc heading attribute `{#label}` to every
  heading so hyperref destination names match the cross-reference LUT; prepends
  `\phantomsection` before unnumbered (annex/appendix) section headings; replaces
  the remote logo URL with the local path; writes `build/pdf.md`.
  Use the typst channel instead for new workflows.

**`nide inject`** post-processes the HTML file that pandoc has produced from
nide's `build/html.md`, applying:

- Custom HTML table of contents (built from `build/toc-mint.json`).
- `<title>` element replaced with the value of `html-title` from config.
- Base and skin CSS inlined; `<link>` tags removed.
- Remote logo URL replaced with a local data URL.
- Image `src` attributes optionally replaced with base64 data URLs.

The result is written to `build/injected.html`.

**`nide harvest`** scans the Markdown sources and writes JSON look-up table (LUT)
files for section and example cross-references.  Run it before `nide assemble`
whenever source files change.

**`nide explain`** performs a dry-run walkthrough without writing any output files.
It prints config values, binder contents with exist/missing tags, channel exclusions,
auxiliary source file status, and LUT file presence.  Useful for diagnosing why an
assembly produces unexpected output.

**`nide doctor [--fix]`** scans the project layout and reports findings at ERROR or
WARN level: missing directories, binder file, source files, first-authored-section
heading, auxiliary sources, and LUT files.  With `--fix`, it creates missing
directories and runs `nide harvest` to generate absent LUT files.

**`nide eject`** emits a fully-commented `nide.yaml` template to stdout.
Use `-o PATH` to write it to a file; add `-f` to overwrite an existing file.

**`nide version`** (also `-V` / `--version`) prints the installed version and exits.

Four output channels are supported:

| Channel    | Output (`nide render`)                                    | Intended consumer           |
|:-----------|:----------------------------------------------------------|:----------------------------|
| `html`     | `build/html.md` + `build/toc-mint.json`                   | pandoc → `nide inject`      |
| `gfm-plus` | `build/gfm-plus.md`                                       | direct delivery             |
| `typst`    | `build/typst.md` + `build/nide-columns.lua`               | pandoc → typst compile      |
| `pdf`      | `build/pdf.md` *(deprecated — use `typst` instead)*       | `liitos`                    |

IR output (`nide assemble`): `build/ir.json`.

## Configuration

All settings are read from `etc/nide.yaml` (override with `-c`).
Every key is optional; the built-in defaults are listed below.
Run `nide eject` to emit a fully-commented template with every key.

Settings are grouped under five top-level YAML keys.

### `assembly:` — source file handling

```yaml
assembly:
  binder: bind.txt                      # binder filename (in etc-path)
  source-path: src                      # directory containing source .md files
  build-path: build                     # output directory
  etc-path: etc                         # directory holding this config and LUT files
  first-authored-section: '# Scope'    # heading where body numbering starts
  binder-ignores:
    gfm-plus: []                        # filenames to skip for GFM-plus channel
    html: []                            # filenames to skip for HTML channel
    typst: []                           # filenames to skip for typst channel
    pdf: []                             # filenames to skip for PDF channel (deprecated)
  delete-when:
    - contains: '<mark title="Ephemeral region marking">'
      delete:
        - '<mark title="Ephemeral region marking">'
        - '</mark>'
  output-paths:
    ir: build/ir.json
    gfm-plus: build/gfm-plus.md
    html: build/html.md
    typst: build/typst.md
    pdf: build/pdf.md
```

### `structure:` — document anatomy

```yaml
structure:
  section-type-prefixes: [Annex, Appendix]   # prefixes that suppress auto-numbering
```

Headings whose text starts with one of these prefixes (plus a space) are treated
as special sections.  The letter designator is spelled out in the source file.

### `enumeration:` — counters and look-up tables

```yaml
enumeration:
  sections:
    l1-dot: true               # trailing dot on level-1 labels (1., 2., …)
    sub-dot: false             # trailing dot on level 2–6 labels
    reference-style: number    # [sec] expansion: number | number-title | section-sign-number
    toc-template:              # 5 entries, one per heading level
      - '$sec_cnt_disp$ [$text$](#$label$)  '
      - "\t$sec_cnt_disp$ [$text$](#$label$)  "
      - "\t\t$sec_cnt_disp$ [$text$](#$label$)  "
      - "\t\t\t$sec_cnt_disp$ [$text$](#$label$)  "
      - "\t\t\t\t$sec_cnt_disp$ [$text$](#$label$)  "
    lut:
      label-to-display: section-label-to-display.json
      display-to-label: section-display-to-label.json
      display-to-text:  section-display-to-text.json
  examples:
    track: false               # enable *Example N: scanning and look-up tables
    global-start: 4321         # global counter offset (must exceed local count)
    lut:
      local-to-global: example-local-to-global.json
      global-to-local: example-global-to-local.json
```

When `l1-dot: true` (default), nide injects `\renewcommand{\thesection}` into
the PDF channel output so LaTeX section numbers match nide's numbering.

### `references:` — cross-reference configuration

```yaml
references:
  citations:
    sources: []                                           # citation source filenames
    skip-prefixes: ['#']                                  # lines bypassing the state machine
    keyword: cite                                         # inline trigger: [cite](#label)
    cite-template: "\\[[$code$](#$label$)\\]"            # inline ref expansion template
    entry-template: '**\[**<span id="$label$" class="anchor"></span>**$code$\]** $text$'
  sections:
    keyword: sec                                          # inline trigger: [sec](#label)
  glossary:
    sources: []                                           # glossary source filenames
  tables:
    keyword: tab                                          # inline trigger: [tab](#label)
    display-prefix: Table                                 # expands to 'Table N'
  figures:
    keyword: fig                                          # inline trigger: [fig](#label)
    display-prefix: Figure                                # expands to 'Figure N'
```

Table anchors use pandoc table-caption syntax: `Table: Caption{#tab:label}`.
Figure anchors use pandoc image attributes: `![alt](path){#fig:label}`.
Both `cite-template` and `entry-template` support `$code$`, `$label$`, and (for
`entry-template`) `$text$` placeholders.

### `channels:` — channel-specific settings

```yaml
channels:
  html:
    title: 'No Title Given'    # <title> element written by nide inject
    inject:
      css-base: ''             # path to base CSS file (empty = skip)
      css-skin: ''             # path to skin CSS file
      images: false            # inline local images as base64 data URLs
      images-path: build/data-url
      intro-trigger: ''        # HTML prefix marking the first authored heading
      logo-data-url: ''        # path to logo data URL .txt file (empty = skip)
      logo-remote-url: ''      # remote logo URL to replace
      toc-trigger: '<h1 id="table-of-contents'
  typst:
    toc-depth: 2               # outline depth (0 = suppress outline)
    toc-dots: false            # dot-leader fill in outline entries
    toc-title: 'Table of Contents'
    code-size: small           # monospace font size: normal | small | footnote
    page-break-l1: false       # weak #pagebreak() before each non-preamble L1 heading
  pdf:                         # deprecated — use typst channel instead
    logo-local-path: images/OASISLogo-v3.0.png
    logo-remote-url: 'https://docs.oasis-open.org/templates/OASISLogo-v3.0.png'
```

Both `toc-trigger` and `intro-trigger` must be non-empty for TOC replacement to
occur.  The trigger values are pandoc-generated HTML id attributes derived from
heading text.

For `channels.typst.code-size`, the valid values and their em sizes are: `normal` (1 em),
`small` (0.85 em), `footnote` (0.75 em).  The same names are valid for the
`\tablefontsize=` body directive.

## Options

### Global options

```
nide [-V|--version]
nide version
```

| Option              | Description                                             |
|:--------------------|:--------------------------------------------------------|
| `-V`, `--version`   | Print version string and exit                           |
| `version`           | Subcommand alias for `--version`                        |

Subcommand names accept any unambiguous prefix: `nide a` = assemble,
`nide r` = render, `nide ej` = eject, `nide ex` = explain, `nide v` = version, etc.

### `nide assemble`

```
nide assemble [-d|--debug] [-c PATH|--config PATH] [-t CHANNEL|--target CHANNEL]
```

| Option       | Default         | Description                                                       |
|:-------------|:----------------|:------------------------------------------------------------------|
| `-t CHANNEL` | (IR only)       | Compat: build IR + render `CHANNEL` in one step (`gfm-plus`, `html`, `typst`, `pdf`) |
| `-c PATH`    | `etc/nide.yaml` | Path to the assembly configuration file                           |
| `-d`         | off             | Debug mode: write per-stage snapshot files                        |

Without `-t`, writes only the channel-neutral IR to `build/ir.json`.

### `nide render`

```
nide render -t CHANNEL[,CHANNEL…] [-c PATH|--config PATH] [-d|--debug]
```

| Option            | Default         | Description                                        |
|:------------------|:----------------|:---------------------------------------------------|
| `-t CHANNEL[,…]`  | (required)      | Channels to render: `gfm-plus`, `html`, `typst`, `pdf`; CSV |
| `-c PATH`         | `etc/nide.yaml` | Path to the assembly configuration file            |
| `-d`              | off             | Enable debug-level logging                         |

Reads `build/ir.json` (or the path in `output-paths.ir`).  Run `nide assemble` first.

### `nide inject`

```
nide inject [-c PATH|--config PATH]
```

| Option    | Default         | Description                             |
|:----------|:----------------|:----------------------------------------|
| `-c PATH` | `etc/nide.yaml` | Path to the assembly configuration file |

### `nide harvest`

```
nide harvest [-c PATH|--config PATH]
```

| Option    | Default         | Description                             |
|:----------|:----------------|:----------------------------------------|
| `-c PATH` | `etc/nide.yaml` | Path to the assembly configuration file |

### `nide explain`

```
nide explain [-c PATH|--config PATH] [-t CHANNEL|--target CHANNEL]
```

| Option       | Default         | Description                                      |
|:-------------|:----------------|:-------------------------------------------------|
| `-c PATH`    | `etc/nide.yaml` | Path to the assembly configuration file          |
| `-t CHANNEL` | (all channels)  | Limit channel-exclusion display to one channel   |

### `nide doctor`

```
nide doctor [-c PATH|--config PATH] [--fix]
```

| Option    | Default         | Description                                            |
|:----------|:----------------|:-------------------------------------------------------|
| `-c PATH` | `etc/nide.yaml` | Path to the assembly configuration file                |
| `--fix`   | off             | Create missing directories and run `nide harvest`      |

Exit code is 1 if any ERROR-level check fails; 0 otherwise (warnings alone → 0).

### `nide eject`

```
nide eject [-o PATH|--output PATH] [-f|--force]
```

| Option       | Default | Description                                   |
|:-------------|:--------|:----------------------------------------------|
| `-o PATH`    | stdout  | Write the template to `PATH` instead of stdout |
| `-f`         | off     | Overwrite `PATH` if it already exists          |

### Environment

| Variable       | Effect                                                              |
|:---------------|:--------------------------------------------------------------------|
| `NIDE_CONFIG`  | Default config path when `-c` is not given (all subcommands)        |
| `NIDE_DEBUG`   | Enable debug output; equivalent to `-d` (assemble and render modes) |
| `DUMP_LUT`     | Write back section LUT files to `etc-path` after assembly           |

## Dependencies

- [jsonschema](https://pypi.org/project/jsonschema/) — JSON Schema validation
- [kaava](https://pypi.org/project/kaava/) — typed configuration from YAML
- [python-jsonpath](https://pypi.org/project/python-jsonpath/) — JSONPath expression scanning
- [PyYAML](https://pypi.org/project/PyYAML/) — YAML parsing
- [vyyhti](https://pypi.org/project/vyyhti/) — Markdown embedding scanner

## Design and requirements

| Document                            | Identifier  | File                                            |
|:------------------------------------|:------------|:------------------------------------------------|
| Software Requirements Specification | NID-SRS-001 | [requirements/srs/](requirements/srs/README.md) |
| Software Design Description         | NID-SDD-001 | [design/sdd/](design/sdd/README.md)             |

Both documents follow the MIL-STD-498 DID structure and are rendered into
the documentation site alongside the quickstart and tutorial.

## Bug Tracker

Any feature requests or bug reports shall go to the [todos of nide](https://todo.sr.ht/~sthagen/nide).

## Primary Source repository

The main source of `nide` is on a mountain in Central Switzerland under
configuration control ([fossil](https://fossil-scm.org/)).

## Contributions

If you like to share small changes under the repositories license please kindly
do so by sending a patchset.
You can send such a patchset per email using [git send-email](https://git-send-email.io).

## Support

Please kindly submit issues at https://todo.sr.ht/~sthagen/nide or write plain
text email to ~sthagen/nide@lists.sr.ht to support.
Thanks.

## Security Policy

See `SECURITY.md` for the security policy.

## Changes

See `docs/changes.md` for the release history.

## Coverage

The test suite maintains high branch coverage (≥99%).
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.
