aboutsummaryrefslogtreecommitdiff
path: root/internal/cli/liturgy.go
blob: 24052ab9a3046fc12670932488231e70155a923e (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
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
	}
	// The computed engine currently does the Ordinary Form only. Rather than
	// silently return OF for a traditional (EF) config, say so.
	if cfg.Selection().Form == "old" {
		fmt.Fprintln(stderr, "lectio: the offline calendar engine currently computes only the Ordinary Form.")
		fmt.Fprintln(stderr, "        Traditional (1962 EF) computation is not built yet; your traditional daily")
		fmt.Fprintln(stderr, "        readings still work via 'lectio [DATE]'. To compute the OF: add --lectionary new.")
		return 1
	}
	dir, _ := config.CalendarsDir()
	layers, errs := caldata.Stack(dir, cfg.Use)
	for _, e := range errs {
		fmt.Fprintln(stderr, "lectio: warning:", e)
	}
	day := calendar.Compute(d.UTC(), cfg.Selection(), layers)
	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"
	}
}