Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Strict by default

A schema is strict. What it declares is required, what it doesn’t declare is refused, and a section contains exactly the blocks that the schema declares for it, in order. So tramlines reports a heading that the schema doesn’t mention. You loosen each rule with a key that you set on purpose.

Why strict

A schema exists to keep documents of one kind alike, so that a reader, a script, or an agent can rely on their shape. A lenient checker passes a document with an extra section, a misspelled frontmatter key, or a missing part. The documents then drift apart unnoticed until someone depends on their shape. A strict checker reports each fault at the line where it occurs.

Strictness also keeps a schema complete. Every part that a document can contain is written in the schema. The schema is therefore the full description of the kind, and the scaffold derived from it contains every part that the schema declares.

The three rules

Three rules apply unless the schema relaxes them:

  • What is declared is required. A declared section, frontmatter key, or block must be present exactly once.
  • What is not declared is refused. A heading, frontmatter key, table column, or block that the schema doesn’t declare is an error.
  • A declaration is exclusive. A section’s body contains exactly its declared blocks, in the declared order, and the sections come in the declared order.

This schema describes a shopping list with a title and one list:

list.yaml

line-limit: 0
frontmatter:
  title:
    example: Groceries
sections:
  - heading: { from: title }
    level: 1
    blocks: []
  - heading: Items
    level: 2
    blocks:
      - kind: bullet-list
        example: Milk

This document adds a frontmatter key and a section that the schema doesn’t declare:

list.md

---
title: Groceries
store: Corner shop
---

# Groceries

## Items

- Milk
- Bread

## Notes

Pay by card.
$ tramlines validate --schema list.yaml list.md
list.md:3: error[TL_55]: frontmatter key `store` is not declared (schema list)
list.md:13: error[TLMD_19]: heading `## Notes` is not declared (schema list)
2 errors, 0 warnings
[exit 1]

The command reports each undeclared part as one error, at the line of that part.

The relaxations

Each relaxation is a key, and each loosens one rule for one part of the schema. Nothing loosens by default, so a reader of the schema sees every exception where it applies.

KeyLoosensWhere
optional: trueA declared part can be absent.A section, a block, a frontmatter key, a position in a sequence.
repeat: trueA declared part can occur more than once.A section, a block, a position in a sequence.
frontmatter-open: trueUndeclared frontmatter keys are accepted.The schema.
headings-open: trueUndeclared headings of the section’s level or deeper can follow it.A section.
subheadings-open: trueUndeclared headings deeper than the section can follow it.A section.
columns-open: trueFurther columns after the declared ones are accepted.A table block.
units-open: trueA unit that no rule under rules takes is accepted.A block.
lines-open: trueA line that no rule under lines takes is accepted.A unit rule with lines.
sections-ordered: falseSections can come in any order.The schema.
blocks-ordered: falseBlocks can come in any order.A section.

The core grammar defines frontmatter-open, and the Markdown grammar defines the rest. A host extension can add -open keys of its own. The schema-keys reference describes each key in full.

The table leaves out min and max, because they are bounds. They set how many units a block contains, and they default to one and no limit.

Relaxing the example

To accept the extra key and section, the schema relaxes the two rules that the document broke, and nothing else. frontmatter-open accepts any frontmatter key. A Notes section with optional: true accepts the extra section without requiring it in every list:

list.yaml

line-limit: 0
frontmatter-open: true
frontmatter:
  title:
    example: Groceries
sections:
  - heading: { from: title }
    level: 1
    blocks: []
  - heading: Items
    level: 2
    blocks:
      - kind: bullet-list
        example: Milk
  - heading: Notes
    level: 2
    optional: true
    blocks:
      - kind: prose
        example: Pay by card.
$ tramlines validate --schema list.yaml list.md
0 errors, 0 warnings

A declaration of store under frontmatter, with optional: true, is the narrower choice. It accepts that one key and still refuses a misspelling of it.

An open region

An open section accepts undeclared headings after it, up to the next declared heading. tramlines checks the bodies under those headings only against the rules that apply to the whole document, such as prohibited-patterns and line-limit. Use an open region for a part whose structure the schema can’t predict, such as free notes, and keep it as small as you can. To ban particular headings inside an open region, list them under sections-prohibited.

Absence is strict too

A key that you leave out never relaxes a rule. A section with no blocks must contain exactly one prose block. A block with no optional is required. A table with no columns-open contains exactly its declared columns. Pitfalls in writing a schema shows the errors that follow.

See also