diff options
| -rw-r--r-- | internal/calendar/names.go | 151 | ||||
| -rw-r--r-- | internal/calendar/names_test.go | 27 | ||||
| -rw-r--r-- | internal/calfeed/build.go | 3 | ||||
| -rw-r--r-- | internal/cli/liturgy.go | 2 |
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 { |
