summaryrefslogtreecommitdiff
path: root/internal/cli/yearview.go
blob: 6b8375c2e822aae710b243ddb4a44f8bc38910c9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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
}