summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/calendar/names.go151
-rw-r--r--internal/calendar/names_test.go27
-rw-r--r--internal/calfeed/build.go3
-rw-r--r--internal/cli/liturgy.go2
4 files changed, 180 insertions, 3 deletions
diff --git a/internal/calendar/names.go b/internal/calendar/names.go
new file mode 100644
index 0000000..935b60c
--- /dev/null
+++ b/internal/calendar/names.go
@@ -0,0 +1,151 @@
+package calendar
+
+import (
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+var weekdayName = map[string]string{
+ "mon": "Monday", "tue": "Tuesday", "wed": "Wednesday", "thu": "Thursday", "fri": "Friday", "sat": "Saturday", "sun": "Sunday",
+ "monday": "Monday", "tuesday": "Tuesday", "wednesday": "Wednesday", "thursday": "Thursday", "friday": "Friday", "saturday": "Saturday", "sunday": "Sunday",
+}
+
+// namedDay maps a computed temporal slug (with any "ef-" prefix removed) to its
+// proper English name, for days that have one.
+var namedDay = map[string]string{
+ "triduum-thu": "Holy Thursday", "triduum-fri": "Good Friday", "triduum-sat": "Holy Saturday",
+ "maundy-thursday": "Holy Thursday", "good-friday": "Good Friday", "holy-saturday": "Holy Saturday",
+ "palm-sunday": "Palm Sunday", "passion-sunday": "Passion Sunday", "low-sunday": "Second Sunday of Easter",
+ "trinity-sunday": "The Most Holy Trinity", "trinity": "Trinity Sunday",
+ "corpus-christi": "The Body and Blood of Christ", "sacred-heart": "The Most Sacred Heart of Jesus",
+ "christ-the-king": "Our Lord Jesus Christ, King of the Universe",
+ "easter-sunday": "Easter Sunday", "pentecost": "Pentecost Sunday", "ascension": "The Ascension of the Lord",
+ "holy-family": "The Holy Family of Jesus, Mary and Joseph", "baptism-of-the-lord": "The Baptism of the Lord",
+ "epiphany": "The Epiphany of the Lord", "christmas": "The Nativity of the Lord", "nativity": "The Nativity of the Lord",
+ "circumcision": "The Circumcision of the Lord", "ash-wednesday": "Ash Wednesday",
+ "ascension-vigil": "Vigil of the Ascension", "pentecost-vigil": "Vigil of Pentecost",
+ "christmas-sunday-sun": "Second Sunday after the Nativity",
+ "mary-mother-of-god-octave-of-christmas": "Mary, the Holy Mother of God",
+}
+
+var (
+ reSundayNum = regexp.MustCompile(`^(ordinary|advent|lent|easter)-sunday-(\d+)$`)
+ reWeekday = regexp.MustCompile(`^(.+)-(\d+)-([a-z]+)$`) // <season>-<week>-<weekday>
+ reOctave = regexp.MustCompile(`^easter-octave-([a-z]+)$`)
+ reAfterAsh = regexp.MustCompile(`^lent-after-ashes-([a-z]+)$`)
+ reHolyWeek = regexp.MustCompile(`^holy-week-([a-z]+)$`)
+ reAdventDec = regexp.MustCompile(`^advent-dec-(\d+)$`)
+ reXmasDate = regexp.MustCompile(`^christmas-(dec|jan)-(\d+)$`)
+ reAftEpiph = regexp.MustCompile(`^christmas-after-epiphany-([a-z]+)$`)
+ reEmber = regexp.MustCompile(`^(september|advent)-ember-([a-z]+)$`)
+ reSeasonSun = regexp.MustCompile(`^(.+)-sunday-(\d+)$`)
+)
+
+func ordinal(n int) string {
+ s := strconv.Itoa(n)
+ if n%100 >= 11 && n%100 <= 13 {
+ return s + "th"
+ }
+ switch n % 10 {
+ case 1:
+ return s + "st"
+ case 2:
+ return s + "nd"
+ case 3:
+ return s + "rd"
+ }
+ return s + "th"
+}
+
+func titleCase(s string) string {
+ small := map[string]bool{"of": true, "the": true, "in": true, "after": true, "before": true}
+ words := strings.Fields(s)
+ for i, w := range words {
+ if i > 0 && small[w] {
+ continue
+ }
+ words[i] = strings.ToUpper(w[:1]) + w[1:]
+ }
+ return strings.Join(words, " ")
+}
+
+// seasonPhrase renders a season slug ("ordinary", "time-after-pentecost") and the
+// preposition that introduces its numbered weeks.
+func seasonPhrase(season string) (name, prep string) {
+ switch season {
+ case "ordinary":
+ return "Ordinary Time", "in"
+ case "advent", "lent", "easter", "septuagesima", "passiontide":
+ return titleCase(season), "of"
+ }
+ if rest := strings.TrimPrefix(season, "time-after-"); rest != season {
+ return titleCase(rest), "after" // e.g. "Week after Pentecost"
+ }
+ return titleCase(strings.ReplaceAll(season, "-", " ")), "of"
+}
+
+// HumanizeSlug turns a computed temporal-day slug into a proper English name (used
+// when a temporal celebration carries no explicit name).
+func HumanizeSlug(slug string) string {
+ s := strings.TrimPrefix(slug, "ef-")
+ if n, ok := namedDay[s]; ok {
+ return n
+ }
+ if m := reSundayNum.FindStringSubmatch(s); m != nil {
+ n, _ := strconv.Atoi(m[2])
+ name, _ := seasonPhrase(m[1])
+ if m[1] == "ordinary" {
+ return ordinal(n) + " Sunday in " + name
+ }
+ return ordinal(n) + " Sunday of " + name
+ }
+ if m := reOctave.FindStringSubmatch(s); m != nil {
+ return weekdayName[m[1]] + " in the Octave of Easter"
+ }
+ if m := reAfterAsh.FindStringSubmatch(s); m != nil {
+ if m[1] == "wed" {
+ return "Ash Wednesday"
+ }
+ return weekdayName[m[1]] + " after Ash Wednesday"
+ }
+ if m := reHolyWeek.FindStringSubmatch(s); m != nil {
+ return weekdayName[m[1]] + " of Holy Week"
+ }
+ if m := reAdventDec.FindStringSubmatch(s); m != nil {
+ n, _ := strconv.Atoi(m[1])
+ return "December " + strconv.Itoa(n)
+ }
+ if m := reXmasDate.FindStringSubmatch(s); m != nil {
+ n, _ := strconv.Atoi(m[2])
+ if m[1] == "dec" {
+ return "December " + strconv.Itoa(n)
+ }
+ return "January " + strconv.Itoa(n)
+ }
+ if m := reAftEpiph.FindStringSubmatch(s); m != nil {
+ return weekdayName[m[1]] + " after the Epiphany"
+ }
+ if m := reEmber.FindStringSubmatch(s); m != nil {
+ return "Ember " + weekdayName[m[2]] + " of " + titleCase(m[1])
+ }
+ if m := reSeasonSun.FindStringSubmatch(s); m != nil { // <multiword-season>-sunday-N (EF)
+ n, _ := strconv.Atoi(m[2])
+ name, prep := seasonPhrase(m[1])
+ return ordinal(n) + " Sunday " + prep + " " + name
+ }
+ if m := reWeekday.FindStringSubmatch(s); m != nil {
+ if wd, ok := weekdayName[m[3]]; ok {
+ n, _ := strconv.Atoi(m[2])
+ name, prep := seasonPhrase(m[1])
+ if n == 0 { // the pre-Palm-Sunday week of Passiontide has no week number
+ if m[1] == "passiontide" {
+ return wd + " of Passion Week"
+ }
+ return wd + " " + prep + " " + name
+ }
+ return wd + " of the " + ordinal(n) + " Week " + prep + " " + name
+ }
+ }
+ return titleCase(strings.ReplaceAll(s, "-", " "))
+}
diff --git a/internal/calendar/names_test.go b/internal/calendar/names_test.go
new file mode 100644
index 0000000..217f481
--- /dev/null
+++ b/internal/calendar/names_test.go
@@ -0,0 +1,27 @@
+package calendar
+
+import "testing"
+
+func TestHumanizeSlug(t *testing.T) {
+ cases := map[string]string{
+ "ordinary-sunday-11": "11th Sunday in Ordinary Time",
+ "ordinary-11-tue": "Tuesday of the 11th Week in Ordinary Time",
+ "advent-2-mon": "Monday of the 2nd Week of Advent",
+ "advent-dec-17": "December 17",
+ "lent-after-ashes-wed": "Ash Wednesday",
+ "lent-after-ashes-thu": "Thursday after Ash Wednesday",
+ "triduum-fri": "Good Friday",
+ "easter-octave-mon": "Monday in the Octave of Easter",
+ "christmas-jan-2": "January 2",
+ "christmas-after-epiphany-mon": "Monday after the Epiphany",
+ "trinity-sunday": "The Most Holy Trinity",
+ "ef-time-after-pentecost-4-tue": "Tuesday of the 4th Week after Pentecost",
+ "ef-passiontide-0-thursday": "Thursday of Passion Week",
+ "ef-september-ember-wed": "Ember Wednesday of September",
+ }
+ for slug, want := range cases {
+ if got := HumanizeSlug(slug); got != want {
+ t.Errorf("HumanizeSlug(%q) = %q, want %q", slug, got, want)
+ }
+ }
+}
diff --git a/internal/calfeed/build.go b/internal/calfeed/build.go
index d3962aa..216daff 100644
--- a/internal/calfeed/build.go
+++ b/internal/calfeed/build.go
@@ -1,7 +1,6 @@
package calfeed
import (
- "strings"
"time"
"github.com/lukaszkasprzak/lectio/internal/calendar"
@@ -64,5 +63,5 @@ func celebrationName(uiLang string, c calendar.Celebration) string {
if c.Slug == "" {
return "(feria)"
}
- return strings.ReplaceAll(strings.TrimPrefix(c.Slug, "ef-"), "-", " ")
+ return calendar.HumanizeSlug(c.Slug)
}
diff --git a/internal/cli/liturgy.go b/internal/cli/liturgy.go
index 5b6036e..aa3f97e 100644
--- a/internal/cli/liturgy.go
+++ b/internal/cli/liturgy.go
@@ -164,7 +164,7 @@ func celebrationName(cfg config.Config, c calendar.Celebration) string {
if c.Slug == "" {
return "(feria)"
}
- return strings.ReplaceAll(strings.TrimPrefix(c.Slug, "ef-"), "-", " ")
+ return calendar.HumanizeSlug(c.Slug)
}
func rankLabel(r calendar.Rank) string {