nide
A bound volume (Finnish: nide) - assemble markdown text 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:
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 pages provide the full CLI, library, configuration, and
overview reference across sections 1 (commands), 3 (library API), 5 (config
file format), and 7 (overview and concepts). Install all of them at once
(man nide afterwards, or man nide-render, man 5 nide, man 7 nide, etc.):
nide eject man
This installs to ~/.local/share/man by default; pass --man-path PATH to
install elsewhere. Re-running after an upgrade always overwrites, so the
locally-installed pages stay in sync with the installed package.
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/section-records.json
pandoc -> build/pandoc.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)
nide quality --rules etc/rules/spec.rules.yaml -> per-rule PASS/FAIL/NA report
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 thirteen subcommands. Every name accepts any unambiguous prefix -
nide a = assemble, nide r = render, nide m = manifest, nide val = validate, nide ej = eject, nide con = config, nide inf = info, nide q = quality, etc.
Three more, doctor, explain, and completion, are deprecated top-level aliases for
config doctor/config explain/complete --for (see nide config and
nide complete below).
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. - Standalone local image references (
as the entire line) are resolved to absolute paths relative to their source file's directory and stored asImageNodeentries in the IR. Remote URLs pass through verbatim.
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.
Before writing any channel output, nide render copies all locally-resolved images
to build/images/ and rewrites image references in all channels. No Makefile cp
step is required - the image tree is managed automatically. A bundled missing.svg
placeholder is installed when any image could not be found at assembly time.
html: numbers headings with inline anchors; expands glossary into<dl>elements; writesbuild/html.mdandbuild/section-records.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/section-records.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 config 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. The top-level nide explain
form still works but is a deprecated alias.
nide config 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.
The top-level nide doctor form still works but is a deprecated alias.
nide info env reports interpreter, platform, pandoc/typst tool versions,
the active YAML backend, and optional-extra availability. --preset extended|full
adds more detail; --format yaml|json changes the output encoding. nide info version mirrors the top-level nide version.
nide eject config 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 eject man [--man-path PATH] installs the bundled man pages (sections
1/3/5/7) into a man-page tree, default ~/.local/share/man. Always overwrites,
so a re-run after an upgrade stays in sync with the installed package.
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 quality [--rules PATH[,PATH...]] [--strict] [--json] [--quiet] reads
build/ir.json and evaluates each rule in the configured YAML rules files,
reporting PASS, FAIL, or NA per rule.
Supports eight check types: section-present, section-absent, metadata-present,
all-match (with optional invert: true), any-match, count, references-resolve,
and conditional (condition failing maps to NA, condition passing will evaluate the then check).
Checks operate on IR targets: prose, headings, links, relative-links, images,
or section:<pattern>.
Rules files use a two-file convention: an org-level base file (fetched from
publication-assurance, do not edit) and a spec-level file that extends it with
meta.extends: <name> and adds spec-specific rules.
Rules files are specified with --rules or under quality.rules in nide.yaml.
Exit codes: 0 (no BLOCKER failed), 1 (at least one BLOCKER failed), 2 (usage error).
nide version (also -V / --version) prints the installed version and exits.
nide complete --for bash|fish|zsh prints a shell completion script to stdout.
Requires pip install shtab. The top-level nide completion SHELL form still works
but is a deprecated alias.
Four output channels are supported:
html
: build/html.md + build/section-records.json -- pandoc to nide inject
gfm-plus
: build/gfm-plus.md -- direct delivery
typst
: build/typst.md + build/nide-columns.lua -- pandoc to 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 config to emit a fully-commented template with every key.
Settings are grouped under seven 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 # inline ref expands to 'Table N'
caption-prefix: Table # standalone caption's own lead qualifier
caption-position: leading # caption before the table (gfm-plus/html)
figures:
keyword: fig # inline trigger: [fig](#label)
display-prefix: Figure # inline ref expands to 'Figure N'
caption-prefix: Figure # standalone caption's own lead qualifier
caption-position: trailing # caption after the figure (gfm-plus/html)
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.
display-prefix controls only the inline [tab](#label)/[fig](#label)
expansion in running prose. In the gfm-plus and HTML channels, nide also
generates the standalone caption itself -- pandoc's gfm reader (used to build
both) has no table_captions/implicit_figures support, unlike the markdown
reader the PDF/typst channels use. That generated caption is controlled
separately: caption-prefix sets its lead qualifier (independent of
display-prefix, so prose can read "see table 3" while the caption reads
"Table 3: ..."), and caption-position (leading/trailing, any unique
case-insensitive prefix accepted, e.g. l) places it before or after the
float. Tables default to leading (a caption ahead of the data often orients
the reader); figures default to trailing (expected to be mostly
self-explanatory). A figure's generated caption text is its image's alt text.
The old leading: true/false key is still accepted as a deprecated alias.
tables: - multi-format table input (gfm-plus/html only)
tables:
normalize-gfm-plus: true # default -- accept simple/multiline/grid tables, not just pipe
on-data-loss: html # default -- html | verbatim (either accepts a unique prefix)
Pandoc accepts four table syntaxes (pipe, simple, multiline, grid) in the
markdown reader the pdf/typst channels use, but the gfm reader (gfm-plus/html)
accepts only pipe tables -- same restriction GitHub's own renderer has. With
normalize-gfm-plus: true (the default), nide accepts all four in source and
converts non-pipe tables to pipe-table markdown for gfm-plus/html only, via a
pandoc subprocess round-trip -- pdf/typst are unaffected, since they already
read all four natively.
Not every table can losslessly become pipe-table markdown -- a pipe-table cell
is single-line only, so a grid table with a multi-paragraph or list cell can't
be represented that way. on-data-loss controls what happens then: html
(default) keeps pandoc's own raw-HTML <table> fallback -- full rendered
fidelity, at the cost of that one table's source no longer looking like a
table; verbatim discards the HTML in favor of wrapping the original block as
a fenced code block -- preserves the authored visual layout, at the cost of it
no longer being a real table in the rendered output. Either way a warning is
logged so the loss isn't silent. Pandoc must be on PATH for normalization to
run; if it isn't, normalization is skipped with a warning rather than failing
the build.
channels: - channel-specific settings
channels:
gfm-plus:
trim-heading-anchor-space: true # strip the space before <a id> on headings
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
image-cache: build/image-cache
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.
nide always separates a heading's text from its trailing <a id="..."> anchor
with a space (e.g. ### 1.2 Some Heading <a id="some-heading"></a>). GitHub's
own markdown renderer derives its automatic heading-permalink slug from the
heading's plain text after stripping inline HTML -- that space survives the
strip and becomes a trailing hyphen (some-heading-), one character off from
the id nide actually assigned (some-heading). trim-heading-anchor-space
(default true, gfm-plus only) removes the space so the two agree; set it to
false to keep the old spacing.
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.
When the HTML channel output (build/html.md) is present, non-empty fields are also injected
as <meta name="DC.*"> / <meta name="DCTERMS.*"> tags into <head> by the manifest embedding pass (DCMI-HTML convention;
subject is comma-split into one tag per term). 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).
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.
quality: - rule-based document quality checks
quality:
rules:
- etc/rules/spec.rules.yaml # spec-level rules file (edit this one)
# - etc/rules/organization.rules.yaml # org-level base file (fetch, do not edit)
quality.rules is a list of paths to YAML rules files, resolved relative to the directory
containing nide.yaml. Files are evaluated in the order listed. Equivalent to
passing --rules on the command line.
Each rules file has three top-level keys:
meta:
name: my-spec-rules
extends: organization # optional: merge organization.rules.yaml first
context:
tc-name: dps
spec-version: "1.0"
stage: csd02
rules:
- id: MY-SECTION
severity: BLOCKER
description: A required section must be present
check:
type: section-present
pattern: '^Required Section'
flags: IGNORECASE
section-present matches HeadingNode.text (base heading text only - the section counter lives in sec_cnt_disp and must not appear in patterns).
context variables are available as ${key} in pattern and literal values.
meta.extends: name merges <name>.rules.yaml from the same directory before applying the spec-specific rules.
A spec rule whose id matches an inherited org rule replaces it in place - same position,
new severity/pattern/check - instead of adding a duplicate; this lets a spec override one org-level rule without forking the whole org file.
Options
Global options
nide [-V|--version]
nide version
nide [SUBCOMMAND] --interface FORMAT
-V, --version
: Print version string and exit
version
: Subcommand alias for --version
--interface FORMAT
: Print the (sub)command's own options/arguments as text, yaml, or json, then exit
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.
--interface may follow any subcommand (nide render --interface yaml) or stand
alone for the whole tree (nide --interface json); both the flag name and the FORMAT
value accept unambiguous prefixes (--interf=y). It is recognised before a
subcommand's own required flags are checked, so it works on commands like render
that would otherwise demand -t/--target. Intended for tooling and shell integration
(it backs bin/discover.py's interface inventory), not everyday interactive use.
nide assemble
nide assemble [-d|--debug] [-c PATH|--config PATH] [-t CHANNEL|--target CHANNEL]
-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]
-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]
-c PATH
: etc/nide.yaml -- Path to the assembly configuration file
nide harvest
nide harvest [-c PATH|--config PATH]
-c PATH
: etc/nide.yaml -- Path to the assembly configuration file
nide config
nide config explain [-c PATH|--config PATH] [-t CHANNEL|--target CHANNEL]
nide config doctor [-c PATH|--config PATH] [--fix]
The top-level nide explain/nide doctor forms still work but are deprecated
aliases: each prints a one-line notice to stderr, then behaves identically.
nide config explain:
-c PATH
: etc/nide.yaml -- Path to the assembly configuration file
-t CHANNEL
: (all channels) -- Limit channel-exclusion display to one channel
nide config doctor:
-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 lead to 0).
nide info
nide info env [--format text|yaml|json] [--preset basic|extended|full]
nide info version
nide info env:
--format FMT
: text -- Output format: text, yaml, or json
--preset LEVEL
: basic -- Detail level: basic, extended, or full
nide info version prints the same version string as the top-level nide version.
nide eject
nide eject config [-o PATH|--output PATH] [-f|--force]
nide eject man [--man-path TREE_ROOT]
nide eject config:
-o PATH
: stdout -- Write the template to PATH instead of stdout
-f
: off -- Overwrite PATH if it already exists
nide eject man:
--man-path TREE_ROOT
: ~/.local/share/man -- Root of the man-page tree; man1/, man3/, man5/, man7/ are created inside it as needed
eject man always overwrites existing files at the destination (idempotent
reinstall after an upgrade), unlike eject config, which refuses to
overwrite without -f.
nide manifest
nide manifest [-c PATH|--config PATH] [--summary [--format text|markdown|yaml]]
nide manifest [-c PATH|--config PATH] --inject-pdf PATH
-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]
-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.
nide complete
nide complete --for bash|fish|zsh
The top-level nide completion SHELL form (positional, no --for) still works
but is a deprecated alias.
--for SHELL
: Target shell: bash, fish, or zsh
Prints a shell completion script to stdout. Requires the shtab package
(pip install shtab) -- nide prints an install hint and exits 2 if it's missing.
nide complete --for bash >> ~/.bash_completion
nide complete --for zsh >> ~/.zshrc
nide complete --for fish > ~/.config/fish/completions/nide.fish
Pre-generated scripts are also checked into docs/completion/nide.{bash,fish,zsh}
as a reference if you'd rather not install shtab yourself.
Environment
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
SOURCE_DATE_EPOCH
: Unix timestamp (seconds since epoch) used by nide diff --mode repro to normalise build timestamps; see below
SOURCE_DATE_EPOCH and reproducible builds
SOURCE_DATE_EPOCH is a reproducible-builds.org standard.
When set, nide diff in repro mode replaces recognised build timestamps - ISO date/datetime values
in <meta content="..."> attributes and, when +xmp is active, in the XMP block - with the canonical UTC
string YYYY-MM-DDTHH:MM:SSZ derived from the epoch value, rather than the generic placeholder [timestamp].
This makes two builds of the same source compare as identical regardless of when they were run.
Authors typically anchor the variable to their last VCS commit before building:
# git
export SOURCE_DATE_EPOCH=$(git log -1 --format=%ct)
# Fossil
export SOURCE_DATE_EPOCH=$(fossil info tip | awk '/^uuid:/{print $NF}' | xargs fossil time -R . | awk 'NR==1{print $1" "$2}' | date -f - +%s 2>/dev/null)
Or set it once in the project Makefile so every make html pdf is automatically anchored:
SOURCE_DATE_EPOCH ?= $(shell git log -1 --format=%ct)
export SOURCE_DATE_EPOCH
Without the variable, repro mode still suppresses timestamp noise - timestamps become [timestamp]
rather than a real date - so setting it is an opt-in for commit-anchored, bit-reproducible builds.
Dependencies
Core (always installed):
- jsonschema - JSON Schema validation
- kaava - typed configuration from YAML
- python-jsonpath - JSONPath expression scanning
- ruamel.yaml - YAML parsing (default backend; see below)
- vyyhti - Markdown embedding scanner
Optional - alternate YAML backend (pip install nide[pyyaml], or a bare
pip install PyYAML -- no extra required):
- PyYAML - alternate YAML parser; select it with
NIDE_YAML_BACKEND=pyyaml. Seenide info envto check which backend is active. An explicit request for a backend that isn't installed fails loudly rather than silently using the other one -- seenide(7).
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
Optional - shell completion (pip install shtab):
- shtab - generates the bash/fish/zsh scripts
nide completeprints
Design and requirements
Software Requirements Specification : NID-SRS-001 -- requirements/
Software Design Description : NID-SDD-001 -- design/
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 releases/ for release summaries and releases/changes/ for the detailed change log.
Complexity
The code base complexity is documented at complexity/.
Coverage
The test suite maintains high branch coverage (>=99%). The HTML report (if generated) is in coverage/.
SBOM
Runtime dependency information is published in sbom/ in SPDX 3.0 (JSON-LD) and CycloneDX 1.6 (JSON) formats.
See sbom/README.md for the component inventory and validation guide.
Metadata
Release files for nide 2026.10.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| nide-2026.10.3.tar.gz | 258.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| nide-2026.10.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 422.7 kB
Release files / nide-2026.10.3.tar.gz
| Download URL | nide-2026.10.3.tar.gz |
|---|---|
| Size | 258.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
59f6c5cde7a7e48fa05041baa7230a997019b429a610868c0b76413026536c1f
|
|
BLAKE2b-256 checksum How to use checksums |
e17d07788dbb743ffcd36a04894f8078388058f358e15ecd22e74a3fd912064c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.5
|
Release files / nide-2026.10.3-py3-none-any.whl
| Download URL | nide-2026.10.3-py3-none-any.whl |
|---|---|
| Size | 164.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f2226145bb5f678762f650a95d78f34a880e79f677b087114a9a0a4c5626bc56
|
|
BLAKE2b-256 checksum How to use checksums |
1117b0f01118fe0c838982866aa2c880c4b908b9ee3e2755c55dc0e88aa44f3d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.5
|