diff options
Diffstat (limited to 'man/colitur-templates.5')
| -rw-r--r-- | man/colitur-templates.5 | 562 |
1 files changed, 360 insertions, 202 deletions
diff --git a/man/colitur-templates.5 b/man/colitur-templates.5 index 2f42d68..06ad583 100644 --- a/man/colitur-templates.5 +++ b/man/colitur-templates.5 @@ -107,9 +107,6 @@ like an .BR colitur\-overlay (5) file, and is never allowed to crash the program that reads it. .SH SCOPE AND LOOKUP -.B This section documents a real hazard, not a theoretical one \(em it has -.B produced wrong output during this program's own development. -.PP Scope is a stack. Rendering starts with the whole view (the year) as the one entry on the stack; each .B {{#section}} @@ -131,79 +128,46 @@ could never reach .BR {{year}} , which lives only on the outermost object. .PP -.B The hazard: a DOTTED PATH that resolves only PART WAY inward falls back -.B WHOLESALE to an outer scope of the SAME NAME. -A bare key inside the immediately enclosing section is not at risk this way -\(em it either resolves right there or fails outright (see -.B num -below); it is specifically a dotted path, one step of which is missing from -the inner object, that abandons the whole path and restarts the lookup one -level out. Falling back outward means a name that exists at -.I both -levels never fails and never warns \(em it just silently resolves to the -.I outer -one, because the inner object's own absence of that key is indistinguishable -from "look further out" and "this key does not apply here". Two collisions -are known to exist in the shipped view model: -.TP +.B A hazard this section used to document here no longer exists, and is +.B recorded as removed rather than silently dropped from this page. +Before the view model's naming rework, .B name -Both a +was an object keyed by language (e.g. +.BR la " and " en ), +carried on both a .B month and a -.B day -carry a -.I name -field (each an object keyed by language, e.g. -.BR la " and " en ). -Written naively, when -.B {{#days}} -is nested inside -.BR {{#months}} , -a bare +.BR day . +A dotted path that resolved only part way inward \(em a bare .B {{name.la}} -does -.I not -resolve to the day's own Latin name. It resolves to the -.I enclosing month's -Latin name, because the day's own -.I name -object either has no -.B la -key (an unnamed day) or the dotted path fails partway and the WHOLE path -falls back to the outer scope, which does have one. This is not a corner -case: on an ordinary month, most days carry no Latin name at all (only named -sanctoral days do), so the naive form renders the +on a day with no Latin name of its own \(em fell back WHOLESALE to the +enclosing +.BR month 's +own +.B name.la +instead of failing, because the lookup rule above cannot distinguish "this +key does not apply here" from "look further out": on an ordinary month, most +days carried no Latin name at all, so the naive form silently printed the .I month's -name on nearly every day \(em in a per\-month booklet, dozens of wrong -lines; in a month grid (whose -.B {{#weeks}} -is itself only ever reachable through -.BR {{#months}} , -since -.I weeks -is a field of -.IR month , -never a top\-level list) EVERY cell reads the month's own name. -.PP -.B The collision needs a month actually on the scope stack to fire. -Iterating the TOP\-LEVEL, flat -.B days -list directly ( -.B {{#days}}...{{/days}} -at the outermost level, never passing through +own name on nearly every day. +.B name +is now a PLAIN STRING, always fully resolved before the template ever sees +it (see +.B VIEW MODEL +below) \(em there is no dotted path left for a partial match to climb out +of, so this specific hazard is unrepresentable, not merely avoided by +convention. A bare +.B {{name}} +inside +.B {{#days}} +always reads that day's own name, nested under .B {{#months}} -first) puts no month object anywhere on the stack, so the naive form does -.I not -silently substitute the wrong answer there \(em it silently resolves to -nothing, exactly as any other absent key would, because there is no -outer scope left to climb to. This is precisely what makes the hazard easy -to miss: an author who tries the naive form against the flat list first -sees the unnamed days come out empty, reasonably concludes the form is -safe, and then hits the real collision the moment the identical fields are -read from inside -.BR {{#months}} , -which is what every shipped template that produces a grid or a per\-month -booklet actually does. +or not, full stop. +.PP +.B One collision of the same underlying shape still exists in the shipped +.B view model, because it involves two PLAIN (non\-dotted) fields that +.B happen to share a name, which the fallback rule above cannot help with +.B either: .TP .B num Both a @@ -216,46 +180,77 @@ field. Inside .BR {{#weeks}} , a bare .B {{num}} -is the week's own number, correctly \(em but only because nothing between -the week and the day currently redefines it. A template that reaches +is the week's own ordinal (1, 2, 3, ...) within its month, correctly \(em +but only because nothing between the week and wherever +.B {{num}} +is actually written currently redefines it. A +.B day +object has no .I num -from any scope where the immediately enclosing section does not itself -carry it will silently climb to whichever ancestor does, and that may not be -the one the author meant. -.PP -.B The safe idiom. -Push the object you actually want onto the scope stack yourself, with a -.B {{#name}} -section, before reading its fields \(em then a bare field inside that -section can only resolve against the object you just pushed, or fail -outright and fall through to an inverted fallback you write explicitly: +field of its own at all, so a bare +.B {{num}} +written inside +.B {{#days}} +climbs straight past the day to the enclosing +.BR week 's +own +.IR num , +not the month's, even where a template author reaching for "the month +number" from deep inside a day cell might expect otherwise. Verified live, +not merely reasoned about (a template iterating +.BR {{#months}}{{#weeks}}{{#days}} , +printing the enclosing week's own +.I num +beside the month's own +.IR month_num , +both carried on every day and week object for exactly this reason \(em see +.B VIEW MODEL +below): .RS .nf -{{#name}}{{la}}{{^la}}{{slug}}{{/la}}{{/name}} +.B colitur table \-\-year 2027 \-\-template num\-hazard.txt +week 1 of month 1 + day\-scope num: 1 +week 2 of month 1 + day\-scope num: 2 .fi .RE -This opens the day's own -.I name -object (never the month's \(em a -.B {{#section}} -always resolves its OWN path from the point it appears, which for -.B {{#name}} -written inside -.B {{#days}} -is the day's -.IR name , -shadowing the month's identically\-named field exactly as intended), reads -.B la -from it, and falls back to the day's own -.B slug -only when -.B la -is genuinely absent from -.I that -object \(em never when it is merely absent from an ancestor. Every shipped -template that prints a day's name uses exactly this idiom; none uses the -bare dotted form. +The "day\-scope num" line is reading a bare +.B {{num}} +from inside +.BR {{#days}} . +It changes from week to week, proving it is the enclosing +.BR week 's +own ordinal, not the constant +.B 1 +a reader expecting "month number" might assume. +.PP +.B The safe idiom, when a template genuinely needs a number from an +.B ancestor further out than the immediately enclosing section: +use the field the view already carries FOR that purpose rather than a bare, +climbing +.BR {{num}} . +A +.B week +object already carries +.B month_num +(and +.BR month_name ) +precisely because the engine has no +.RB \(lq "../" \(rq +parent\-path syntax to reach the enclosing month any other way \(em see +.B week +under +.B VIEW MODEL +below. There is no equivalent shortcut for reaching a +.B week +object's own +.I num +from inside +.BR {{#days}} , +because no shipped template needs one; a template that does should carry it +down explicitly the same way, rather than relying on the climb. .SH HOST\-LANGUAGE COMMENTS .B The engine has no awareness of the target language's own comment syntax. A @@ -438,6 +433,12 @@ for the rite's own scope and limitations (readings cover only the Epistle and Gospel; the votive Office of the Blessed Virgin Mary on Saturday does not yet select among its five seasonal Masses). .SH VIEW MODEL +.B Every field below is verified against +.I lib/render/view.ml +.B and +.IR schema/day\-v1.json , +.B not transcribed from memory or from an earlier version of this page. +.PP The value a template renders against is built once per .B colitur table / @@ -447,6 +448,7 @@ The value a template renders against is built once per invocation, from the same resolved calendar .B colitur emit uses, and is shaped for two artefacts from one model: a flat booklet (the +top\-level .B days list, one entry per day of the requested year) and a month grid (the .B months @@ -457,6 +459,28 @@ cells so every row has exactly seven). This is the same shape published at .IR schema/day\-v1.json , described here in prose; the JSON Schema is the machine\-checked contract and this page is its worked explanation. +.PP +Every localised field below (marked +.RI \(lq "in the active language" \(rq ) +resolves through whichever language +.B \-\-lang +selected, default +.IR la ; +under +.BR \-\-raw , +each equals its own unlocalised counterpart (a +.B name +equals its +.BR slug , +a +.B rank_name +equals its +.BR rank , +and so on) rather than being blank \(em see +.BR colitur (1)'s +own +.B NAMING +section. .SS Top level .TP .B rite @@ -466,6 +490,32 @@ The rite identifier, currently always the string .B year The civil year requested, as a four\-digit string. .TP +.B term +A fixed, closed vocabulary of strings in the active language, so a +translated booklet needs no template edit for its own boilerplate words. +Object keys: +.BR ordo ", " contents ", " epistle ", " lesson ", " gospel ", " +.BR commemoration " and " week . +Referenced as +.BR {{term.ordo}} , +.BR {{term.epistle}} , +and so on. +.TP +.B weekday_headings +A list of exactly seven objects, Sunday first, each +.RB { name ", " last }, +for a localised grid header row \(em +.B name +is the weekday's own name in the active language, +.B last +is true on the seventh (Saturday) entry, the same +.RB \(lq "unless this is the last one" \(rq +flag the +.B day +object's own +.B last +field below provides for a week's row of cells. +.TP .B months A list of twelve .B month @@ -483,14 +533,9 @@ template iterates over directly, without going through The month number, 1 through 12, as a string. .TP .B name -An object keyed by language (currently -.B la -and -.BR en ), -each value the month's own name in that language (e.g. -.RB \(lq Ianuarius \(rq -/ -.RB \(lq January \(rq ). +The month's own resolved display name in the active language (e.g. +.RB \(lq Ianuarius \(rq ), +a plain string. .TP .B days This month's own @@ -512,7 +557,39 @@ The week's ordinal within its month (1, 2, 3, ...), as a string. This is .I not a liturgical week number \(em see .B day \(-> week -below for that. +below for that, and +.B SCOPE AND LOOKUP +above for the +.B num +collision this field and the enclosing month's own +.B num +share. +.TP +.B month_num +.TQ +.B month_name +The enclosing month's own +.B num +and +.BR name , +carried onto every week because the engine has no +.RB \(lq "../" \(rq +parent\-path syntax \(em a nested +.B {{month_num}} +or +.B {{name}} +read from inside +.B {{#weeks}} +would otherwise be unable to reach the month at all (a bare +.B {{name}} +here would in fact resolve to the WEEK's enclosing DAY's name once one is +pushed, not the month's, since +.I week +itself carries no +.B name +field of its own \(em carrying +.B month_name +explicitly is what avoids relying on that climb). .TP .B days Exactly seven @@ -556,8 +633,13 @@ seven entries); a template checks this, not emptiness, to decide whether to render a cell's contents. .TP .B season -The liturgical season's own string name (e.g. -.BR paschaltide ", " lent ). +The liturgical season's own unlocalised string key (e.g. +.BR paschaltide ", " lent ), +stable across every language \(em unaffected by +.BR \-\-lang / \-\-raw . +.TP +.B season_name +The season's own resolved display name, in the active language. .TP .B week The liturgical week number within the season, as a string, or the empty @@ -578,30 +660,60 @@ key of its own at all, only so there is nothing for it to shadow). .TP .B slug -The observed celebration's stable identifier (e.g. -.BR ef\-easter\-sunday ). +The observed celebration's stable, unlocalised identifier (e.g. +.BR ef\-easter\-sunday ), +unaffected by +.BR \-\-lang / \-\-raw \(em +the machine key a template or a downstream script keys off, distinct from +.B name +below. .TP .B name -An object keyed by language, the observed celebration's own name in each -language colitur's data supplies one for. Frequently has no -.B la -or -.B en -key at all (most temporal days, most sanctoral entries in the shipped data) -\(em see -.B SCOPE AND LOOKUP -above for the resulting month\-name collision and its safe idiom. +The observed celebration's resolved display name, in the active language +\(em a PLAIN STRING, not an object keyed by language. Under +.B \-\-raw +this equals +.B slug +exactly, which is what makes +.B \-\-raw +output byte\-stable; a real language's own table names essentially every +slug the engine can produce (verified: 725 of 725 over the window colitur's +own coverage test measures), so in ordinary operation +.B name +differs from +.B slug +on nearly every day, not only the ones with a proper name in the historical +sense. +.TP +.B weekday +The day's own weekday name in the active language (e.g. +.RB \(lq "Feria V" \(rq , +.RB \(lq Sabbatum \(rq ), matching +.BR dow 's +numbering (0 = Sunday). .TP .B rank -The observed celebration's class, as the kernel's own string (e.g. -.BR class\-1 ). -There is deliberately no separate, localized rank label: the kernel carries -no per\-language rank names to draw one from. +The observed celebration's class, as the kernel's own unlocalised string +key (e.g. +.BR class\-1 ), +unaffected by +.BR \-\-lang / \-\-raw . +.TP +.B rank_name +The rank's own resolved display name, in the active language (e.g. +.RB \(lq "I classis" \(rq ). .TP .B colour -The observed celebration's liturgical colour, lowercase (one of +The observed celebration's liturgical colour, lowercase, unlocalised (one of .BR white ", " red ", " green ", " violet ", " rose ", " black , -or the empty string). +or the empty string), unaffected by +.BR \-\-lang / \-\-raw . +.TP +.B colour_name +The colour's own resolved display name, in the active language (e.g. +.RB \(lq albus \(rq +for +.BR white ). .TP .BR is_white ", " is_red ", " is_green ", " is_violet ", " is_rose ", " is_black Six booleans, exactly one true (matching @@ -614,16 +726,17 @@ is no expression evaluation, so this is the only way a template branches on colour at all. .TP .B subject -Whose feast this is, lowercase (one of +Whose feast this is, lowercase, unlocalised (one of .BR lord ", " bvm ", " saint ", " temporal ). .TP .B comms A list of commemoration objects admitted on this day, each carrying -.BR slug , -.B name -(an object keyed by language, same shape as the day's own -.BR name ), -and +.BR slug " (unlocalised), " name +(the SAME resolved\-string shape as the day's own +.BR name , +through the identical language table \(em a commemoration's slug is drawn +from the same sanctoral/temporal pool as the observed day's, not a second +vocabulary), and .B privileged (boolean: true for a privileged commemoration under RG 109, which survives even where an ordinary one would be capped out). Empty list on a day with no @@ -634,14 +747,18 @@ cell. A list of at most one object, present when a feast impeded elsewhere was transferred onto THIS day (RG 96\(en98); carries the transferred celebration's own -.BR slug . +.B slug +only \(em +.I not +a resolved +.BR name . Empty list when nothing transferred in. .TP .B transferred_out A list of objects, one per celebration that would have fallen on this day but was displaced and moved to a later date; each carries .B slug -and +(only, as above) and .B to (the ISO\-8601 date it was moved to). Empty on the ordinary day. .TP @@ -649,6 +766,9 @@ and The Epistle/Lesson reading citation (e.g. .RB \(lq "Heb 1:1\-12" \(rq ), never scripture text \(em a reference only. Empty string when none resolved. +Unaffected by +.BR \-\-lang / \-\-raw : +a citation is a reference, not a display name. .TP .B gospel The Gospel reading citation, same shape as @@ -666,16 +786,15 @@ an eighth, empty column and .B pdflatex would reject the file outright. .SH A WORKED MINIMAL TEMPLATE +.B Every line below is pasted from a real run, not written by hand. A plain\-text booklet, days nested inside months \(em the shape every -shipped template actually uses, and the shape the -.B SCOPE AND LOOKUP -hazard needs to fire \(em using the safe name idiom from that section: +shipped template actually uses: .RS .nf {{rite}} {{year}} {{#months}}{{#days}} -{{iso}} {{#name}}{{la}}{{^la}}{{slug}}{{/la}}{{/name}} {{colour}}{{#comms}} +{{slug}}{{/comms}} +{{iso}} {{weekday}} {{name}} {{colour_name}}{{#comms}} +{{name}}{{/comms}} {{/days}}{{/months}} .fi .RE @@ -693,89 +812,119 @@ one): .B colitur table \-\-year 2026 \-\-template minimal.txt | head \-7 ef 2026 -2026\-01\-01 ef\-circumcision white +2026\-01\-01 Feria V In Octava Nativitatis Domini albus -2026\-01\-02 ef\-christmas\-1\-friday white +2026\-01\-02 Feria VI Feria VI ante Epiphaniam albus -2026\-01\-03 Officium sanctae Mariae in sabbato white +2026\-01\-03 Sabbatum Officium sanctae Mariae in sabbato albus .fi .RE .PP -The second data line shows the fallback firing: 2 January carries no Latin +Every line names a real celebration in Latin \(em there is no unnamed\-day +fallback to demonstrate here the way an earlier version of this page showed +one: the shipped +.I la +table names every slug the engine can produce (725 of 725 over the window +its own coverage test measures), so .B name -in the shipped data, so -.B {{^la}} -supplies -.B {{slug}} -instead. The third shows the non\-fallback case: 3 January -.I does -carry a Latin name (the votive Office of the Blessed Virgin Mary on -Saturday), and the idiom prints it correctly. -.PP -Now swap only the interpolation \(em the safe idiom above becomes the -naive, unsafe -.B {{name.la}} , -nesting left exactly as it was: +is never the bare +.B slug +in ordinary operation. +.B \-\-raw +is what produces the bare\-slug form instead, on demand, not a gap in the +data: .RS .nf -{{rite}} {{year}} -{{#months}}{{#days}} -{{iso}} {{name.la}} {{colour}} -{{/days}}{{/months}} +.B colitur table \-\-year 2026 \-\-template minimal.txt \-\-raw | head \-3 +ef 2026 + +2026\-01\-01 4 ef\-circumcision white .fi .RE .PP -Rendered against the identical year, same extension and flavour: +.RB \(lq 4 \(rq +above is +.BR weekday 's +own raw form: with no language table at all, +.I weekday +falls back to the numeral +.BR dow +itself carries (Thursday, 1 January 2026), not a slug\-like string \(em +there is no bare English or Latin word for a weekday to echo back the way a +missing celebration name echoes its own +.BR slug . +.PP +The +.B num +collision from +.B SCOPE AND LOOKUP +above, reproduced here in full \(em a grid\-shaped template, a week nested +inside a month, a day nested inside the week: .RS .nf -.B colitur table \-\-year 2026 \-\-template minimal\-unsafe.txt | head \-7 -ef 2026 - -2026\-01\-01 Ianuarius white - -2026\-01\-02 Ianuarius white +{{#months}}{{#weeks}}week {{num}} of month {{month_num}} +{{#days}} day\-scope num: {{num}} +{{/days}}{{/weeks}}{{/months}} +.fi +.RE +.RS +.nf -2026\-01\-03 Officium sanctae Mariae in sabbato white +.B colitur table \-\-year 2027 \-\-template num\-hazard.txt | head \-9 +week 1 of month 1 + day\-scope num: 1 + day\-scope num: 1 + day\-scope num: 1 + day\-scope num: 1 + day\-scope num: 1 + day\-scope num: 1 + day\-scope num: 1 +week 2 of month 1 .fi .RE .PP -The collision fires for real on the first two lines: with a month on the -scope stack, the day's own missing -.B la -key falls back all the way out to the ENCLOSING MONTH's own -.RB \(lq Ianuarius \(rq -rather than failing. The third line is untouched, because 3 January's own -.I name -object genuinely has a -.B la -key, so the dotted path resolves without ever needing to fall back. -.PP -.B This is specific to nesting, and that is the point. -The identical naive -.B {{name.la}} -written against the TOP\-LEVEL, flat -.B days -list (no -.B {{#months}} -wrapping it) does -.I not -print -.RB \(lq Ianuarius \(rq -anywhere \(em there is no month on the stack to fall back to, so it prints -nothing on an unnamed day instead, silently but not wrongly. See +Every +.RB \(lq "day\-scope num" \(rq +line inside week 1 reads +.BR 1 , +and every one inside week 2 (not shown above, but the next seven lines of +the same run) reads +.BR 2 : +a bare +.B {{num}} +written inside +.B {{#days}} +is reading the enclosing +.BR week 's +own ordinal, silently, because +.I day +itself carries no +.I num +field to shadow it with. Nothing here is wrong \(em the lookup rule is +doing exactly what .B SCOPE AND LOOKUP -above for why testing the naive form only against a flat list is exactly -how this hazard goes unnoticed until a template is later nested under -.BR {{#months}} . +documents \(em but a template author who expected "the month number" from +that bare +.B {{num}} +would be reading the wrong field with no warning at all. Compare +.B {{month_num}} +in the same template, which stays +.B 1 +throughout January regardless of which week or day it is read from, +because it is carried explicitly rather than reached by climbing. .SH SEE ALSO .BR colitur (1) for .BR table ", " render " and " publish , -and for the five +for the five .B emit -formats that share this same view model. +formats that share this same view model, and for +.BR \-\-lang / \-\-raw +and the +.B NAMING +section that controls every localised field this page documents. .PP .BR colitur\-overlay (5) for the local\-calendar file format that supplies the celebrations a @@ -784,5 +933,14 @@ template's and .B slug fields can carry. +.PP +.BR colitur\-config (5) +for setting a default +.B \-\-template +so +.B table +/ +.B render +need not repeat it on every invocation. .SH LICENSE AGPL\-3.0\-or\-later. |
