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
Install the core package:
pip install nide
To record and verify source-tree provenance with nide manifest and nide validate,
install the optional manifest dependency group:
pip install nide[manifest]
This adds three packages:
| Package | Purpose |
|---|---|
tallipoika |
RFC 8785 canonical JSON — required for nide manifest generation |
blake3 |
BLAKE3 hashing — sha256-only fallback if absent; both hashes recorded when present |
pyHanko |
PDF XMP injection — used by nide manifest --inject-pdf to write Dublin Core metadata as XMP |
nide manifest --summary and nide validate read an existing build/manifest.json
and do not require tallipoika. They can run in environments where only the core
package is installed, provided the manifest was generated elsewhere.
Manual
The man page provides the CLI reference (man nide after placing the file on your MANPATH):
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.
A step-by-step guided build of a document pipeline is provided in the tutorial.
Some commented real-world configurations are provided in the example 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
nide manifest → build/manifest.json + embedded blocks in channel outputs
nide validate → six-check integrity report (zero exit on pass)
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 ten subcommands. Every name accepts any unambiguous prefix —
nide a = assemble, nide r = render, nide m = manifest, nide val = validate, 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 bynide 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; writesbuild/html.mdandbuild/toc-mint.json.gfm-plus: numbers headings with inline anchors; passes glossary through verbatim; writesbuild/gfm-plus.md.typst: converts headings, code blocks, anchors, and cross-references to raw typst blocks; writesbuild/typst.mdandbuild/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\phantomsectionbefore unnumbered (annex/appendix) section headings; replaces the remote logo URL with the local path; writesbuild/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 ofhtml-titlefrom config.- Base and skin CSS inlined;
<link>tags removed. - Remote logo URL replaced with a local data URL.
- Image
srcattributes 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 manifest [-c PATH] records source-tree provenance after all channel renders are
complete. It hashes every binder source file, the config file, and all channel output files,
detects the active CCS (git, fossil, or none), records branch/revision/per-file dates,
captures toolchain versions, and writes build/manifest.json. It also appends a canonical
JSON block to each channel output file (HTML comment for GFM-plus and HTML; /* … */ for
typst); the operation is idempotent. Requires pip install nide[manifest] for the
tallipoika canonical-JSON dependency.
nide manifest --summary [--format text|markdown|yaml] reads an existing
build/manifest.json and prints a human-readable rendering to stdout. Does not require
tallipoika. Default format is text (aligned); markdown produces pipe tables; yaml
produces a PyYAML human-readable subset.
nide validate [--strict] [--quiet] reads build/manifest.json and runs six integrity
checks: manifest self-hash, per-source-file content hashes, config hash, binder composition
hash, per-channel output hashes (embedded blocks stripped before hashing), and cross-channel
source-state consistency. Exits 0 (all pass), 1 (any fail), or 2 (usage error / manifest
not found). --strict treats WARN as failure; --quiet suppresses per-check output.
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 six top-level YAML keys.
assembly: — source file handling
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
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
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
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: {#fig:label}.
Both cite-template and entry-template support $code$, $label$, and (for
entry-template) $text$ placeholders.
channels: — channel-specific settings
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
list-of-figures: false # emit List of Figures outline after TOC
list-of-tables: false # emit List of Tables outline after TOC (LOF first)
watermark: '' # diagonal page-background watermark text; '' = none
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.
metadata: — Dublin Core document metadata
metadata:
title: '' # dc:title (lang-alt in XMP)
creator: '' # dc:creator
publisher: '' # dc:publisher (rdf:Alt)
description: '' # dc:description
subject: '' # dc:subject, comma-separated tags (rdf:Bag)
date: '' # publication/creation date in ISO 8601 (e.g. 2026-07-16)
type: '' # dc:type (rdf:Bag), e.g. 'Standard'
identifier: '' # dc:identifier, persistent URI/DOI
language: '' # dc:language (rdf:Bag), BCP 47 tag, e.g. 'en'
rights: '' # dc:rights (rdf:Alt), rights statement or license URI
version: '' # dcterms:hasVersion, e.g. '1.0'
status: '' # dcterms:abstract, e.g. 'DRAFT' or 'Approved'
All fields are optional and default to empty string. Non-empty fields are written
into build/manifest.json under the metadata key after each nide manifest run.
nide manifest --inject-pdf PATH maps non-empty fields to the corresponding XMP
namespaces and writes <stem>-injected.pdf alongside the source PDF (requires
pip install nide[manifest] for pyHanko). 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, etc.
An unknown or ambiguous subcommand prints an error to stderr and exits with code 2.
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 |
nide manifest
nide manifest [-c PATH|--config PATH] [--summary [--format text|markdown|yaml]]
nide manifest [-c PATH|--config PATH] --inject-pdf PATH
| Option | Default | Description |
|---|---|---|
-c PATH |
etc/nide.yaml |
Path to the assembly configuration file |
--summary |
off | Read and print existing manifest; do not regenerate |
--format FORMAT |
text |
Summary format: text, markdown, or yaml |
--inject-pdf PATH |
(none) | Inject metadata: fields as XMP into PDF; writes <stem>-injected.pdf; needs pyHanko |
Without --summary, nide manifest generates (or regenerates) build/manifest.json
and embeds blocks in channel outputs. Requires the tallipoika package
(pip install nide[manifest]).
With --summary, reads the existing manifest and prints to stdout; no tallipoika needed.
--inject-pdf writes Dublin Core XMP metadata from the metadata: config section
into the PDF. Exit 1 if pyHanko is absent; exit 2 if the PDF is not found.
nide validate
nide validate [-c PATH|--config PATH] [--strict] [--quiet]
| Option | Default | Description |
|---|---|---|
-c PATH |
etc/nide.yaml |
Path to the assembly configuration file |
--strict |
off | Treat WARN-level findings as failures (exit 1) |
--quiet |
off | Suppress per-check output; only exit code is informative |
Exit code 0: all six checks pass. Exit code 1: at least one check fails
(or WARN when --strict). Exit code 2: usage error or build/manifest.json
not found.
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
Core (always installed):
- jsonschema — JSON Schema validation
- kaava — typed configuration from YAML
- python-jsonpath — JSONPath expression scanning
- PyYAML — YAML parsing
- vyyhti — Markdown embedding scanner
Optional — manifest provenance (pip install nide[manifest]):
- tallipoika — RFC 8785 canonical JSON (required for
nide manifestgeneration) - blake3 — BLAKE3 hashing (enhancement; sha256-only fallback if absent)
- pyHanko — PDF XMP injection; used by
nide manifest --inject-pdf
Design and requirements
| Document | Identifier | File |
|---|---|---|
| Software Requirements Specification | NID-SRS-001 | requirements/srs/ |
| Software Design Description | NID-SDD-001 | design/sdd/ |
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.
Primary Source repository
The main source of nide is on a mountain in Central Switzerland under
configuration control (fossil).
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.
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.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nide-2026.7.16.tar.gz.
File metadata
- Download URL: nide-2026.7.16.tar.gz
- Upload date:
- Size: 115.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b408b178b54c0b32683925e59a3db50925ec0ceda3745328a169efb2e1a80831
|
|
| MD5 |
62b8e7f6c5dee8a1b7004e3b0243ac39
|
|
| BLAKE2b-256 |
fae0cc9168aed85d59be627c23c25e42c4af423f43f2c209647d87f76e96cf20
|
File details
Details for the file nide-2026.7.16-py3-none-any.whl.
File metadata
- Download URL: nide-2026.7.16-py3-none-any.whl
- Upload date:
- Size: 67.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb65f0989691031c8508a2a30c3b75c0cd676c124a9596a8a34b50d7a16938f6
|
|
| MD5 |
3717415ac70f4b334132ae86d20fc528
|
|
| BLAKE2b-256 |
f6a273a72247aa1000a8ee3117979a00e7289b5940075cacb86f72ba3dd42986
|