Coming in Podlite 3.0: a specification that tests itself

Podlite is a semantic language: a document says what its parts are, not how they look. Its specification is prose, and when an implementation reads it differently, you find out from a document that renders wrong. Podlite 3.0, due in mid-November, gives the rules tests, written in Podlite and placed right in the specification text, each next to the rule it checks. The tests are semantic too: they check the structure a parser makes of a document, its blocks with their names and configuration, and no output format, HTML or any other.

The same blocks check any document, not only the specification. A set of tests becomes a schema of your own, written from your requirements and extended as they change: say, every invoice in a knowledge base carries a number. It can lint one document or a whole group of them, and it gives an agent that edits the files a way to keep the documents and the structure of the knowledge base intact.

Rules that carry their own tests

A test is a block. It may hold a piece of source, the fixture, and it holds one or more assertions about what a parser makes of it:

=begin test :id<heading-carries-its-level>
=begin fixture
=head2 Overview
=end fixture
=for assert :caption('a second-level heading is recognised as such')
head2
=end test

Each assertion carries a selector. Without :absent, it holds when the selector matches at least one block; with :absent, when it matches none. So "every invoice has a number" is written as "no invoice lacks one":

=for assert :absent
Invoice[ :!?number ]

The specification now carries around ninety of these tests, kept next to the rules they check. On podlite.org/v3.0/specification a test shows as a label beside its rule and opens in place. One sentence settles which side wins: "If a test of this specification differs from the rule it checks, the rule governs."

A rule of the 3.0 specification with its test opened: the source, then what each selector must and must not find

Assertions carry ordinary selectors, and the same selectors work with podlite query on any document, for example in a build script.

Selectors gain predicates

A pattern may now carry a predicate in square brackets. It tests the configuration in effect for the block, including options from =config and =set. The universal pattern * matches a block of any name, so a selection can go by configuration alone:

=include file:./guide.podlite | *[ :platform<arm> ]

The operators cover the usual questions: is the attribute set, is it missing, does it equal a value, is it one of several. A selected block keeps all of its content.

What else changes

3.0 also tightens the rules for =include, tables and attribute values, and notification blocks now use :notice. Some of these changes alter how existing documents read from 3.0 on; the change log lists them all.

The scope of the release is tracked in podlite-specs#42, and questions go to Discussions. If an example is wrong, a rule reads two ways, or a test checks the wrong thing, now is the time to say so.

Funding ↗ pays for the time that goes into Podlite.