diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-29 08:24:34 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-29 08:24:34 +0200 |
| commit | 4ef5570c348e2d8d83c67e049b06e7b5d1a7542d (patch) | |
| tree | 1da35c3bd67f4235f8ed58af6f3f6e27eaa5a270 /internal | |
| parent | 802cedfa3b92a07247bf513b90e0fdff71201294 (diff) | |
| download | lectio-4ef5570c348e2d8d83c67e049b06e7b5d1a7542d.tar.gz lectio-4ef5570c348e2d8d83c67e049b06e7b5d1a7542d.zip | |
feat(cli): --cal-dump NAME to generate a commented calendar template
Replace the static examples/sanctorale-of.ini with a `--cal-dump NAME` flag
that writes the running universal calendar to ~/.config/lectio/calendars/NAME.ini
as a fully-commented layer template (every celebration present, all commented).
Uncomment one day and edit a field to override it; enable with `use = NAME`.
- Form-aware: respects -l/--lectionary, so `-l trad --cal-dump x` dumps the 1962
calendar (322 entries) and the default dumps the OF calendar (212 entries).
- Entries sorted by date then slug; refuses to overwrite an existing file.
- Always in sync with the embedded (or sanctorale-overridden) calendar, so no
stale checked-in template to maintain.
- Update help text, man page (OPTIONS/FILES/EXAMPLES) and README to match;
drop examples/sanctorale-of.ini.
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/cli/callayer.go | 102 | ||||
| -rw-r--r-- | internal/cli/cli.go | 9 |
2 files changed, 110 insertions, 1 deletions
diff --git a/internal/cli/callayer.go b/internal/cli/callayer.go index 3900eb1..db59b13 100644 --- a/internal/cli/callayer.go +++ b/internal/cli/callayer.go @@ -43,8 +43,110 @@ name.en = Saint Example, Patron reading.gospel = Jn 10:11-16 ` +// calDumpHeader precedes a --cal-dump file. %[1]s = the layer name, %[2]s = a +// human label for the form (e.g. "Ordinary Form"). +const calDumpHeader = `# lectio calendar layer "%[1]s" -- the %[2]s universal calendar, DUMPED and +# fully COMMENTED OUT. As shipped it changes nothing. To alter one day, uncomment +# its block, edit a field (rank, colour, name, ...), then enable the layer by +# adding its name to 'use' in config.ini: use = %[1]s +# +# A layer OVERLAYS the base calendar field by field, so every day you leave +# commented keeps its default. You can also: +# * remove a celebration: uncomment its block, add suppress = true +# * add one: write a new [your-slug] with date/rank/colour/name.* +# * change its readings: add reading.first / reading.psalm / reading.gospel +# +# Fields: date = MM-DD (or easter+-N / christmas+-N / sunday-after MM-DD) ; +# rank = solemnity|feast|memorial|optional ; class = lord|bvm|saint ; +# colour = white|red|green|violet|rose|black ; name.<lang> = display name. +# (reading.* lines are omitted here; add them only to change a day's readings.) +# +# Re-dump anytime with: lectio --cal-dump %[1]s (add -l trad for the 1962 form). + +[layer] +id = %[1]s +name = %[1]s +type = diocesan +` + var validRanks = map[string]bool{"solemnity": true, "feast": true, "memorial": true, "optional": true} +// runCalDump writes the running universal calendar for a form to +// <CalendarsDir>/<name>.ini as a fully-commented layer template: every +// celebration present, all commented, so uncommenting and editing one day +// overrides it. It refuses to overwrite an existing file. formFlag ("old"/"new", +// optional) overrides the form implied by cfg. +func runCalDump(cfg config.Config, name, formFlag string, stdout, stderr io.Writer) int { + sel := cfg.Selection() + if formFlag != "" { + if formFlag != "old" && formFlag != "new" { + fmt.Fprintf(stderr, "lectio: invalid --form %q (want old|new)\n", formFlag) + return 2 + } + sel.Form = formFlag + } + path, err := layerPath(name) + if err != nil { + fmt.Fprintln(stderr, "lectio:", err) + return 2 + } + if _, err := os.Stat(path); err == nil { + fmt.Fprintf(stderr, "lectio: %s already exists (not overwriting)\n", path) + return 1 + } + + base := caldata.Base(sel.Form) + formLabel := "Ordinary Form" + if sel.Form == "old" { + formLabel = "Extraordinary Form (1962)" + } + + // Sort by date then slug. MM-DD dates sort chronologically and ahead of the + // movable specs (easter+N, christmas-N), which then group alphabetically. + slugs := make([]string, 0, len(base.Cels)) + for slug := range base.Cels { + slugs = append(slugs, slug) + } + sort.Slice(slugs, func(i, j int) bool { + di, dj := base.Cels[slugs[i]].Fields["date"], base.Cels[slugs[j]].Fields["date"] + if di != dj { + return di < dj + } + return slugs[i] < slugs[j] + }) + + var b strings.Builder + fmt.Fprintf(&b, calDumpHeader, name, formLabel) + order := []string{"date", "rank", "class", "colour", "name.en", "name.pl", "name.la"} + n := 0 + for _, slug := range slugs { + f := base.Cels[slug].Fields + if f["date"] == "" { + continue // skip anything without a date (defensive) + } + b.WriteByte('\n') + fmt.Fprintf(&b, "# [%s]\n", slug) + for _, k := range order { + if v := f[k]; v != "" { + fmt.Fprintf(&b, "# %s = %s\n", k, v) + } + } + n++ + } + + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + fmt.Fprintln(stderr, "lectio:", err) + return 1 + } + if err := os.WriteFile(path, []byte(b.String()), 0o644); err != nil { + fmt.Fprintln(stderr, "lectio:", err) + return 1 + } + fmt.Fprintf(stdout, "dumped %d %s celebrations to %s\n", n, formLabel, path) + fmt.Fprintf(stdout, "uncomment a day, edit it, then set use = %s in config.ini\n", name) + return 0 +} + // layerPath resolves <CalendarsDir>/<name>.ini, rejecting a name with path separators. func layerPath(name string) (string, error) { if name == "" || strings.ContainsAny(name, `/\`) { diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 078d0c2..ffefd54 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -46,6 +46,8 @@ Flags: --week list the coming week's gospel references and exit -L, --liturgy print the computed liturgical day (offline, no network) and exit --cal-new NAME scaffold a custom calendar layer ~/.config/lectio/calendars/NAME.ini + --cal-dump NAME dump the whole universal calendar to NAME.ini, commented, + to uncomment+edit one day (add -l trad for the 1962 form) --cal-check NAME validate a custom calendar layer NAME.ini and exit --corpus-check X validate a bible corpus (code, e.g. drb, or a path to a <code>.tsv) and exit; errors fail, coverage gaps warn @@ -132,7 +134,7 @@ func Run(args []string, stdin io.Reader, stdout, stderr io.Writer) int { var list bool var ref string var liturgy bool - var calNew, calCheck string + var calNew, calCheck, calDump string var corpusCheck string var jsonOut bool var format, fromFlag, toFlag, yearFlag, formFlag string @@ -174,6 +176,7 @@ func Run(args []string, stdin io.Reader, stdout, stderr io.Writer) int { fs.BoolVar(&liturgy, "liturgy", false, "print the computed liturgical day (offline calendar engine) and exit") fs.StringVar(&calNew, "cal-new", "", "scaffold a new calendar-layer file NAME.ini and exit") fs.StringVar(&calCheck, "cal-check", "", "validate the calendar-layer file NAME.ini and exit") + fs.StringVar(&calDump, "cal-dump", "", "dump the universal calendar to NAME.ini as a commented layer template and exit") fs.StringVar(&corpusCheck, "corpus-check", "", "validate a bible corpus (code or path to <code>.tsv) and exit") fs.BoolVar(&jsonOut, "json", false, "with --corpus-check, print the report as JSON") fs.StringVar(&format, "format", "", "json|ical: emit the computed calendar and exit") @@ -229,6 +232,10 @@ func Run(args []string, stdin io.Reader, stdout, stderr io.Writer) int { cfg.UILanguage = config.NormalizeUILanguage(uiLang) } + if calDump != "" { + return runCalDump(cfg, calDump, formFlag, stdout, stderr) + } + // `--year N` on its own prints the human-readable key-dates overview; // with --format (or --from/--to) it emits the machine calendar via runFeed. if yearFlag != "" && format == "" && fromFlag == "" && toFlag == "" { |
