Skip to main content

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/man/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/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 twelve 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, nide q = quality, 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.
  • Standalone local image references (![alt](path) as the entire line) are resolved to absolute paths relative to their source file's directory and stored as ImageNode entries 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; writes build/html.md and build/section-records.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/section-records.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 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 → NA, condition passing → 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.

Four output channels are supported:

Channel Output (nide render) Intended consumer
html build/html.md + build/section-records.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 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                                 # 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

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

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/oasis.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: oasis          # optional: merge oasis.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.

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
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):

Optional — manifest provenance (pip install nide[manifest]):

  • tallipoika — RFC 8785 canonical JSON (required for nide manifest generation)
  • 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 docs/requirements/srs/
Software Design Description NID-SDD-001 docs/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/releases/ for release summaries and docs/releases/changes/ for the detailed change log.

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

nide-2026.7.19.tar.gz (158.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nide-2026.7.19-py3-none-any.whl (88.6 kB view details)

Uploaded Python 3

File details

Details for the file nide-2026.7.19.tar.gz.

File metadata

  • Download URL: nide-2026.7.19.tar.gz
  • Upload date:
  • Size: 158.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for nide-2026.7.19.tar.gz
Algorithm Hash digest
SHA256 9f75260028ff98a543c34f3a4f0dd132f672cf73994c284ce5d6e6598cf2ab8c
MD5 056a1c7454ef6e23ec27c41db7626d80
BLAKE2b-256 aac879e9986c82b44f5465669ac9084e551604ddc433ce65b2e7b3ff30368a27

See more details on using hashes here.

File details

Details for the file nide-2026.7.19-py3-none-any.whl.

File metadata

  • Download URL: nide-2026.7.19-py3-none-any.whl
  • Upload date:
  • Size: 88.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for nide-2026.7.19-py3-none-any.whl
Algorithm Hash digest
SHA256 5bcbf843117e688a88efdb4f3586a6062a332b5a6e6da625c115a56b206e7c5c
MD5 99c8bb4ff0177df1fd8007af5ee27000
BLAKE2b-256 33b7615db9de1dd83b153b2877053a56a6df9a4cba7c854d7cf26669db1cec69

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page