aboutsummaryrefslogtreecommitdiff
path: root/bin/main.ml
diff options
context:
space:
mode:
Diffstat (limited to 'bin/main.ml')
-rw-r--r--bin/main.ml409
1 files changed, 43 insertions, 366 deletions
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 <year> Easter, and the movable feasts anchored to it
- colitur temporal <year> [--rite ef|of] [--pretty]
- [--month N | --date YYYY-MM-DD | --today]
- the temporal cycle, one line per day
- colitur day <year> the resolved day identity, one line per day
- colitur readings <year> the Mass reading citations, one line per day
- colitur rubrics <year> the Mass formulary said, one line per day
- colitur day|readings|rubrics [<year>] [--year Y] [--rite ef|of]
- [--overlay FILE ...] [--lang CODE|FILE] [--raw] [--pretty]
- [--month N | --date YYYY-MM-DD | --today]
- <year> 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>] [--year Y] --template FILE [--rite ef|of]
- [--flavour X] [--overlay FILE ...] [--lang CODE|FILE] [--raw]
- colitur render --template FILE [<year>] [--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
-
-<year> 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.
+ colitur easter <year> Easter and the feasts anchored to it
+ colitur temporal <year> the temporal cycle, one line per day
+ colitur day <year> the resolved day, one line per day
+ colitur readings <year> the Mass reading citations
+ colitur rubrics <year> 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
- 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.
+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
-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.
+<year> 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.
- `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.
+exit status: 0 success; 2 bad usage, year out of range, or unreadable data.
-naming:
- --lang CODE|FILE applies to `day`, `readings`, `rubrics`, `emit`,
- `table`, `render` and `publish`. A CODE (e.g. `la`, `en`) is
- looked up as
- <lang-dir>/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.
-
- <rite>/<year>.{json,csv,xml,ics} one civil year, all days
- <rite>/<year>/<mm>/<dd>.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
-
- <rite> 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 (<prefix>/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.
+Reading references only (e.g. "Jn 3:16"); never scripture text.
-exit status:
- 0 success
- 2 bad usage, year out of range, or the calendar data could not be read
+Full documentation -- every output format, the overlay and template file
+formats, citation naming and numbering, the sources this computes against
+and its stated limits:
-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.|}
+ 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;