Select and count units and lines
When one list mixes items of several kinds, or a code block must contain certain lines, write the grammar of the block with rules or lines. At the end, the rule of its kind takes each unit or line, and each kind stays within the count that you set. Anything else fails at its line.
Before you begin
- A schema with blocks. Describe blocks and their units shows how to write one.
- Templates and patterns, as in Write the shape of a line.
Steps
-
Replace
eachorsequencewithrules, a list of unit rules. A rule must take every unit. A rule acts in one of four ways:- A rule with a shape and no
select-patternis a production. It takes the units that its shape matches. Each unit goes to the first production that matches it, so list the narrowest production first. - A rule with a
select-patternis a guard. It takes the units whose text the regular expression matches, and checks them. Use a guard to pick units by a label rather than by their shape, such as an item whose nested blocks you check. - A rule with only a
select-patternand a count is a counter. It counts the units that it matches and takes none, so a production still checks them. - A rule with neither a shape nor a
select-patterntakes every unit.
- A rule with a shape and no
-
Bound each rule with
minandmax. By default, a production requires at least one unit, andmin: 0allows none. A rule with aselect-patternhas no default, so you must set itsmin. A counter withmax: 0bans what it matches.A guard selects units independently of other rules: two guards can select one unit, and both check it. A counter counts a unit that a production takes.
This changelog schema accepts added and fixed entries, at most one breaking entry with a migration command under it, and no
TODO:
changelog.yaml
line-limit: 80
sections:
- heading: { template: "${:int}.${:int}.${:int}", example: "2.1.0" }
level: 2
repeat: true
blocks:
- kind: bullet-list
rules:
- template: "Added: ${}."
min: 0
example: "Added: a dark theme."
- template: "Fixed: ${}."
min: 0
example: "Fixed: the crash on start."
- select-pattern: "^Breaking: "
min: 0
max: 1
example: "Breaking: the config file moved."
blocks:
- kind: code
lang: console
example: "$ app migrate"
- select-pattern: "TODO"
min: 0
max: 0
- Check a document:
CHANGELOG.md
## 2.1.0
- Added: a dark theme.
- Fixed: the crash on start.
- Breaking: the config file moved.
```console
$ app migrate
```
$ tramlines validate --schema changelog.yaml CHANGELOG.md
0 errors, 0 warnings
- Read the errors for rules. A unit that no rule takes fails against the production that it matches furthest, at the column where it stops matching. A unit that matches no production at all fails with the text of the unit. The error for a counter outside its bounds names its pattern and the count:
CHANGELOG.md
## 2.1.0
- Added: a dark theme.
- Fixed: the slow search
- Added: export to PDF (TODO: docs).
- Removed: the old menu.
$ tramlines validate --schema changelog.yaml CHANGELOG.md
CHANGELOG.md:4: error[TLMD_57]: `Fixed: the slow search` matches no rule of the block; the nearest fails at column 23: expected `.`, found the end; an item of this kind reads Fixed: the crash on start. (schema changelog, section "${:int}.${:int}.${:int}" block 1)
CHANGELOG.md:5: error[TLMD_159]: the items matching `TODO`: at most 0 are allowed; there is 1 (schema changelog, section "${:int}.${:int}.${:int}" block 1)
CHANGELOG.md:6: error[TLMD_53]: `Removed: the old menu.` matches no rule of the block; declare it or set `units-open` (schema changelog, section "${:int}.${:int}.${:int}" block 1)
3 errors, 0 warnings
[exit 1]
-
To accept units that no rule takes, set
units-open: trueon the block. The rules still check the units that they take, and still apply their counts. -
To check the lines of a code or
htmlblock, setlineson theeachof the block. A code block has one unit, its text. The keylinesis the grammar of that text, asrulesis the grammar of the units of a block. The same productions, guards, and counts apply to each line. tramlines ignores the indent of each line and skips blank lines. A line rule can’t declarelinesorblocks.To accept lines that no rule takes, set
lines-open: truebesidelines. This schema requires a settings file with a[server]line, one to three settings, and no comments:
settings.yaml
line-limit: 60
sections:
- heading: Settings
level: 1
blocks:
- kind: code
lang: ini
each:
lines:
- text: "[server]"
- template: "${name:slug} = ${}"
min: 1
max: 3
example: "port = 8080"
- select-pattern: "^#"
min: 0
max: 0
settings.md
# Settings
```ini
[server]
port = 8080
host = example.org
# TODO
debug: true
```
$ tramlines validate --schema settings.yaml settings.md
settings.md:8: error[TLMD_87]: `# TODO` matches no rule of the fence; declare it or set `lines-open` (schema settings, section "Settings" block 1)
settings.md:8: error[TLMD_159]: the lines matching `^#`: at most 0 are allowed; there is 1 (schema settings, section "Settings" block 1)
settings.md:9: error[TLMD_57]: `debug: true` matches no rule of the fence; the nearest fails at column 6: expected ` = `, found `: true`; a line of this kind reads port = 8080 (schema settings, section "Settings" block 1)
3 errors, 0 warnings
[exit 1]
The indented host line passes, and the comment fails twice: the counter counts it but doesn’t take it, and no other rule takes it.
- Fix the document: remove the comment and write
debug = true.
settings.md
# Settings
```ini
[server]
port = 8080
host = example.org
debug = true
```
$ tramlines validate --schema settings.yaml settings.md
0 errors, 0 warnings
- Order the productions from narrowest to widest. If an earlier production takes the example of a later one, the later one can take nothing, and the schema fails:
tasks.yaml
line-limit: 60
sections:
- heading: Tasks
level: 1
blocks:
- kind: bullet-list
rules:
- template: "${}."
min: 0
example: "Water the plants."
- template: "Urgent: ${}."
min: 0
example: "Urgent: pay the rent."
tasks.md
# Tasks
- Water the plants.
$ tramlines validate --schema tasks.yaml tasks.md
tasks.yaml:11: error[TLMD_58]: the example `Urgent: pay the rent.` is taken by the rule at line 8 first, so this rule takes nothing: order the rules from the narrowest (schema tasks, section "Tasks" block 1)
1 error, 0 warnings
[exit 1]
Move the Urgent rule before the other, and the schema passes.
Troubleshooting
TLMD_53,TLMD_57: no rule takes a unit or a line. Correct the unit or line from the column that the message names, add a rule for it, or setunits-openorlines-open. See TLMD_53 and TLMD_57.TLMD_87: no rule oflinestakes a line. See TLMD_87.TLMD_93: a rule takes or counts fewer units than itsmin. See TLMD_93.TLMD_159: a rule takes or counts more units than itsmax. See TLMD_159.TLMD_92: a rule with aselect-patternlacks amin. Addmin: 0to check the units without requiring any. See TLMD_92.TLMD_91: a unit rule declaresselect-pattern,min, ormaxoutsiderulesandlines. See TLMD_91.TLMD_142: a rule declarestextbesideselect-pattern. A repair oftextrewrites the whole unit and would drop what the guard selected on. See TLMD_142.TLMD_90: a rule declareslinesunder a block that isn’t a code block or anhtmlblock. See TLMD_90.