package cli import ( "fmt" "io" "strings" "time" "github.com/lukaszkasprzak/lectio/internal/bible" "github.com/lukaszkasprzak/lectio/internal/caldata" "github.com/lukaszkasprzak/lectio/internal/calendar" "github.com/lukaszkasprzak/lectio/internal/config" "github.com/lukaszkasprzak/lectio/internal/i18n" "github.com/lukaszkasprzak/lectio/internal/naming" "github.com/lukaszkasprzak/lectio/internal/render" ) // runLiturgy prints the computed liturgical day for date (default today) using // the offline calendar engine, and exits. It shows the day's identity and // resolved reading citations; the daily-readings paths render the same offline // engine's readings as full text. 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 } dir, _ := config.CalendarsDir() layers, errs := caldata.Stack(cfg.Selection().Form, dir, cfg.Use) for _, e := range errs { fmt.Fprintln(stderr, "lectio: warning:", e) } day := calendar.Compute(d.UTC(), cfg.Selection(), layers) tbl, _ := bible.LoadBookTable(userBooksINI()) readings := dayReadings(cfg.Selection(), layers, d.UTC(), day) printLiturgicalDay(stdout, cfg, tbl, day, readings) return 0 } // dayReadings resolves the day's Mass readings; the logic lives in // caldata.Readings (shared with calfeed.Build) so it stays reusable outside // the CLI. func dayReadings(sel calendar.Selection, layers []calendar.Layer, date time.Time, day calendar.LiturgicalDay) []calendar.Reading { return caldata.Readings(sel, layers, date, day) } // vernacularVersion is the corpus that renders reading text: the config's // resolved reading corpus (Config.ReadingCorpus -- an explicit // reading_version, else a reading_lang/ui_language match against // autoselect-eligible corpora), else the complete Latin Vulgate. The // built-in Wujek (wuj) sets autoselect=false in its sidecar (it lacks the // deuterocanonical Daniel/Esther additions and ~400 verses its builder // dropped), so Polish still falls back to Latin until a properly-formatted // Polish Bible is supplied as a user corpus (the national-bibles feature); // wuj stays available for explicit `--ref -b wuj` and version comparison. func vernacularVersion(cfg config.Config) string { if code := cfg.ReadingCorpus(); code != "" { return code } return latinFallback // "vul" } // versionDisplayName resolves a corpus code's display label for lang: the // localized i18n chrome label (internal/i18n) when lang defines one for // code, else the corpus's own sidecar Name (bible.Meta -- covers user and // other registered corpora the built-in i18n tables don't know about), else // the bare code. func versionDisplayName(lang, code string) string { if l, ok := i18n.Get(lang).Version[code]; ok { return l } if m, ok := bible.Meta(code); ok && m.Name != "" { return m.Name } return code } // latinFallback is the corpus used to render a passage the vernacular corpus // lacks: the Latin Vulgate, which carries the full canon -- including the // deuterocanonical Daniel (13-14) and Esther (11-16) additions the vernacular // corpora omit and which the EF Lenten lectionary reads. The EF is a Latin // rite, so the Latin is the authoritative text to fall back to. const latinFallback = "vul" // readingLine resolves an English-authored citation and returns (display, text, // used): display is the citation re-formatted in the user's configured sigla // dialect (SiglaLang); text is the verses; used is the version that actually // supplied the text -- version, else latinFallback, else "" when neither has // it. The reading data is authored in English, so it is parsed in the "en" // dialect; only the display honors the configured dialect's abbreviations. func readingLine(tbl *bible.BookTable, cfg config.Config, version, citation string) (display, text, used string) { engRef, ok := tbl.ParseRef("en", citation) if !ok { return citation, "", "" // unparseable: show as authored } display = tbl.FormatRef(cfg.SiglaLang(), engRef) if txt := lookupText(version, engRef); txt != "" { return display, txt, version } if version != latinFallback { if txt := lookupText(latinFallback, engRef); txt != "" { return display, txt, latinFallback } } return display, "", "" } // lookupText joins the verses of ref in version into one string ("" if none). func lookupText(version, ref string) string { vs, _ := bible.Lookup(version, ref) parts := make([]string, 0, len(vs)) for _, v := range vs { parts = append(parts, strings.TrimSpace(v.Text)) } return strings.Join(parts, " ") } func printLiturgicalDay(out io.Writer, cfg config.Config, tbl *bible.BookTable, day calendar.LiturgicalDay, readings []calendar.Reading) { fmt.Fprintf(out, "%s %s\n", day.Date.Format("2006-01-02"), day.Weekday.String()) if day.SundayCycle != "" { // OF carries the reading cycles; EF does not fmt.Fprintf(out, "season: %s (week %d · Sunday cycle %s · weekday cycle %s)\n", day.Season, day.Week, day.SundayCycle, day.WeekdayCycle) } else { fmt.Fprintf(out, "season: %s (week %d)\n", day.Season, day.Week) } 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)) } } vern := vernacularVersion(cfg) for _, r := range readings { disp, txt, used := readingLine(tbl, cfg, vern, r.Citation) fmt.Fprintf(out, "\n %s (%s):\n", r.Part, disp) if txt == "" { fmt.Fprintf(out, " (text not in %s)\n", vern) continue } if used != vern { // the vernacular lacked it; fell back to Latin fmt.Fprintf(out, " [%s has no text here -- showing %s]\n", versionDisplayName("en", vern), versionDisplayName("en", used)) } for _, line := range strings.Split(render.Wrap(txt, 72), "\n") { fmt.Fprintf(out, " %s\n", line) } } } // celebrationName is the celebration's name in the UI language, resolved by // naming.CelebrationName (name. -> English -> Latin -> humanized slug). // A bare, unnamed feria renders as "(feria)". func celebrationName(cfg config.Config, c calendar.Celebration) string { if n := naming.CelebrationName(cfg.UILanguage, c); n != "" { return n } return "(feria)" } 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" case calendar.RankClass1: return "I class" case calendar.RankClass2: return "II class" case calendar.RankClass3: return "III class" case calendar.RankClass4: return "IV class" case calendar.RankCommemoration: return "commemoration" default: return "feria" } }