From 6d78c65c58820f79ec5a978a9476d29969da218f Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Mon, 27 Jul 2026 12:58:31 +0200 Subject: feat(cli): 'lectio --liturgy/-L' prints the computed liturgical day offline Uses the pure engine + embedded universal calendar; UI-language names, rank, colour, cycles, and inline proper readings. Added the Assumption's propers. --- internal/cli/cli.go | 7 ++++ internal/cli/liturgy.go | 84 ++++++++++++++++++++++++++++++++++++++++++++ internal/cli/liturgy_test.go | 33 +++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 internal/cli/liturgy.go create mode 100644 internal/cli/liturgy_test.go (limited to 'internal/cli') diff --git a/internal/cli/cli.go b/internal/cli/cli.go index d69bce0..6d09591 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -119,6 +119,7 @@ func Run(args []string, stdin io.Reader, stdout, stderr io.Writer) int { var width int var list bool var ref string + var liturgy bool fs := flag.NewFlagSet("lectio", flag.ContinueOnError) fs.SetOutput(stderr) @@ -163,6 +164,8 @@ func Run(args []string, stdin io.Reader, stdout, stderr io.Writer) int { fs.StringVar(&output, "out", "", "write --md/--pdf output to FILE (default: stdout)") fs.StringVar(&output, "output", "", "write --md/--pdf output to FILE (default: stdout)") fs.StringVar(&calendar, "calendar", "", "export a month (YYYY-MM) as a printable A4 PDF calendar") + fs.BoolVar(&liturgy, "L", false, "print the computed liturgical day (offline calendar engine) and exit") + fs.BoolVar(&liturgy, "liturgy", false, "print the computed liturgical day (offline calendar engine) and exit") if err := fs.Parse(rest); err != nil { return 2 @@ -207,6 +210,10 @@ func Run(args []string, stdin io.Reader, stdout, stderr io.Writer) int { cfg.UILanguage = config.NormalizeUILanguage(uiLang) } + if liturgy { + return runLiturgy(cfg, date, stdout, stderr) + } + if citation || week || randV || randCh { tbl, _ := bible.LoadBookTable(userBooksTOML()) switch { diff --git a/internal/cli/liturgy.go b/internal/cli/liturgy.go new file mode 100644 index 0000000..523531d --- /dev/null +++ b/internal/cli/liturgy.go @@ -0,0 +1,84 @@ +package cli + +import ( + "fmt" + "io" + "strings" + "time" + + "github.com/lukaszkasprzak/lectio/internal/caldata" + "github.com/lukaszkasprzak/lectio/internal/calendar" + "github.com/lukaszkasprzak/lectio/internal/config" +) + +// runLiturgy prints the computed liturgical day for date (default today) using +// the offline calendar engine, and exits. It is the engine's demo/validation +// surface; the daily-readings paths still use the scraper. +func runLiturgy(cfg config.Config, date string, stdout, stderr io.Writer) int { + if date == "" { + date = today() + } + d, err := time.Parse("2006-01-02", date) + if err != nil { + fmt.Fprintf(stderr, "lectio: bad date %q (want YYYY-MM-DD)\n", date) + return 2 + } + day := calendar.Compute(d.UTC(), cfg.Selection(), []calendar.Layer{caldata.Universal()}) + printLiturgicalDay(stdout, cfg, day) + return 0 +} + +func printLiturgicalDay(out io.Writer, cfg config.Config, day calendar.LiturgicalDay) { + fmt.Fprintf(out, "%s %s\n", day.Date.Format("2006-01-02"), day.Weekday.String()) + fmt.Fprintf(out, "season: %s (week %d · Sunday cycle %s · weekday cycle %s)\n", + day.Season, day.Week, day.SundayCycle, day.WeekdayCycle) + fmt.Fprintf(out, "observed: %s [%s · %s]\n", celebrationName(cfg, day.Observed), rankLabel(day.Observed.Rank), day.Colour) + for _, o := range day.Others { + if n := celebrationName(cfg, o); n != "" { + fmt.Fprintf(out, " also: %s [%s]\n", n, rankLabel(o.Rank)) + } + } + for _, m := range day.Observed.Masses { + for _, r := range m.Readings { + label := r.Part + if m.Variant != "" { + label = m.Variant + " " + label + } + fmt.Fprintf(out, " %-16s %s\n", label+":", r.Citation) + } + } +} + +// celebrationName is the celebration's name in the UI language (falling back to +// English, then a humanized slug for temporal days that carry no proper name). +func celebrationName(cfg config.Config, c calendar.Celebration) string { + lang := "en" + if cfg.UILanguage == "pl" { + lang = "pl" + } + if n := c.Name[lang]; n != "" { + return n + } + if n := c.Name["en"]; n != "" { + return n + } + if c.Slug == "" { + return "(feria)" + } + return strings.ReplaceAll(c.Slug, "-", " ") +} + +func rankLabel(r calendar.Rank) string { + switch r { + case calendar.RankSolemnity: + return "solemnity" + case calendar.RankFeast: + return "feast" + case calendar.RankMemorial: + return "memorial" + case calendar.RankOptional: + return "optional memorial" + default: + return "feria" + } +} diff --git a/internal/cli/liturgy_test.go b/internal/cli/liturgy_test.go new file mode 100644 index 0000000..c3ab0b4 --- /dev/null +++ b/internal/cli/liturgy_test.go @@ -0,0 +1,33 @@ +package cli + +import ( + "bytes" + "path/filepath" + "strings" + "testing" + + "github.com/lukaszkasprzak/lectio/internal/config" +) + +func TestRunLiturgy(t *testing.T) { + dir := t.TempDir() + t.Setenv("LECTIO_CONFIG", filepath.Join(dir, "config.ini")) + cfg, err := config.Load() + if err != nil { + t.Fatal(err) + } + var buf bytes.Buffer + if code := runLiturgy(cfg, "2025-08-15", &buf, &buf); code != 0 { + t.Fatalf("exit code %d", code) + } + out := buf.String() + if !strings.Contains(out, "2025-08-15") { + t.Errorf("missing date:\n%s", out) + } + if !strings.Contains(strings.ToLower(out), "solemnity") || !strings.Contains(out, "Assumption") { + t.Errorf("2025-08-15 should be the Assumption solemnity:\n%s", out) + } + if !strings.Contains(out, "gospel:") { + t.Errorf("proper reading not shown:\n%s", out) + } +} -- cgit v1.3