.TH COLITUR 1 "2026" "colitur" "User Commands" .SH NAME colitur \- deterministic liturgical calendar and lectionary engine (Roman rite, 1962) .SH SYNOPSIS .B colitur .BR easter | temporal | day | readings | rubrics .I YEAR .br .B colitur .BR day | readings .I YEAR .RB [ \-\-overlay " FILE" " ...]" .RB [ \-\-lang " CODE\(brFILE" ] .RB [ \-\-sigla\-style " CODE\(brFILE" ] .RB [ \-\-sigla\-book " full\(brabbr" ] .RB [ \-\-sigla\-tradition " NAME" ] .RB [ \-\-raw ] .br .B colitur .B rubrics .I YEAR .RB [ \-\-overlay " FILE" " ...]" .br .B colitur .B emit .BI \-\-format " FMT" .BI \-\-from " YEAR" .BI \-\-to " YEAR" .RB [ \-\-overlay " FILE" " ...]" .RB [ \-\-dtstamp " STAMP" ] .RB [ \-\-lang " CODE\(brFILE" ] .RB [ \-\-sigla\-style " CODE\(brFILE" ] .RB [ \-\-sigla\-book " full\(brabbr" ] .RB [ \-\-sigla\-tradition " NAME" ] .RB [ \-\-raw ] .br .B colitur .BR table | render .BI \-\-year " YEAR" .BI \-\-template " FILE" .RB [ \-\-flavour " FLAVOUR" ] .RB [ \-\-overlay " FILE" " ...]" .RB [ \-\-lang " CODE\(brFILE" ] .RB [ \-\-sigla\-style " CODE\(brFILE" ] .RB [ \-\-sigla\-book " full\(brabbr" ] .RB [ \-\-sigla\-tradition " NAME" ] .RB [ \-\-raw ] .br .B colitur .B publish .BI \-\-from " YEAR" .BI \-\-to " YEAR" .BI \-\-out " DIR" .RB [ \-\-overlay " FILE" " ...]" .RB [ \-\-prune ] .RB [ \-\-dtstamp " STAMP" ] .RB [ \-\-lang " CODE\(brFILE" ] .RB [ \-\-sigla\-style " CODE\(brFILE" ] .RB [ \-\-sigla\-book " full\(brabbr" ] .RB [ \-\-sigla\-tradition " NAME" ] .RB [ \-\-raw ] .br .B colitur .B lang .RB { \-\-list | \-\-dump " CODE" | \-\-check " FILE" } .br .B colitur .B config .B \-\-show .br .B colitur .BR \-h | \-\-help .SH DESCRIPTION .B colitur computes the liturgical calendar of the 1962 Roman Missal \(em the Extraordinary Form \(em and the Mass reading citations for every day, entirely offline and without a network. Its name is .I computus liturgicus crossed with the Latin .IR colitur , "He is worshipped". .PP For each day it resolves the season, the week, the observed celebration with its rank and colour, any commemorations, any transfers, and the day's Epistle and Gospel. It emits reading .I references only \(em .RB \(lq "Jn 3:16" \(rq \(em and never scripture text. .PP The engine is total and deterministic over the whole domain .BR "1583..9999" . It reads no clock, draws no randomness, and given the same data produces the same answer for any year in range. Years outside the domain are refused at the boundary rather than approximated. .PP Each report covers 1 January to 31 December of the civil .IR YEAR . That is deliberately not a liturgical year, which is Advent\-anchored and straddles two civil years; .B colitur resolves both liturgical years that touch the requested civil one and prints the civil slice. .SH COMMANDS .TP .BI easter " YEAR" Easter and the movable feasts anchored to it \(em Ash Wednesday, Palm Sunday, Ascension, Pentecost, Corpus Christi \(em one per line, as .RI \(lq name " " date \(rq. .TP .BI temporal " YEAR" The temporal cycle alone, one line per day, before the sanctoral calendar is resolved against it. Chiefly useful for inspecting season and week boundaries in isolation. .TP .BI day " YEAR" The resolved day identity, one line per day: the temporal cycle and the sanctoral calendar reconciled by the rite's own rules of precedence, occurrence, commemoration and transfer. .TP .BI readings " YEAR" The Mass reading citations, one line per day. .TP .BI rubrics " YEAR" Four rubrics of the Mass, one line per day: which formulary is actually said \(em 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, and RG 78/309(a)'s votive Saturday Mass of Our Lady is said in place of an unoccupied office's own \(em 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). See .B OUTPUT FORMAT below. .TP .B emit Render a civil\-year range through one of five emitters \(em .BR csv ", " json ", " sexp ", " xml " or " ics . See .B EMIT below. .TP .BR table | render Compute one civil year and render it through a user\-supplied template, in one process. .B table and .B render are the same operation under two names \(em see .B RENDERING below for why there is no separate, stdin\-fed .B render . .TP .B publish Write the static tree that .I is this program's API: a civil\-year range rendered once, as files, so any web server or git repository can serve it and nothing runs at request time. See .B PUBLISH below. .TP .BI convert " FILE" .ini Convert a flat INI overlay to the S\-expression form, on standard output. The conversion verifies its own output before emitting it: the generated text is parsed back with the same function that loads an overlay, and must mean exactly what the INI said. A separate step rather than teaching .B \-\-overlay to sniff the extension, so you can see what your INI became. See .BR colitur\-overlay (5). .TP .B new\-overlay Print a starter overlay file to standard output, for redirection. Every value in it is a placeholder that will appear in .B day output if left unedited, so a half\-finished overlay is visible rather than silently inert. .TP .BI check " FILE" ... Load each overlay, apply it to the shipped calendar, and report what it does: the directive counts, the slug each targets, and any directive that matched nothing. Exits .B 2 if a file fails to load or a directive found no target, so it composes into a Makefile or a pre\-commit hook. It answers three narrow questions \(em does the file parse, does every directive find its target, and what does the merged result contain. It does .I not validate a calendar against the rubrics, and cannot: see .B OVERLAYS below. .TP .B lang \-\-list List the language files this build can find, and each one's own declared .BR fallback , if any. .TP .BI lang " " \-\-dump " CODE" Print the named language's full key set, in INI form, to standard output \(em a starting point for a new translation, or a way to diff two versions of one. See .B NAMING below. .TP .BI lang " " \-\-check " FILE" Report what a language file is .B MISSING (a real slug the engine can produce with no entry for it) and, separately, any entry naming a slug that does not exist at all \(em a typo, which would otherwise be silently dead. Exits .B 1 if anything is unknown, so it composes into a Makefile or a pre\-commit hook, the same way .B check does for an overlay. See .B NAMING below. .TP .B config \-\-show Print every effective setting \(em .IR lang ", " overlay ", " template ", " format ", " sigla_style ", " .IR sigla_book " and " sigla_tradition \(em its resolved value, and where it came from: .BR flag ", " config " or " default . See .BR colitur\-config (5) for the config file's location and precedence in full. .TP .BI \-\-overlay " FILE" Apply a user calendar on top of the shipped one. Repeatable and ordered; .BR day ", " readings ", " rubrics ", " emit ", " table ", " render " and " publish only. See .B OVERLAYS below. .TP .BI \-\-format " FMT" .RB ( "colitur emit" " only)" One of .BR csv ", " json ", " sexp ", " xml " or " ics . Required. See .B EMIT below. .TP .BI \-\-from " YEAR" ", " \-\-to " YEAR" .RB ( "colitur emit" " and " "colitur publish" " only)" The inclusive civil\-year range to render, each .B 1583..9999 as elsewhere. .I FROM must not be after .IR TO . Both required. .TP .BI \-\-dtstamp " STAMP" .RB ( "colitur emit \-\-format ics" " and " "colitur publish" " only)" Fix the feed's own DTSTAMP instead of the default .IR YYYY0101T000000Z , where .I YYYY is the emitted year. Never a clock read either way \(em see .B EMIT below. .TP .BI \-\-out " DIR" .RB ( "colitur publish" " only)" The directory to write the static tree into. Created if it does not exist. Required. See .B PUBLISH below. .TP .B \-\-prune .RB ( "colitur publish" " only)" Remove files a previous .B publish run into the same .B \-\-out wrote that this run did not rewrite. Never removes a file that is not recorded in .IR out /.colitur\-manifest , regardless of this flag. See .B PUBLISH below. .TP .BI \-\-year " YEAR" .RB ( "colitur table" " and " "colitur render" " only)" The civil year to compute, .B 1583..9999 as elsewhere. Required. .TP .BI \-\-template " FILE" .RB ( "colitur table" " and " "colitur render" " only)" The template file to render the year through. Required. See .B RENDERING below. .TP .BI \-\-flavour " FLAVOUR" .RB ( "colitur table" " and " "colitur render" " only)" One of .BR latex ", " typst ", " groff ", " html ", " xml ", " ics " or " none . Overrides the flavour that would otherwise be inferred from .BR \-\-template 's own extension. See .B RENDERING below. .TP .BI \-\-lang " CODE\(brFILE" .RB ( day ", " readings ", " emit ", " table ", " render " and " publish " only)" Resolve display names through this language instead of the default. .I CODE (e.g. .BR la ", " en ) is looked up as a file in the installed language directory; a value containing .B / or ending .B .ini is read as a literal path instead. Default .BR la , overridable by a config file. An unknown language is a hard error naming what is available, never a silent fallback to Latin. See .B NAMING below. .TP .BI \-\-sigla\-style " CODE\(brFILE" .RB ( day ", " readings ", " emit ", " table ", " render " and " publish " only)" Which punctuation/abbreviation convention to render a Mass reading citation in \(em looked up exactly as .B \-\-lang is (a .I CODE against the installed language directory, or a literal path). Default the resolved .BR \-\-lang , overridable by a config file. See .B SIGLA below. .TP .BI \-\-sigla\-book " full\(brabbr" .RB ( day ", " readings ", " emit ", " table ", " render " and " publish " only)" Which form of the book name a citation uses. Default .BR abbr , overridable by a config file. An unrecognised value is a hard error, the same discipline an unknown .B \-\-lang gets. See .B SIGLA below. .TP .BI \-\-sigla\-tradition " NAME" .RB ( day ", " readings ", " emit ", " table ", " render " and " publish " only)" Which numbering tradition a citation's book DENOTES \(em a section name in .BR lang/traditions.ini . Default .BR vulgate , overridable by a config file. Unlike .B \-\-sigla\-book and .BR \-\-lang , an unrecognised value is .I not fatal: it degrades to .B vulgate with a warning on standard error, because asking for a renumbering is optional the way asking for a language is not. See .B SIGLA below. .TP .B \-\-raw .RB ( day ", " readings ", " emit ", " table ", " render " and " publish " only)" Restore the pre\-naming output: every display name equals its bare machine slug, and every reading citation is emitted .B verbatim \(em exactly as stored, bypassing the parser, the style, the book form and the tradition entirely. See .B NAMING and .B SIGLA below. .TP .BR \-h ", " \-\-help Print a usage summary to standard output and exit 0. .TP .BR \-V ", " \-\-version Print the version and exit 0. .SH OUTPUT FORMAT .SS day .RS .nf date weekday season week slug rank colour [+commemoration ...] [name] .fi .RE .PP Space\-separated, with one .BI + slug suffix per admitted commemoration. A .B \- in the week column means the day carries no week number. The resolved display .I name (see .B NAMING below) is appended LAST, after any commemorations, rather than substituted for .I slug above: a name may itself contain spaces, and inserting it earlier in the row would break every fixed\-position field that follows it \(em the same mechanical reason .B readings is a separate command rather than extra columns on .B day (see below). It is present only when it differs from .I slug already shown; under .BR \-\-raw , or a language with no entry for that particular day, the trailing field is simply absent \(em not merely empty \(em which is what makes .B \-\-raw byte\-identical to this program's pre\-naming output. .RS .nf 2026\-04\-05 sunday paschaltide 1 ef\-easter\-sunday class\-1 white Dominica Resurrectionis 2026\-11\-02 monday time\-after\-pentecost 23 commemoration\-of\-all\-souls class\-1 black In Commemoratione Omnium Fidelium Defunctorum 2057\-03\-26 monday lent 3 annunciation\-of\-the\-blessed\-virgin\-mary class\-1 white +ef\-lent\-3\-monday In Annuntiatione B. Mariae Virg. .fi .RE .SS readings .RS .nf date slug | Epistle | Gospel [| name] .fi .RE .PP A reading citation contains spaces and commas, so this report separates its fields with .RB \(lq " | " \(rq where .B day stays space\-separated. That is the reason the citations are a separate command rather than extra columns on .BR day : appended there, no field number could recover where the Epistle ended. A .B \- in either citation field means none was resolved. A citation's book names, punctuation and numbering are all configurable \(em see .B SIGLA below. The resolved display .I name is appended as a fourth, .RB \(lq " | " \(rq \-delimited field on the same "present only when it differs from .IR slug "" \(cq terms as .BR day 's own trailing field, above. .RS .nf 2026\-12\-25 ef\-nativity | Heb 1:1\-12 | John 1:1\-14 | In Nativitate Domini 2038\-03\-06 sts\-felicitas\-perpetua | Ecclus 51:1\-8, 12 | Matt 13:44\-52 | Ss. Perpetuae et Felicitatis Mm. .fi .RE .SS rubrics .RS .nf date [TAB] formulary\-slug [TAB] source [TAB] creed [TAB] gloria [TAB] preface .fi .RE .PP The day's own Mass formulary (which slug's Mass is actually said, and how that was decided), followed by whether the Creed is said (RG 475\-476), whether the Gloria in excelsis is said (RG 431\-432), and which preface is said (RG 482\-499). .B rubrics separates its fields with a literal TAB \(em not a plain space like .B day or .RB \(lq " | " \(rq like .B readings \(em because a resolved formulary NAME (a column a later version may add, not either of these) can carry both spaces and punctuation a citation never does, which rules out either separator already in use above. A separate command for the identical mechanical reason .B day is separate from .BR readings : .BR day 's own row is fixed\-width space\-separated with a variable\-length .RI + slug tail, so appending anything with its own internal whitespace there would leave it unsplittable by field number. .I source is one of .BR proper ", " own ", " preceding\-sunday ", " common " or " votive . .I creed and .I gloria are each .B true or .B false (OCaml's own literal, not .RB \(lq yes / no \(rq or .RB \(lq 1/0 \(rq : this row has no other boolean field to be consistent with). A day with no Mass at all for a rite that has not implemented a rule reads .B false outright \(em it is a decision, never a third \(lqunknown\(rq state. .I preface is one of .BR nativity ", " epiphany ", " lent ", " holy\-cross ", " easter ", " .BR ascension ", " sacred\-heart ", " christ\-the\-king ", " holy\-spirit ", " .BR trinity ", " bvm ", " st\-joseph ", " apostles ", " common " or " requiem , or a literal .B \- when this engine resolves no Mass at all that day (Good Friday) \(em unlike .I creed / gloria , .I preface is a genuine option, so .B \- here can also mean a rite that has not implemented the rule at all. .RS .nf 2026\-01\-01 [TAB] ef\-circumcision [TAB] own [TAB] true [TAB] true [TAB] nativity 2038\-03\-08 [TAB] john\-of\-god [TAB] proper [TAB] false [TAB] true [TAB] common 2025\-12\-01 [TAB] ef\-advent\-sunday\-1 [TAB] preceding\-sunday [TAB] false [TAB] false [TAB] common .fi .RE .PP Unlike .B day and .BR readings , .B rubrics resolves no display name and no citation, so it takes none of .BR \-\-lang ", " \-\-raw or any .B \-\-sigla\-* flag \(em refused rather than silently ignored, the same discipline .B \-\-overlay gets on .B easter and .BR temporal . .PP All three reports are one line per day and ordered by date, so they compose with .BR grep (1), .BR awk (1) and .BR join (1) in the ordinary way. Pass .B \-\-raw to restore the pre\-naming byte\-exact output of .B day or .B readings \(em no trailing field at all \(em for a script written against either before the naming feature existed; .B rubrics is already in that form and has no .B \-\-raw of its own to pass. .SH EMIT .BI "colitur emit " \-\-format " FMT " \-\-from " YEAR " \-\-to " YEAR" renders the same resolved day \(em season, week, slug, rank, colour, subject, the resolved display name in the active language, citations, commemorations \(em through one of five emitters, for every day in the inclusive civil\-year range .IR FROM .. TO . Every emitter consumes one shared view of the data, so all five describe exactly the same fields. .TP .B csv RFC 4180. One header row for the whole run, not one per year, so a multi\-year range still has exactly one header and .BR wc (1) or .B "awk 'NR>1'" behave as expected. .RS .nf .B colitur emit \-\-format csv \-\-from 2026 \-\-to 2026 | head \-2 date,rite,season,season_name,week,slug,name,weekday,rank,rank_name,colour,colour_name,subject,first,gospel,comms 2026\-01\-01,ef,christmastide,Tempus Nativitatis,,ef\-circumcision,In Octava Nativitatis Domini,Feria V,class\-1,I classis,white,albus,temporal,Titus 2:11\-15,Luke 2:21, .fi .RE .PP .IR name ", " season_name ", " rank_name " and " colour_name are the resolved display strings in the active language (see .B NAMING below); .IR slug ", " season ", " rank " and " colour stay the kernel's own unlocalised keys, unaffected by .BR \-\-lang / \-\-raw , so a script can key off the stable machine value while a human reads the localised one beside it. .TP .B json One JSON object per requested year, concatenated. Shape pinned by .IR schema/day\-v1.json . .RS .nf .B colitur emit \-\-format json \-\-from 2026 \-\-to 2026 | head \-c 40 {"rite":"ef","year":"2026","months":[{... .fi .RE .TP .B sexp One S\-expression per day, one per line \(em the same .I Liturgical_day.t shape used internally, printed with .IR sexplib "'s " to_string_hum . .TP .B xml Element\-per\-field, one .I document per requested year, concatenated. Attributes carry identity only (rite, year, date); everything else is an element. Shape pinned by .IR schema/colitur\-v1.xsd , checked by .B make check\-schema when .BR xmllint (1) is installed. .TP .B ics RFC 5545. One .I VCALENDAR per requested year, concatenated, one all\-day .I VEVENT per day. Lines are folded at 75 octets and end .RI ( CRLF ), matching the protocol exactly \(em .RB \(lq " cat \-A " \(rq on the output shows .B ^M$ at each line end. .RS .nf .B colitur emit \-\-format ics \-\-from 2026 \-\-to 2026 | head \-1 BEGIN:VCALENDAR .fi .RE .PP .B \-\-dtstamp fixes the feed's own .I DTSTAMP field, which RFC 5545 requires on every event. Without it the value defaults to .I YYYY0101T000000Z for the emitted year \(em a fixed value, not a clock read \(em so two .B emit \-\-format ics runs over identical data are byte\-identical, which matters for a reproducible build or a diffable published calendar file. Nothing in the .B emit path reads the wall clock, for any format. .PP .BR \-\-overlay is accepted exactly as on .B day and .BR readings : applied on top of the shipped calendar, in order, before the range is rendered. See .B OVERLAYS below. .SH RENDERING .BI "colitur table " \-\-year " YEAR " \-\-template " FILE" and .BI "colitur render " \-\-template " FILE " \-\-year " YEAR" are the .I same operation under two names: compute the resolved year, shape it into the same view .B emit uses, and render it through .I FILE in one process. Both accept .BR \-\-flavour " and " \-\-overlay identically. .SS Why there is no stdin\-fed render The design this project followed originally sketched a Unix pipe, .BR "compute | render" , with .B render reading a serialised view back from standard input. That is deliberately .I not built. Honouring the pipe would require a JSON .I parser inside .B colitur \(em a second hand\-rolled component, purely so this program could read back a view it had just serialised itself, and a second place for the published output schema to drift out of step with what the parser actually accepts. That is a real cost for no benefit over calling the same view builder directly in the same process, which is what .B table and .B render both do. .PP Unix composition is not abandoned, only narrowed to where it is cheap and honest: .B "colitur emit \-\-format json | jq" still composes fine, because that JSON is the .I output of the pipeline, never something .B colitur itself has to parse back in. .SS Templates .I FILE is a deliberately logic\-less, Mustache\-family template: it is .I data, never a program. The only constructs are .BR {{placeholder}} , .BR {{#section}}...{{/section}} , .BR {{^inverted}}...{{/inverted}} and .BR {{!comment}} . There are no partials, no lambdas, no expression evaluation, no arithmetic, and no filesystem or process access from inside a template. There is deliberately no "raw" or triple\-brace form either \(em a template cannot opt out of its flavour's escaping. .PP The template renders against the same schema .B emit uses (season, week, slug, rank, colour, subject, the resolved display name, citations, commemorations), reshaped for two artefacts from one model: a flat booklet (the .B days list, one entry per day of the year) and a month grid (the .B weeks list, with padding cells flagged for the leading and trailing blanks a grid needs and a booklet does not). A key absent on a given day (an optional field a rite does not always set) renders as the empty string rather than an error \(em the one deliberate silence, so a template survives a day that does not carry every optional field. .PP See .BR colitur\-templates (5) for the full syntax, the escaping table per flavour, the complete view\-model field reference, and \(em before writing a template of any complexity \(em its .B SCOPE AND LOOKUP section: a .B month and a .B week both carry a .I num field, and a bare .B {{num}} read from inside .B {{#days}} silently climbs to the enclosing week's own value, not the month's. A similar hazard around .B name existed before the view model's naming rework and is now unrepresentable \(em .I name is a plain resolved string, with no dotted path left for a partial match to fall back through. .SS Flavours .BI \-\-flavour controls how interpolated .I values are escaped for the target format. It never touches the template's own literal markup, which is the author's and is trusted as\-is. One of: .RS .nf latex typst groff html xml ics none .fi .RE .PP When .B \-\-flavour is omitted it is inferred from .BR \-\-template 's own file extension: .RS .nf .I .tex -> latex .I .typ -> typst .I .ms .mom .me -> groff .I .html .htm -> html .I .xml -> xml .I .ics -> ics .I .md .adoc .txt -> none .fi .RE .PP .B none escapes nothing: Markdown, AsciiDoc and plain text have no fixed metacharacter set, so escaping them here would produce worse output than leaving them alone. .PP An extension .B colitur does not recognise is a hard error naming the seven flavours above; it is .I never a silent fallback to .BR none . Guessing the flavour wrong produces output that looks fine right up until the metacharacters it silently failed to escape show up in a rendered document. .RS .nf .B colitur table \-\-year 2027 \-\-template invite.wat colitur: cannot infer a flavour from ".wat"; pass \-\-flavour latex|typst|groff|html|xml|ics|none .fi .RE .PP A malformed template reports the parser's own reason and exits 2, never a crash \(em a template is user input, exactly like an overlay file. .RS .nf .B colitur table \-\-year 2027 \-\-template bad.txt colitur: template bad.txt: unclosed section {{#days}} .fi .RE .SH PUBLISH .BI "colitur publish " \-\-from " YEAR " \-\-to " YEAR " \-\-out " DIR" writes the static tree that .I is this program's API: every file a civil\-year range can be asked for, computed once and written out, so any web server or git repository can serve the result as\-is and nothing runs at request time. .RS .nf ef/.json one civil year, all days, whole\-year emitters ef/.csv ef/.xml ef/.ics ef///
.json one file per day schema/day\-v1.json the published JSON contract index.html a generated index page, not a template \&.colitur\-manifest every path this run wrote, one per line .fi .RE .PP Every emitted file goes through the same emitters .B emit uses; a published .I .ics file for a given year is byte\-for\-byte what .B "colitur emit \-\-format ics" would print for that year, and .B \-\-dtstamp means exactly what it means there. The per\-day JSON files carry the same shape as the whole\-year one, scoped to a single day \(em .B "colitur table" and template authors needing one day's data can read either. .PP .B Deterministic. Publishing the same .B \-\-from / \-\-to range into an empty directory twice produces a byte\-identical tree. Nothing in the publish path reads the wall clock; the .I .ics files' own DTSTAMP defaults to a fixed value derived from the emitted year, exactly as it does under .B emit (see .B EMIT above), and .B \-\-dtstamp overrides it the same way. This is what makes publishing into a git repository safe: .B git status shows only genuine change, and you review an actual diff before pushing, never a rewrite of files that did not change. .PP .B Non\-destructive. .B publish writes only files it owns, and records the relative path of every one of them in .IR out /.colitur\-manifest (itself never subject to pruning). A file you put in the output directory yourself \(em by hand, or from some other tool \(em is never named in that manifest, so it is never touched, .I whether or not .B \-\-prune is given. .RS .nf .B "touch out/MY\-NOTES.txt" .B "colitur publish \-\-from 2027 \-\-to 2027 \-\-out out \-\-prune" .B "test \-f out/MY\-NOTES.txt && echo kept" kept .fi .RE .PP .B \-\-prune removes exactly the entries a .I previous publish into the same .B \-\-out wrote that this run did not rewrite \(em typically an earlier year's own per\-day files, when a later .B publish targets a different .B \-\-from / \-\-to range into the same directory. A directory a stale entry's removal leaves empty is removed too (so, for example, .I out/ef/2027/ itself goes away once every file under it is gone), but nothing above .B \-\-out is ever touched, and .B \-\-out itself is never removed even when nothing is left in it. Without .BR \-\-prune , old entries are left in place, and only the manifest is rewritten to describe the current run. .PP .BR \-\-overlay is accepted exactly as on .BR day ", " readings " and " emit : applied on top of the shipped calendar, in order, before each year in the range is rendered. See .B OVERLAYS below. .SH OVERLAYS .TP .BI \-\-overlay " FILE" .RB ( \-o ) Apply a user\-supplied calendar on top of the shipped universal one. Repeatable and ordered. .PP Overlays are applied .I on top of the 1962 universal calendar, never instead of it. The shipped adjustments \(em which carry the inseparable Peter/Paul commemoration, the Major Litanies, St Barbara and Rogation Wednesday \(em are applied first, then each .B \-\-overlay in the order given. Last writer wins, so a later file may deliberately override an earlier one, or a universal entry, by naming its slug. .PP The workflow: .RS .nf .B colitur new\-overlay > my\-parish.sexp .B $EDITOR my\-parish.sexp .B colitur check my\-parish.sexp .B colitur day 2026 \-\-overlay my\-parish.sexp .fi .RE .PP The format is documented in full in .BR colitur\-overlay (5). In brief, an overlay is an S\-expression file with an .I id and a list of directives: .BR Add ", " Suppress ", " Replace " and " Edit . An added entry carries its own date specification, which may be a fixed .RI ( month ", " day ) pair, an .I Easter_offset in days (signed; Easter itself is 0), or an .I Nth_weekday of a month \(em the .I nth may be negative to count from the end, so .B \-1 is the last \(em so a patronal feast on "the first Sunday of October" or a dedication anniversary reckoned from Easter are both expressible. .PP In an added celebration the .I citations and .I layer fields may be omitted: they default to empty and to the overlay's own .IR id . The remaining six are required, and each is a closed set \(em .I rank is .BR Class1 ", " Class2 ", " Class3 " or " Class4 , .I status is .B Feast or .BR Commemoration_only , .I colour is .BR White ", " Red ", " Violet ", " Green ", " Black " or " Rose , and .I subject is .BR Lord ", " Bvm ", " Saint " or " Temporal . See .I /share/colitur/examples/diocesan\-example.sexp for a worked, runnable example of all four directives and all three date shapes: .RS .nf .B colitur day 2026 \-\-overlay /share/colitur/examples/diocesan\-example.sexp .fi .RE .PP Accepted on .BR day ", " readings ", " rubrics ", " emit ", " table ", " render " and " publish . .BR easter " and " temporal read no sanctoral data at all, so the flag would have no effect there and is .I refused rather than silently ignored. .PP .B An overlay is applied, not validated. This program's test layers \(em properties over every year in the domain, a differential against a sibling engine, three published\-calendar oracles, and hand\-verified pins \(em assert things about the .I shipped calendar. None of them can vouch for a file you supply. A directive naming a slug that does not exist prints a warning to standard error and the run continues, so a typo in a local calendar is visible rather than silent; a file that fails to load is fatal. .SH NAMING .TP .BI \-\-lang " CODE\(brFILE" Resolve every display name \(em a day's .IR name , its localised .IR weekday / rank_name / colour_name / season_name , each month's own name, and the fixed .I term vocabulary a template routes through \(em through this language instead of the default. Accepted on .BR day ", " readings ", " emit ", " table ", " render " and " publish ; refused elsewhere, the same discipline .B \-\-overlay gets. .I CODE (e.g. .BR la ", " en ) is looked up as .IR / CODE .ini ; a value containing .B / or ending .B .ini is read as a literal file path instead. Default .BR la , overridable by .I lang in the config file (see .BR colitur\-config (5)), itself overridden by .BR \-\-lang . .PP .B An unknown language is a hard error naming what is available, never a .B silent fallback to Latin: a booklet quietly printed in the wrong language is worse than one that refuses to print. .RS .nf .B colitur day 2027 \-\-lang xx colitur: no language "xx" (looked in .../share/colitur/lang); try: colitur lang \-\-list .fi .RE .PP A language file may declare .B fallback " = " CODE in its .I [meta] section (both shipped files show the shape; .I lang/en.ini declares .BR "fallback = la" ). A slug the active language does not itself name still resolves through the fallback chain, so a partial translation is usable from its first line rather than only once it is complete. .TP .B \-\-raw Restore every command's pre\-naming output: a day's .I name (and every other localised field the .B emit schema carries) equals the bare machine .IR slug , exactly as if no language had ever been resolved. This is not a special case threaded through the naming code \(em the identity table under .RB ( raw ) is an ordinary language table like any other, under which every lookup echoes its key back unchanged, so .B \-\-raw and a real language file share the same code path throughout. .B day and .B readings under .B \-\-raw are byte\-identical to this program's own pre\-naming output; every existing script built against that output therefore needs one flag, not a rewrite. .B \-\-raw also governs every reading citation, through a dedicated .I verbatim path rather than an identity language table \(em see .B SIGLA below for why that distinction matters. .TP .B lang \-\-list .TQ .BI lang " " \-\-dump " CODE" .TQ .BI lang " " \-\-check " FILE" What make "anyone can write a language file" true rather than merely permitted \(em the same idea the overlay system already established with .B new\-overlay and .BR check . .RS .nf .B colitur lang \-\-dump la > my\-lang.ini .B $EDITOR my\-lang.ini .B colitur lang \-\-check my\-lang.ini .B colitur day 2026 \-\-lang ./my\-lang.ini .fi .RE .PP .B \-\-check reports what is .B MISSING (a real slug the engine can produce, with no entry for it in the file) and, separately, any entry naming a slug that does not exist at all \(em a typo, which would otherwise be silently dead, its author never learning why their own name never appears. Exits .B 1 if anything is unknown, so it composes into a Makefile or a pre\-commit hook. The reference slug set is the same one .BR "colitur lang \-\-check" 's own coverage test walks: the observed office AND every commemoration and transfer the engine can produce, over a fixed multi\-year window \(em not merely a hand\-picked sample. .TP .B config \-\-show Print every effective setting \(em .IR lang ", " overlay ", " template ", " format ", " sigla_style ", " .IR sigla_book " and " sigla_tradition \(em its resolved value, and where it came from: .BR flag ", " config " or " default , via the same resolver every other command uses (there is deliberately no separate "provenance" function, so the two cannot disagree). Also prints the config file's own path and whether it exists. See .BR colitur\-config (5) for the file's location, its precedence in full, and every setting it recognises (as of this writing that page still describes the original four; .BR sigla_style / sigla_book / sigla_tradition are the same .B [defaults] mechanism, documented in full here in .B SIGLA below). .SH SIGLA A Mass reading citation .RB ( "Jn 3:16" ) is parsed into structure \(em book, chapter, verses \(em and re\-rendered, so its book names, its punctuation and abbreviation convention, and its numbering tradition are each a file a reader can edit, not something baked into the engine. This section covers the three flags, the two language\-file sections that drive them, and .IR lang/traditions.ini . .SS Two different questions Getting a citation right involves two independent questions that are easy to conflate: .RS .nf what is the book CALLED? -- a language file's [bible] section what book does it DENOTE? -- lang/traditions.ini .fi .RE .PP Naming varies by language: the third book of Kings is .I "Liber Regum III" in .I lang/la.ini and .I "3 Kings" in .IR lang/en.ini . Denoting does not: "modern numbering" renumbers the SAME book the SAME way whether the citation is rendered in Latin, English or any other language, so it lives in one file, not one section per language. Conflating the two is how a citation ends up naming the .I wrong book \(em correct punctuation, correct language, wrong reference. .SS "[sigla] -- how a citation is written" A language file's .I [sigla] section is a citation .I style : .RS .nf book = abbr ; or full book_sep = " " chapter_verse = {chapter}:{verses} range = {first}\-{last} part_sep = "; " verse_sep = ", " .fi .RE .PP .BR book " and " book_sep control the book name and what separates it from the reference proper. .BR chapter_verse " and " range are templates: the placeholders .BR {chapter} ", " {verses} " (in " chapter_verse ) and .BR {first} ", " {last} " (in " range ) are substituted; an unrecognised .B {placeholder} is left in the output literally, so a typo in a hand\-written style file is visible rather than silently swallowed. .B {chapter_roman} is also available in .BR chapter_verse , an alternative to .B {chapter} that prints the chapter as a Roman numeral \(em set .RI ( "chapter_verse = {chapter_roman}, {verses}" ) and the Missal's own idiom ("Feria IV", "Hebdomada I") extends to citations too: .RS .nf $ colitur readings 2026 | grep 2026\-06\-21 2026\-06\-21 ef\-time\-after\-pentecost\-sunday\-4 | Rom 8:18\-23 | Luc 5:1\-11 | \e Dominica IV post Pentecosten $ colitur readings 2026 \-\-sigla\-style my\-roman\-style.ini | grep 2026\-06\-21 2026\-06\-21 ef\-time\-after\-pentecost\-sunday\-4 | Rom VIII, 18\-23 | Luc V, 1\-11 | \e Dominica IV post Pentecosten .fi .RE .PP (where .I my\-roman\-style.ini carries only .RI ( "chapter_verse = {chapter_roman}, {verses}" ) under its own .IR [sigla] ). .PP .BR part_sep " and " verse_sep separate multiple readings within one citation and multiple verse ranges within one reading, respectively \(em what makes .I "Ecclus 51:1\-8, 12" and .I "Ioel 2:23\-24; 2:26\-27" render correctly. .PP .B "book_sep and typeset output." A booklet rendered to LaTeX or Typst may want a .B non\-breaking space here, so a line break can never fall between the book abbreviation and its reference (\(lqLuc.\(rq stranded at the end of one line, \(lq5, 12\-14\(rq starting the next). Set .B book_sep to a literal .B U+00A0 character \(em typed directly into the INI file, not a LaTeX tie .RB ( "~" ) : the flavour escapers match ASCII bytes only, so a real U+00A0 (a two\-byte UTF\-8 sequence) passes through every flavour untouched, but a literal .B "~" does .I not survive the LaTeX escaper, which turns it into .BR \etextasciitilde{} . .B "U+00A0 is invisible in a terminal" \(em it looks exactly like an ordinary space in an editor, in .BR "cat colitur.ini" , and in a diff that does not mark whitespace \(em so a careless copy\-paste can silently replace it with a normal space, or vice versa. Verify what is actually in the file, not what it looks like: .RS .nf $ grep \-o 'book_sep.*' my\-style.ini | xxd | head \-1 00000000: 626f 6f6b 5f73 6570 203d 2022 c2a0 220a book_sep = "... .fi .RE .PP .RB ( c2 " " a0 is U+00A0 in UTF\-8; .B 20 would be a plain space instead.) .SS "[bible] -- what a book is called" A language file's .I [bible] section supplies every book's display name, one .B full and one .B abbr form per id: .RS .nf luke.full = Evangelium secundum Lucam luke.abbr = Luc .fi .RE .PP .B \-\-sigla\-book selects which of the two forms .RB ( "full" " or " "abbr" ) a citation uses; the style's own .B book setting is the default when neither the flag nor the config key is given. Unlike .IR [celebration] " (deliberately partial for a new translation, per " NAMING above), .I [bible] is expected complete: a missing entry degrades to the citation data's own built\-in spelling (the pre\-naming form), never to another language's name via the .B fallback chain \(em a book name silently borrowed from the wrong language would be worse than one left untranslated. .SS "lang/traditions.ini -- what a book denotes" .I lang/traditions.ini is a second file, separate from every language file, naming .B traditions : sections that remap a Vulgate\-numbered book id onto the id a modern reader would expect. The shipped file: .RS .nf [vulgate] ; identity -- deliberately empty [modern] kings_3 = kings_1 kings_4 = kings_2 esdras_2 = nehemiah ecclesiasticus = sirach osee = hosea jonas = jonah apocalypse = revelation .fi .RE .PP .B \-\-sigla\-tradition names a section by its header; the default, .BR vulgate , is shipped deliberately empty, so a citation is never renumbered unless a tradition is chosen explicitly \(em the 1962 Missal on which this engine's data is built is Vulgate\-numbered throughout. .B modern renders the Vulgate id but with the OTHER tradition's name and numbering, so .I "3 Kings 19:3\-8" becomes .IR "1 Kings 19:3\-8" , via this mapping, not via a second copy of the reading data: .RS .nf $ colitur readings 2026 \-\-lang en | grep 2026\-02\-25 2026\-02\-25 ef\-lent\-ember\-wed | 3 Kgs. 19:3\-8 | Matt 12:38\-50 | \e Lenten Ember Wednesday $ colitur readings 2026 \-\-lang en \-\-sigla\-tradition modern | grep 2026\-02\-25 2026\-02\-25 ef\-lent\-ember\-wed | 1 Kgs 19:3\-8 | Matt 12:38\-50 | \e Lenten Ember Wednesday .fi .RE .PP An unrecognised .B \-\-sigla\-tradition degrades to .B vulgate with a warning on standard error, never a hard error \(em asking for a renumbering is optional, unlike asking for a language: .RS .nf colitur: .../lang/traditions.ini: no tradition "bogus"; falling back to the Vulgate .fi .RE .SS "--raw is byte-exact" .B \-\-raw does not merely reformat a citation with an identity style \(em that would still parse it and reprint its punctuation, which is not the same as leaving it untouched. Under .BR \-\-raw , every citation is emitted .B exactly as stored, with no parsing step at all. Two reasons this matters, both load\-bearing: .RS .nf 1. diffing this program's output against lectio (the sibling Go engine colitur's citation data is bootstrapped from) is only meaningful byte\-for\-byte -- a reformatted citation would show spurious diffs even where the two engines fully agree. 2. the raw view must not depend on the citation PARSER being correct -- if a parser bug ever mis\-renders a citation, the raw output used to diagnose that bug must not itself have gone through the same parser. .fi .RE .SS Config keys .BR sigla_style ", " sigla_book " and " sigla_tradition in a config file's .B [defaults] section are the config\-file counterpart of .BR \-\-sigla\-style ", " \-\-sigla\-book " and " \-\-sigla\-tradition , resolved with the identical flag > config > default precedence as .BR lang , and reported the same way by .BR "colitur config \-\-show" . See .B COMMANDS above for each flag's own default and error behaviour, and .BR colitur\-config (5) for the config file's format, location and the four settings it currently documents in full. .SS Sourcing discipline .IR lang/la.ini "'s own " .I [bible] rows are transcribed from the 1962 Missal's own reading incipits, each citing a scan line \(em the same discipline .I [celebration] already follows. Two rows are marked where that was not straightforwardly possible, so a reader can tell a sourced name from one that is not at a glance rather than trusting silently: .RS .nf ; UNSOURCED no instance of the book's own title was found in either scan; the entry falls back to the citation data's own built\-in spelling (e.g. Proverbs, Song of Songs). ; CONSTRUCTED composed from two separately\-sourced parts, because their COMBINATION does not appear verbatim in the Missal (the two Books of Kings: the shared incipit "Lectio libri Regum" is sourced, the volume numeral comes from the sourced chapter:verse locator, but no scan line spells out "Liber Regum III" as such). .fi .RE .PP An unmarked row is transcribed verbatim (case aside). This is not an apology for incompleteness \(em it is what lets a reader trust every .I sourced row precisely because the unsourced ones are labelled rather than blended in silently. .SH ENVIRONMENT .TP .B COLITUR_DATA_DIR Read the calendar data from this directory instead of the installed or build\-tree location. If it is set and contains no .IR sanctoral.sexp , .B colitur exits 2 naming the directory; it does .I not fall back to another copy. Naming a directory states an intent, and quietly computing a calendar from different data than the one requested is a failure mode this program refuses. .SH FILES .TP .I /share/colitur/ef/ The installed calendar data: the sanctoral calendar, its one hand\-authored overlay, the temporal lectionary and the Commons. Four S\-expression files. .TP .I /share/colitur/examples/diocesan\-example.sexp A worked example overlay, shipped as runnable documentation. Every celebration in it is invented; copy it and put your own calendar in its place. .TP .I /../data/ef/ The build\-tree location, used when running from a source checkout. .PP Resolution order is .B COLITUR_DATA_DIR first, then the installed directory, then the build tree. A directory counts only if it actually contains .IR sanctoral.sexp , so a failed or half\-removed installation falls through to a working checkout instead of shadowing it. .SH EXIT STATUS .TP .B 0 Success. .TP .B 2 Bad usage, a year outside 1583..9999, or the calendar data could not be read. .SH EXAMPLES Easter and its dependent feasts: .RS .nf .B colitur easter 2026 .fi .RE .PP One date: .RS .nf .B colitur day 2026 | grep '^2026\-12\-25' .fi .RE .PP Every first\-class day of a year: .RS .nf .B colitur day 2026 | awk '$6 == "class\-1"' .fi .RE .PP Days carrying at least one commemoration: .RS .nf .B colitur day 2027 | grep '+' .fi .RE .PP Names are Latin by default; render in English instead, or fall back to the pre\-naming, slugs\-only output a script written before this feature existed still expects: .RS .nf .B colitur day 2026 \-\-lang en | head \-1 .B colitur day 2026 \-\-raw | head \-1 .fi .RE .PP A local calendar on top of the universal one: .RS .nf .B colitur day 2026 \-\-overlay ~/calendars/diocese.sexp .B colitur day 2026 \-\-overlay ~/calendars/diocese.sexp \-\-overlay ~/calendars/parish.sexp .fi .RE .PP Run against a checkout's data rather than the installed copy: .RS .nf .B COLITUR_DATA_DIR=~/git/projects/colitur/data/ef colitur day 2026 .fi .RE .PP Render a year through a template, flavour inferred from the extension: .RS .nf .B colitur table \-\-year 2026 \-\-template booklet.tex > booklet.tex.out .fi .RE .PP Publish a year range as a static tree, then keep it in step with .B \-\-prune as the range moves: .RS .nf .B colitur publish \-\-from 2026 \-\-to 2027 \-\-out ~/public/colitur .B colitur publish \-\-from 2027 \-\-to 2028 \-\-out ~/public/colitur \-\-prune .fi .RE .SH SOURCES The calendar is computed against the 1962 .I Missale Romanum and its .IR "Rubricae Generales" , which are the sole authority for what .B colitur emits. Published calendars \(em missalemeum, Divinum Officium, gcatholic \(em are used as comparison oracles in the test suite only: a divergence from one is flagged loudly and adjudicated against the Missal, never silently adopted. Several such divergences have been resolved in this engine's favour. Missalemeum's own data derives from Divinum Officium, so those two are one lineage rather than two independent witnesses; a printed .I Ordo from the Latin Mass Society of England and Wales, compiled directly from the Missal and never passed through that lineage, is used as a genuinely independent oracle for one liturgical year (Advent 2024 through the end of 1962's 2025) \(em the Creed rubric, the Saturday votive Mass of Our Lady's own seasonal selection, and which Mass a day borrows, each compared against it in full. It covers only the universal calendar (its own diocesan propers and local patrons are excluded from every comparison), one civil year, and one publisher's transcription \(em a divergence against it is a question, not a verdict, the same discipline applied to every other oracle here. .SH LIMITATIONS Only the Extraordinary Form (1962) is implemented; the Ordinary Form is a planned peer module, not an overlay of this one. .PP Only the Epistle and the Gospel are emitted. The chants \(em Psalm, Gradual, Tract, Alleluia, Sequence \(em are deliberately not computed: they have no source in this engine's data and no oracle to validate them against, and the engine rejects any citation part outside those two rather than emit one it cannot stand behind. .PP The votive Office of the Blessed Virgin Mary on Saturday is kept, but the seasonal selection among its five Masses is not yet implemented, so its reading citations fall back to the day's ordinary ones. .SH SEE ALSO .BR colitur\-overlay (5) for the overlay file format \(em every directive, every field, the three date shapes and worked examples. .PP .BR colitur\-templates (5) for the template format used by .BR table ", " render " and " publish \(em the four syntax forms, the seven flavours and their escaping, the full view\-model field reference, and the one remaining scope hazard a template author can still hit. .PP .BR colitur\-config (5) for the config file's location, the flag > config > default precedence, and every setting it recognises. .PP .BR lectio (1) .SH LICENSE AGPL\-3.0\-or\-later.