diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-18 13:55:18 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-18 13:55:18 +0200 |
| commit | 36daf47dde9b0c16dacef31163ea74effdd5e7f3 (patch) | |
| tree | fa7e5bdd618902fd1bcfc34318395a181f531254 /bin/main.ml | |
| parent | 150f8c550d2f9ebbac61d195eb342eb2b2ec6b95 (diff) | |
| download | colitur-36daf47dde9b0c16dacef31163ea74effdd5e7f3.tar.gz colitur-36daf47dde9b0c16dacef31163ea74effdd5e7f3.zip | |
feat(overlay): a flat INI front end, which verifies its own output
A convenience format for calendars that add a few local feasts and drop one
or two universal entries. Section names are slugs, a [overlay] section carries
the id, and status/subject/layer default so the common case -- an ordinary
local saint's feast -- says only what distinguishes it.
It is a FRONT DOOR, not a second data model. It parses to exactly the
Overlay.t the S-expression form parses to, and everything downstream is the
same code on the same values; a test asserts an INI overlay and its
hand-written sexp equivalent produce identical Overlay.t values. It is also
deliberately less expressive -- Add, Suppress and single-field Edit only --
and refuses Replace, multi-field edits and citation edits BY NAME rather than
dropping them silently. Anything it cannot say is a reason to write sexp.
Little of this is new machinery: tools/bootstrap_sanctoral.ml has parsed INI
and mapped it to celebrations since the sanctoral was bootstrapped from
lectio. The dates needed extending, since that mapping handled only MM-DD;
the flat forms are easter+N/easter-N and mon/day/nth, with nth negative to
count from the end.
`colitur convert` is a separate step rather than --overlay sniffing the
extension, so the author can read what their INI became. When a date form was
mistyped, "what did the engine actually get" is the question, and an invisible
transpile cannot answer it.
The conversion verifies its own output: the emitted text is parsed back with
the same function that loads an overlay and must equal what the INI denoted,
or nothing is written. That is the point of the module. A transpiler emitting
valid-but-wrong sexp is the failure a convenience format invites, and `colitur
check` could never catch it -- the output would parse cleanly and mean
something else.
That check was WRONG on the first attempt, in exactly the way it exists to
prevent. It re-serialised the parsed value instead of parsing the text being
returned, so it verified t -> sexp -> t, which is true by construction and
proves nothing. Found by mutation: corrupting the renderer to emit a different
overlay id sailed through and exited 0. It now parses the returned text, the
mutation is caught with exit 2, and two tests fail under it where none did
before.
Diffstat (limited to 'bin/main.ml')
| -rw-r--r-- | bin/main.ml | 38 |
1 files changed, 38 insertions, 0 deletions
diff --git a/bin/main.ml b/bin/main.ml index b997cbe..9b7c8a7 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -389,6 +389,7 @@ usage: colitur readings <year> the Mass reading citations, one line per day colitur day|readings <year> --overlay FILE [--overlay FILE ...] colitur new-overlay print a starter overlay file to stdout + colitur convert FILE.ini flat INI overlay -> S-expression, on stdout colitur check FILE ... load an overlay, say what it does, exit 2 if not colitur -h, --help this help colitur -V, --version print the version and exit @@ -432,6 +433,10 @@ overlays: Makefile or a pre-commit hook. It does not check a calendar against the rubrics -- nothing here can. + A flatter INI form exists for simple calendars, converted with + `colitur convert`, which verifies its own output before emitting + it. See colitur-overlay(5) for both forms. + In an added celebration, `citations` and `layer` may be omitted: they default to empty and to the overlay's own id. Dates may be (Fixed (month M) (day D)), (Easter_offset N) signed, or @@ -582,6 +587,36 @@ let check_report paths = paths; exit (if !ok then 0 else 2) +(* `colitur convert FILE.ini` -- the flat INI form to the S-expression one, + on stdout for redirection. + + A separate step rather than teaching --overlay to sniff the extension, and + deliberately so: the user gets to SEE what their INI became. When a date + form was mistyped or an edit silently dropped, "what did the engine + actually get" is the question, and an invisible transpile cannot answer it. + + {!Overlay_ini.convert} verifies its own output before returning it -- see + that function's own comment. Nothing is written if the round trip fails. *) +let convert_report path = + match + (try Ok (In_channel.with_open_text path In_channel.input_all) + with Sys_error e -> Error e) + with + | Error e -> + Printf.eprintf "colitur: %s\n" e; + exit 2 + | Ok text -> ( + match + Colitur_kernel.Overlay_ini.convert ~rank_of_string:Rite_ef.Vocab_ef.rank_of_string + ~rank_to_sexp:Rite_ef.Vocab_ef.sexp_of_rank ~rank_of_sexp:Rite_ef.Vocab_ef.rank_of_sexp text + with + | Error e -> + Printf.eprintf "colitur: %s: %s\n" path e; + exit 2 + | Ok sexp -> + print_string sexp; + exit 0) + (* `colitur new-overlay` -- a starter file on stdout, for redirection. Deliberately printed rather than written: the user picks the path, and a command that creates files where it likes is a worse citizen. Every value is @@ -660,6 +695,9 @@ let () = | "check" :: (_ :: _ as files) -> reject_overlays_for "check" overlays; check_report files + | [ "convert"; path ] -> + reject_overlays_for "convert" overlays; + convert_report path | [ "new-overlay" ] -> reject_overlays_for "new-overlay" overlays; print_string new_overlay_template; |
