package cli import ( "fmt" "io" "regexp" "strconv" "strings" "time" "github.com/lukaszkasprzak/lectio/internal/caldata" "github.com/lukaszkasprzak/lectio/internal/calendar" "github.com/lukaszkasprzak/lectio/internal/config" ) // reNumberedSunday matches the generic seasonal Sundays ("ordinary-sunday-11", // "lent-sunday-1"), which carry solemnity rank but are not what a "key dates" // list wants -- the season boundary already marks them. var reNumberedSunday = regexp.MustCompile(`-sunday-\d+$`) // seasonalSunday reports whether a slug is a generic numbered Sunday or an // Octave-of-Easter weekday, which the overview omits from the solemnity list. func seasonalSunday(slug string) bool { s := strings.TrimPrefix(slug, "ef-") return reNumberedSunday.MatchString(s) || strings.HasPrefix(s, "easter-octave-") } // seasonName renders a season slug as a display phrase for the year overview. func seasonName(s calendar.Season) string { switch s { case calendar.Advent: return "Advent" case calendar.Christmas: return "Christmas" case calendar.Lent: return "Lent" case calendar.Triduum: return "the Paschal Triduum" case calendar.Easter_: return "Eastertide" case calendar.Ordinary: return "Ordinary Time" } switch string(s) { case "septuagesima": return "Septuagesima" case "passiontide": return "Passiontide" case "time-after-epiphany": return "Time after Epiphany" case "time-after-pentecost": return "Time after Pentecost" } return string(s) } // topRank reports whether a rank is the year's headline tier: a solemnity // (Ordinary Form) or a class-1 feast (Extraordinary Form). func topRank(r calendar.Rank) bool { return r == calendar.RankSolemnity || r == calendar.RankClass1 } // runYearOverview prints the key liturgical dates of a civil year -- season // boundaries, solemnities and feasts of the Lord -- computed from the engine // for the configured form (and custom-calendar layers). It is what `--year N` // does on its own (with --format it emits the machine calendar instead). Output // goes through the pager when one is configured and stdout is a terminal. func runYearOverview(cfg config.Config, yearStr, formFlag string, pagerFlag, noPager bool, stdout, stderr io.Writer) int { year, err := strconv.Atoi(strings.TrimSpace(yearStr)) if err != nil || year < 1583 || year > 9999 { fmt.Fprintf(stderr, "lectio: invalid --year %q (want 1583-9999)\n", yearStr) return 2 } 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 } dir, _ := config.CalendarsDir() layers, errs := caldata.Stack(sel.Form, dir, cfg.Use) for _, e := range errs { fmt.Fprintln(stderr, "lectio: warning:", e) } out := stdout if pagerRequested(pagerFlag, noPager, cfg) && isTerminalWriter(stdout) { if o, finish, ok := startPager(pagerCommand(cfg), stdout, stderr); ok { out = o defer finish() } } formName := "Ordinary Form" if sel.Form == "old" { formName = "Extraordinary Form (1962)" } title := fmt.Sprintf("Key liturgical dates %d — %s", year, formName) fmt.Fprintf(out, "%s\n%s\n\n", title, strings.Repeat("=", len([]rune(title)))) prev := calendar.Season("") for d := time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC); d.Year() == year; d = d.AddDate(0, 0, 1) { day := calendar.Compute(d, sel, layers) var tags []string if day.Season != prev && prev != "" { tags = append(tags, seasonName(day.Season)+" begins") } prev = day.Season if topRank(day.Observed.Rank) && !seasonalSunday(day.Observed.Slug) { tags = append(tags, "solemnity") } else if day.Observed.Class == calendar.ClassLord && day.Observed.Rank == calendar.RankFeast { tags = append(tags, "feast of the Lord") } if len(tags) == 0 { continue } name := celebrationName(cfg, day.Observed) fmt.Fprintf(out, " %s %s %-46s %s\n", d.Format("2006-01-02"), d.Weekday().String()[:3], name, strings.Join(tags, " · ")) } return 0 }