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

Constrain frontmatter and ids

When the frontmatter of a document must have certain keys with certain values, declare each key under frontmatter and constrain its value. At the end, a document with a missing key, an unknown key, or a value of the wrong shape fails with an error that names the key.

In this guide, you write the frontmatter of a requirement list: a fixed type, a numbered id, a code in capitals, a status, one or two links, and optional tags.

Before you begin

Steps

  1. Declare each key under frontmatter. The check requires every declared key unless you set optional: true on it. A key that the schema doesn’t declare is an error.

  2. Constrain each value with one of these keys:

    • const: the value must equal the given value.
    • enum: the value must be one of the listed values.
    • template or pattern: the value must match the shape. Give the key an example, as on any rule.
    • format: url for an absolute URL, or slug for lowercase letters and digits joined by single hyphens.
  3. For a key whose value is a list, set list: true. The constraint then applies to every element, and min and max bound the number of elements. A list where no list is declared, or a single value where a list is declared, is an error.

  4. Give id a template. The template uses only the holes that the engine fills when it writes a new document:

    • ${:seq}: a sequence number, unique within the kind of document. A template can contain at most one. The number is three digits wide unless you set seq-width beside the template.
    • ${:slug}: one or more slug words.
    • ${<key>}: a hole with no type, named for a lifecycle key. It holds the state of the document for that key. Step 7 shows an example.

    An id template takes no example. For a kind that has only one document, set the id with const instead.

requirements.yaml

line-limit: 0
types:
  code: '[A-Z]{2,6}'
frontmatter:
  type:
    const: Requirements
  id:
    template: 'req-${:seq}-${:slug}'
  code:
    template: '${:code}'
    example: LINK
  status:
    enum: [draft, approved]
  homepages:
    list: true
    format: url
    min: 1
    max: 2
    example: https://example.com/link-checker
  tags:
    list: true
    format: slug
    optional: true
    example: parsing
sections:
  - heading: Link checker
    level: 1
    blocks:
      - kind: prose
        example: The link checker follows every link in a document.
  1. Check a document. Each error names the frontmatter key that fails:

link-checker.md

---
type: Requirements
id: req-7-link-checker
code: Link
status: final
homepages: https://example.com/link-checker
tags: [Parsing, links]
owner: Sam
---

# Link checker

The link checker follows every link in a document.
$ tramlines validate --schema requirements.yaml link-checker.md
link-checker.md:3: error[TL_56]: id `req-7-link-checker` does not match the template `req-${:seq}-${:slug}` (schema requirements, key `id`)
link-checker.md:4: error[TL_56]: `code` `Link` fails at column 1: expected a `code` (`[A-Z]{2,6}`), found `Link`; a value of this kind reads LINK (schema requirements, key `code`)
link-checker.md:5: error[TL_56]: `status` must be one of `draft`, `approved`; `final` is not (schema requirements, key `status`)
link-checker.md:6: error[TL_57]: `homepages` must be a list (schema requirements, key `homepages`)
link-checker.md:7: error[TL_56]: `tags` must be a slug; `Parsing` is not (schema requirements, key `tags`)
link-checker.md:8: error[TL_55]: frontmatter key `owner` is not declared (schema requirements)
6 errors, 0 warnings
[exit 1]

The id has a one-digit sequence number where the template needs three, code isn’t in capitals, final isn’t a status, homepages isn’t a list, Parsing isn’t a slug, and owner isn’t declared. This version of the document corrects each one:

link-checker.md

---
type: Requirements
id: req-007-link-checker
code: LINK
status: draft
homepages: [https://example.com/link-checker]
tags: [parsing, links]
---

# Link checker

The link checker follows every link in a document.
$ tramlines validate --schema requirements.yaml link-checker.md
0 errors, 0 warnings
  1. To accept keys that the schema doesn’t declare, set frontmatter-open: true on the schema. The command still checks the declared keys. With frontmatter-open: true, the command accepts owner:

requirements.yaml

line-limit: 0
frontmatter-open: true
types:
  code: '[A-Z]{2,6}'
frontmatter:
  type:
    const: Requirements
  id:
    template: 'req-${:seq}-${:slug}'
  code:
    template: '${:code}'
    example: LINK
  status:
    enum: [draft, approved]
  homepages:
    list: true
    format: url
    min: 1
    max: 2
    example: https://example.com/link-checker
  tags:
    list: true
    format: slug
    optional: true
    example: parsing
sections:
  - heading: Link checker
    level: 1
    blocks:
      - kind: prose
        example: The link checker follows every link in a document.

owned.md

---
type: Requirements
id: req-008-owned-links
code: LINK
status: draft
homepages: [https://example.com/link-checker]
owner: Sam
---

# Link checker

The link checker follows every link in a document.
$ tramlines validate --schema requirements.yaml owned.md
0 errors, 0 warnings
  1. To put a state of the document in its id, name a hole of the id template after a lifecycle key. Apply a rule in some states only shows how to declare one. The id must then contain the state of the document for that key. This schema requires a four-digit number in the id of each issue:

issue.yaml

line-limit: 0
lifecycle:
  - { key: kind, states: [bug, feature] }
frontmatter:
  id:
    template: 'issue-${:seq}-${kind}-${:slug}'
    seq-width: 4
sections:
  - heading: Issue
    level: 1
    blocks:
      - kind: prose
        example: A list item indented with a tab is read as a code block.

tab.md

---
id: issue-0007-feature-tab
kind: bug
---

# Issue

A list item indented with a tab is read as a code block.
$ tramlines validate --schema issue.yaml tab.md
tab.md:2: error[TL_59]: id `issue-0007-feature-tab` holds `feature` for `kind`, but `kind` is `bug` (schema issue, key `id`)
1 error, 0 warnings
[exit 1]

The command reserves no frontmatter key. A program that embeds the engine can reserve its own keys. The check never reports a reserved key as undeclared. A schema can still declare a reserved key to require it or to narrow its value.

Troubleshooting

  • TL_54: a declared key is missing. Add it, or set optional: true on the key. See TL_54.
  • TL_55: a key isn’t declared. Remove it, declare it, or set frontmatter-open: true. See TL_55.
  • TL_56: a value fails its const, template, pattern, enum, or format. See TL_56.
  • TL_106, TL_107: a list has fewer elements than its min, or more than its max. See TL_106 and TL_107.
  • TL_57: a list is declared and the value is a single value, or the other way round. See TL_57.
  • TL_58: the id template contains a hole that the engine can’t fill for a new document. See TL_58.
  • TL_114: the id template contains more than one ${:seq}. See TL_114.
  • TL_115: the id declares another key beside its template. See TL_115.
  • TL_59: the id contains a state other than the state of the document for that key. See TL_59.
  • TL_53: the frontmatter isn’t valid YAML. See TL_53.
  • TL_123: the frontmatter contains an anchor, an alias, or a tag. See TL_123.

See also