aboutsummaryrefslogtreecommitdiff
path: root/internal/cli/liturgy.go
blob: af58a3445c91506fb997a1f74e397490e01458de (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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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/render"
)

// 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
	}
	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 observed celebration's
// inline propers, else the temporal lectionary for its slug, else -- for an EF
// weekday with no proper Mass -- the preceding Sunday's Mass (the 1962 rule
// that green-season ferias repeat the Sunday).
func dayReadings(sel calendar.Selection, layers []calendar.Layer, date time.Time, day calendar.LiturgicalDay) []calendar.Reading {
	var rs []calendar.Reading
	for _, m := range day.Observed.Masses {
		rs = append(rs, m.Readings...)
	}
	if len(rs) > 0 {
		return rs
	}
	if r := caldata.TemporalReadings(sel.Form, day.Observed.Slug); len(r) > 0 {
		return r
	}
	if sel.Form == "old" && day.Observed.Layer == "temporal" && date.Weekday() != time.Sunday {
		sun := date.AddDate(0, 0, -int(date.Weekday())) // preceding Sunday (Sunday = 0)
		sd := calendar.Compute(sun, sel, layers)
		return caldata.TemporalReadings(sel.Form, sd.Observed.Slug)
	}
	return nil
}

// vernacularVersion is the offline corpus used to render reading text: Wujek
// for Polish, Douay-Rheims otherwise (bt is scraped, so it is not used here).
func vernacularVersion(cfg config.Config) string {
	if cfg.TraditionalLang == "pl" || cfg.UILanguage == "pl" {
		return "wuj"
	}
	return "drb"
}

// readingLine resolves an English-authored citation and returns (display,
// text): display is the citation re-formatted in the user's configured sigla
// dialect (SiglaLang); text is the verses from version (or "" if unresolved).
// 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) (string, string) {
	engRef, ok := tbl.ParseRef("en", citation)
	if !ok {
		return citation, "" // unparseable: show as authored
	}
	display := tbl.FormatRef(cfg.SiglaLang(), engRef)
	vs, _ := bible.Lookup(version, engRef)
	parts := make([]string, 0, len(vs))
	for _, v := range vs {
		parts = append(parts, strings.TrimSpace(v.Text))
	}
	return display, 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 := readingLine(tbl, cfg, vern, r.Citation)
		fmt.Fprintf(out, "\n  %s (%s):\n", r.Part, disp)
		if txt != "" {
			for _, line := range strings.Split(render.Wrap(txt, 72), "\n") {
				fmt.Fprintf(out, "    %s\n", line)
			}
		} else {
			fmt.Fprintf(out, "    (text not in %s)\n", vern)
		}
	}
}

// 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(strings.TrimPrefix(c.Slug, "ef-"), "-", " ")
}

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"
	}
}