From 5dd14266f2609faadc3ec60077127a7ef7e8ac8b Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Wed, 2 Sep 2026 12:30:56 +0200 Subject: feat(cli): --help is one screen, not the whole manual It printed 374 lines: every output format, the RFC 4180 quoting rule, the template scope hazard, the publish manifest semantics. All of that is real documentation and all of it is already in colitur(1), installed beside the binary. A help text nobody reads at the terminal is not more helpful than a short one, it is less -- the reader stops looking. Now 42 lines: the commands, the flags most runs need, the year domain, the exit status, and where the rest lives. Everything removed is in the man pages, which is checked rather than assumed -- each dropped section has a counterpart section there. One thing moved rather than went. cli.t pinned `rubrics`' documented field list against what `rubrics` actually prints, because that field count drifted once already and stayed wrong for a whole review round. Deleting the prose would have quietly retired the check, so it now reads the worked example out of man/colitur.1 instead, and test/dune's cram stanza gains the man page as a dep -- a sandbox holds only what that stanza names. Mutation-tested: changing the documented example reddens the suite. The tagline also stopped saying "Roman rite, 1962" -- the OF has shipped since v1.1.0. --- bin/main.ml | 417 +++++++----------------------------------------------------- 1 file changed, 47 insertions(+), 370 deletions(-) (limited to 'bin') diff --git a/bin/main.ml b/bin/main.ml index 7565090..b49a19e 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -1986,381 +1986,58 @@ let publish_report ~rite ~lang ~sigla ~from_y ~to_y ~out ~overlays ~dtstamp ~pru test expectation for no gain. *) let version = "1.2.0" +(* --help is a REMINDER, not a reference. It answers "what are the commands + and which flag do I want", in one screen; every rule, format, hazard and + worked example lives in colitur(1), which is installed beside the binary + and is where a reader who needs more should be sent. + + It used to print 374 lines -- the whole manual, including the RFC 4180 + quoting rule and the template scope hazard. A help text nobody can read + at the terminal is not more helpful than a short one; it is less, because + the reader stops looking. *) let help_text = - {|colitur -- deterministic liturgical calendar engine (Roman rite, 1962) + {|colitur -- deterministic liturgical calendar engine (Roman rite, EF 1962 and OF) usage: - colitur easter Easter, and the movable feasts anchored to it - colitur temporal [--rite ef|of] [--pretty] - [--month N | --date YYYY-MM-DD | --today] - the temporal cycle, one line per day - colitur day the resolved day identity, one line per day - colitur readings the Mass reading citations, one line per day - colitur rubrics the Mass formulary said, one line per day - colitur day|readings|rubrics [] [--year Y] [--rite ef|of] - [--overlay FILE ...] [--lang CODE|FILE] [--raw] [--pretty] - [--month N | --date YYYY-MM-DD | --today] - may be given positionally or as --year (both, if they - agree); rubrics's own --lang/--raw govern its trailing - formulary-name column, --sigla-* stay refused there (see - "naming" below); --pretty and the narrowing flags are described - under "reading it yourself" below - colitur emit --format csv|json|sexp|xml|ics --from Y --to Y [--rite ef|of] - [--overlay FILE ...] [--dtstamp S] [--lang CODE|FILE] [--raw] - render a resolved year range through one of five emitters - colitur table [] [--year Y] --template FILE [--rite ef|of] - [--flavour X] [--overlay FILE ...] [--lang CODE|FILE] [--raw] - colitur render --template FILE [] [--year Y] [--rite ef|of] - [--flavour X] [--overlay FILE ...] [--lang CODE|FILE] [--raw] - compute year Y and render it through FILE, a logic-less - Mustache-family template; table and render are the same - operation, two names (see "rendering" below) - colitur publish --from Y --to Y --out DIR [--rite ef|of] - [--overlay FILE ...] [--prune] [--dtstamp S] - [--lang CODE|FILE] [--raw] - write the static tree: per-year csv/json/xml/ics, one JSON - file per day, the schema and a generated index (see - "publish" below) - colitur lang --list which language files this build can find - colitur lang --dump CODE a language's full key set, in INI form, on stdout - colitur lang --check FILE what a language file is missing, and any typo'd key - colitur config --show every setting, its value and where it came from - 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 - - is a civil year, 1583..9999 inclusive. Each report covers 1 January to -31 December of that year, not a liturgical year. `emit`/`publish` take a -RANGE instead (--from Y --to Y, inclusive) and do not also accept a single ---year or positional year -- deliberate: they may compute many years in one -run, and a third, single-year spelling on top of the range form would add -parsing surface for no real workflow gain. - -reading it yourself: - --pretty lay the rows out as boxes for a person rather - than for awk; accepted on day/readings/rubrics/ - temporal, refused elsewhere. The box format is - for eyes only and may change between releases -- - parse the default rows, which will not. - --month N print only that month, 1..12 - --date YYYY-MM-DD print only that day - --today print only today - - The three narrowing flags are ALTERNATIVES -- naming two is an error, not a - silent win for one. They work in the default format too, and matter most - under --pretty, whose boxes span several lines and so survive no line-wise - grep at all. - - --date and --today NAME a year, so on those two the year may be omitted: - `colitur day --today` is complete. Give one anyway and it must agree, the - same rule a positional year and --year already follow. --month names no - year, so it still needs one. On `temporal`, which refuses --year, --date - and --today may still supply the year: they select a day and merely happen - to determine the year, which --year does not do. - -output formats: - day date weekday season week slug rank colour [+commemoration ...] [name] - 2026-04-05 sunday paschaltide 1 ef-easter-sunday class-1 white - readings date slug | Epistle | Gospel [| name] - 2026-12-25 ef-nativity | Heb 1:1-12 | John 1:1-14 - rubrics date, formulary slug, source, creed, gloria, preface [name] -- TAB-separated - 2026-01-01[TAB]ef-circumcision[TAB]own[TAB]true[TAB]true[TAB]nativity[TAB]In Octava Nativitatis Domini - - A citation contains spaces, so readings uses " | " between its fields while - day stays space-separated; that is why they are separate commands rather - than extra columns. The same reasoning is why the resolved display NAME, - present by default (see "naming" below), is appended as the LAST field - rather than substituted for the slug shown above: a name may itself - contain spaces, and inserting it earlier in the row would break every - fixed-position field that follows it. It is present only when it differs - from the slug already shown -- under --raw, or any language with no entry - for that particular day, the trailing field is simply absent, which is - what makes --raw byte-identical to this program's pre-naming output. - - rubrics prints which Mass is actually said and how that was decided - (source: proper/own/preceding-sunday/common/votive) -- not always the - day's own: a weekday with no proper resumes the preceding Sunday's, a - saint with no proper says his assigned Common -- followed by whether the - Creed is said (RG 475-476), whether the Gloria in excelsis is said - (RG 431-432, deferring to the Breviary's own Te Deum rule, nn. 237-238, - for RG 431(a)), and which preface is said (RG 482-499) -- Creed/Gloria - both "true"/"false", OCaml's own literal, not "yes"/"no" or "1"/"0"; - preface one of nativity/epiphany/lent/holy-cross/easter/ascension/ - sacred-heart/christ-the-king/holy-spirit/trinity/bvm/st-joseph/apostles/ - common/requiem, or "-" when this engine resolves no Mass at all that day - (Good Friday). TAB-separated rather than space or " | ": the resolved - formulary NAME (below) can carry both spaces and punctuation a citation - never does, which rules out either alternative already in use above. - --overlay is accepted (the observed celebration it changes decides the - formulary, the Creed, the Gloria and the preface); --sigla-* are refused - -- this row prints no citation for them to affect. - - --lang/--raw ARE accepted (unlike --sigla-*): `said` (the formulary slug) - is a machine key exactly like `day`'s own slug, and this row resolves it - to a display name under the identical append-only rule day/readings use - -- present as a trailing 8th field only when it differs from the slug - already printed, so --raw (or a language with no entry for that day) is - byte-identical to the seven-field row shown above. - - emit one schema (season, week, slug, rank, colour, subject, names, - citations, commemorations), rendered five ways: csv (RFC 4180, - one header for the whole run), json, sexp, xml (schema/colitur- - v1.xsd) and ics (RFC 5545). --from/--to give a civil-year range, - inclusive. --dtstamp fixes the ics DTSTAMP so two runs over the - same data are byte-identical -- the engine reads no clock. - -rite: - --rite ef|of selects which rite module a command computes against; - default ef (so every invocation written before this flag - existed is unaffected). `of` is the 1970 Missale Romanum - (editio typica tertia, 2002). Accepted on `day`, `readings`, - `rubrics`, `temporal`, `emit`, `table`, `render` and `publish`; - refused, not silently ignored, on `check`/`convert` (operate - on an overlay FILE, not a computed year), `new-overlay` - (prints a static starter, no calendar computation), and - `lang`/`config` (answer naming/config questions orthogonal to - any rite). `easter` is refused too, but PROVEN rite-invariant - rather than merely unbuilt for `of`: EF and OF reckon Easter - on the identical Gregorian computus, so no second value could - ever change the six dates it prints. - - `emit --format csv --rite of` and `publish --rite of` widen - their CSV output with a 17th column, "second", between - "first" and "gospel" -- present, and usually empty, because a - Sunday or solemnity genuinely carries a Second reading (OLM - 1981 Praenotanda n. 66.1) and a feria/feast/memorial does not - (n. 69.1); EF's own 16-column CSV header is unaffected, byte - for byte, because EF's `citations` never contains one. json/ - xml/sexp/ics need no such widening -- each already had a place - for a variable-length reading list. - - A single `--out` directory can hold both rites' own `publish` - trees side by side ("ef/", "of/"), but not safely combined - with --prune on either: publish's own manifest and index.html - describe the WHOLE tree, not one rite's slice of it, so a - later run for the other rite would not know the first run's - files exist and --prune would delete them as stale. Publish a - single rite per --out, or omit --prune when deliberately - layering both. - -overlays: - --overlay FILE (repeatable, ordered; -o) applies a user calendar ON TOP of - the shipped universal one, never instead of it, so local feasts - add to it rather than replacing it. Later files win over earlier - ones, and over the universal calendar, when they name the same - slug. Accepted on `day`, `readings`, `rubrics`, `emit`, `table`, - `render` and `publish` -- `easter` and `temporal` read no - sanctoral data, so the flag is refused there rather than - silently ignored. - - An overlay is applied, NOT validated: colitur's test layers assert - things about the shipped calendar and cannot vouch for a file you - supply. A directive naming a slug that does not exist warns on - stderr and the run continues; a file that fails to load is fatal. - - To write one: - - colitur new-overlay > my-parish.sexp # a commented starter - $EDITOR my-parish.sexp - colitur check my-parish.sexp # parses? every directive hit? - colitur day 2026 --overlay my-parish.sexp - - `check` reports what each directive targets and exits 2 if a file - will not load or a directive matched nothing, so it fits a - 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 - (Nth_weekday (month M) (nth N) (weekday W)) with N negative to - count from the end of the month. - -year: - `day`, `readings`, `rubrics`, `table` and `render` each take a single - civil year, sayable two ways -- positionally (`colitur day - 2026`) or as --year (`colitur day --year 2026`) -- additively: - neither form was removed when the other was added, so every - invocation that worked before still works unchanged. Naming - both is fine as long as they agree (`colitur day 2026 --year - 2026`); naming both with DIFFERENT years is a hard usage error - rather than one silently winning. - - `emit`/`publish` deliberately do NOT gain a --year: they take - --from Y --to Y instead (see the usage block above), and stay - that way even for a single-year run (`--from 2026 --to 2026`) - -- a third spelling meaning exactly the same thing as the two - above would add parsing surface, and a range command's own - natural single-year form is already `--from Y --to Y`, not a - new flag. - -naming: - --lang CODE|FILE applies to `day`, `readings`, `rubrics`, `emit`, - `table`, `render` and `publish`. A CODE (e.g. `la`, `en`) is - looked up as - /CODE.ini; a value containing '/' or ending ".ini" is - read as a literal path instead. Default `la`, overridable by a - config file (see below). An unknown language is a hard ERROR - naming what is available (try `colitur lang --list`), never a - silent fallback to Latin -- a booklet quietly printed in the - wrong language is worse than one that refuses to print. A - language file may declare `fallback = CODE` in its [meta] - section, so a partial translation shows its fallback language - for the keys it does not itself carry, rather than bare slugs. - - --raw restores every command's pre-naming output: `name` (and every - other localised field `emit`'s schema carries) equals the bare - machine slug, exactly as if no language had ever been resolved. - This is not a special case threaded through the naming code -- - it is `colitur lang`'s own identity table, under which every - lookup echoes its key back unchanged. - - --sigla-style CODE|FILE, --sigla-book full|abbr, --sigla-tradition NAME - settings for how a Mass reading CITATION is written -- WHICH - punctuation/abbreviation style, WHICH book form, and WHICH - numbering tradition. Each has a config-key counterpart - (`sigla_style`/`sigla_book`/`sigla_tradition`) resolved with - the identical flag > config > default precedence as --lang, and - each is reported by `colitur config --show` with its source. - Defaults: `sigla_style` the resolved language code (so a - booklet's citations follow its own --lang unless told - otherwise), `sigla_book` `abbr`, `sigla_tradition` `vulgate`. - An unrecognised `--sigla-book` is a hard ERROR (want `full` or - `abbr`), the same discipline as an unknown `--lang`; an - unrecognised `--sigla-tradition` is not -- it degrades to the - Vulgate with a stderr warning, because asking for a renumbering - is optional the way asking for a language is not. `--raw` - bypasses all of it and emits each citation exactly as stored, - byte for byte, so it stays usable for diffing and does not - depend on the citation parser being correct. - - colitur lang --list which language files this build can find, and - each one's own declared fallback, if any. - colitur lang --dump CODE the named language's full key set, in INI - form, on stdout -- a starting point for a new - translation, or a way to diff two versions of - one. - colitur lang --check FILE what a language file is MISSING (a real slug - with no entry) and, separately, any entry - naming a slug that does not exist at all (a - typo, silently dead otherwise) -- exits 1 if - anything is unknown, so it fits a Makefile or - a pre-commit hook. - colitur config --show every effective setting -- lang, overlay, - template, format, sigla_style, sigla_book, - sigla_tradition -- its resolved value, and - where it came from: `flag`, `config` or - `default`. See colitur-config(5) for the - config file's location and full precedence. - -rendering: - --template FILE (required on `table`/`render`) is a logic-less Mustache- - family template: {{placeholder}}, {{#section}}...{{/section}}, - {{^inverted}}...{{/inverted}}, {{!comment}} -- nothing else. It is - DATA, never a program: no partials, no lambdas, no expression - evaluation, no filesystem or process access, and no "raw" or - triple-brace form that could opt out of escaping. The value it - renders against is the same schema `emit` uses (season, week, - slug, rank, colour, subject, names, citations, commemorations), - reshaped into a booklet (`days`) and a month grid (`weeks`, with - padding cells for the leading/trailing blanks); see - colitur-templates(5) for the full field list, the syntax, and - the one remaining scope hazard (`num` -- both a month and a week - carry it, and only the innermost one is ever meant). `name` is a - plain resolved string, not a lang-keyed object, so it carries no - equivalent hazard of its own. - - --flavour X selects how interpolated VALUES are escaped (never the - template's own literal markup, which is the author's). One of: - - latex typst groff html xml ics none - - Inferred from --template's extension when --flavour is omitted: - - .tex -> latex - .typ -> typst - .ms .mom .me -> groff - .html .htm -> html - .xml -> xml - .ics -> ics - .md .adoc .txt -> none (no metacharacters are escaped; - Markdown/AsciiDoc/plain text have no fixed - metacharacter set, so escaping them here - would produce worse output than leaving - them alone) - - An extension colitur does not recognise is a hard ERROR naming - the seven flavours above, never a silent fallback to `none`: - guessing wrong produces output that looks fine until the - metacharacters it silently failed to escape show up. - - `table` and `render` are the SAME operation under two names. The design - this project followed originally sketched `compute | render` as a - Unix pipe, with `render` reading a serialised view back from - stdin. That is deliberately not built: honouring the pipe would - need a JSON *parser*, purely so this program could re-read a view - it had just serialised itself -- a second hand-rolled component, - and a second place for the published contract to drift, for no - benefit over calling the view builder directly in the same - process. There is therefore no stdin-fed `render`; `colitur emit - --format json | jq` still composes for real pipe use, because - that JSON is the OUTPUT, never something colitur itself parses - back in. - -publish: - --out DIR (required) writes the static tree that IS this program's API: - any web server or git repo can serve it as-is, and nothing runs - at request time. - - /.{json,csv,xml,ics} one civil year, all days - ///
.json one file per day - schema/day-v1.json the published JSON contract - index.html a generated index page - .colitur-manifest every path this run wrote - - is "ef" or "of", selected by --rite exactly as on every - other command (default ef) -- see "rite" above for what a - single --out directory holding both rites' own trees needs. - - Deterministic: publishing the same --from/--to range twice - produces a byte-identical tree (--dtstamp behaves exactly as on - `emit`). That is what makes publishing into a git repo safe -- - `git status` shows only real change, and you review an actual - diff before pushing. - - Non-destructive: publish writes only files it owns, and records - every one in .colitur-manifest. A file you put in the output - directory yourself is never in that manifest, so it is never - touched, whether or not --prune is given. --prune additionally - removes manifest entries from a PREVIOUS run that this run did - not rewrite (e.g. an earlier year's per-day files, when you - publish a different range into the same directory) -- never - anything the manifest does not name. - -environment: - COLITUR_DATA_DIR - Read the calendar data from this directory instead of the - installed (/share/colitur/ef) or build-tree location. - If it is set and holds no sanctoral.sexp, colitur exits 2 rather - than silently falling back to a different copy of the data. - -exit status: - 0 success - 2 bad usage, year out of range, or the calendar data could not be read + colitur easter Easter and the feasts anchored to it + colitur temporal the temporal cycle, one line per day + colitur day the resolved day, one line per day + colitur readings the Mass reading citations + colitur rubrics the Mass said, Creed, Gloria, preface + colitur emit --format FMT --from Y --to Y csv|json|sexp|xml|ics + colitur table|render --template FILE --year Y render through a template + colitur publish --from Y --to Y --out DIR write a static tree + colitur check FILE ... load an overlay and say what it does + colitur convert FILE.ini INI overlay -> S-expression, on stdout + colitur new-overlay print a starter overlay to stdout + colitur lang --list|--dump CODE|--check FILE + colitur config --show every setting and where it came from + +common flags: + --rite ef|of which form (default ef) + --year Y the year, as an alternative to the positional one + --month N print only that month; --date YYYY-MM-DD, --today + --pretty boxes for a person rather than rows for awk + --lang CODE|FILE name things in this language; --raw for bare slugs + --overlay FILE apply a local calendar (repeatable, ordered) + -h, --help this help -V, --version the version + + is 1583..9999 and means 1 January to 31 December, not a liturgical +year. emit and publish take --from/--to instead. A flag a command cannot +honour is refused, never silently ignored. + +exit status: 0 success; 2 bad usage, year out of range, or unreadable data. Reading references only (e.g. "Jn 3:16"); never scripture text. -See colitur(1) for the full description and the sources it computes against, -colitur-overlay(5) for the overlay file format in full, -colitur-templates(5) for the template format in full -- the syntax, the -remaining scope hazard, the seven flavours' escaping, and the full view-model -field reference -- and colitur-config(5) for the config file's location and -precedence in full.|} + +Full documentation -- every output format, the overlay and template file +formats, citation naming and numbering, the sources this computes against +and its stated limits: + + man colitur the program, in full + man 5 colitur-overlay the overlay format + man 5 colitur-templates the template format + man 5 colitur-config the config file|} let print_help () = print_endline help_text; -- cgit v1.3