aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/caldata/tridentine-calendar.ini40
-rw-r--r--internal/caldata/tridentine-lectionary.ini166
-rw-r--r--internal/calendar/calendar.go62
-rw-r--r--internal/calendar/coverage_test.go75
-rw-r--r--internal/calendar/precedence.go31
-rw-r--r--internal/calendar/temporal_ef.go44
-rw-r--r--internal/config/config.go2
7 files changed, 285 insertions, 135 deletions
diff --git a/internal/caldata/tridentine-calendar.ini b/internal/caldata/tridentine-calendar.ini
index aaa9841..cce1a65 100644
--- a/internal/caldata/tridentine-calendar.ini
+++ b/internal/caldata/tridentine-calendar.ini
@@ -974,6 +974,14 @@ name.en = St. Paulinus of Nola
reading.first = 2 Cor. 8:9-15
reading.gospel = Luke 12:32-34
+[vigil-of-the-nativity-of-st-john-the-baptist]
+date = 06-23
+rank = class-2
+colour = violet
+name.en = Vigil of the Nativity of St. John the Baptist
+reading.first = Jer 1:4-10
+reading.gospel = Luke 1:5-17
+
[nativity-of-st-john-the-baptist]
date = 06-24
rank = class-1
@@ -998,6 +1006,14 @@ name.en = Sts. John & Paul
reading.first = Eccli 44:10-15
reading.gospel = Luke 12:1-8
+[vigil-of-sts-peter-paul]
+date = 06-28
+rank = class-2
+colour = violet
+name.en = Vigil of Sts. Peter & Paul
+reading.first = Acts 3:1-10
+reading.gospel = John 21:15-19
+
[sts-peter-paul]
date = 06-29
rank = class-1
@@ -1352,6 +1368,14 @@ rank = commemoration
colour = red
name.en = St. Romanus
+[vigil-of-st-lawrence]
+date = 08-09
+rank = class-3
+colour = red
+name.en = Vigil of St. Lawrence
+reading.first = Ecclus 51:1-8, 12
+reading.gospel = Matt 16:24-27
+
[lawrence]
date = 08-10
rank = class-2
@@ -1380,6 +1404,14 @@ rank = commemoration
colour = red
name.en = Sts. Hippolytus & Cassian
+[vigil-of-the-assumption]
+date = 08-14
+rank = class-2
+colour = white
+name.en = Vigil of the Assumption
+reading.first = Sir 24:23-31
+reading.gospel = Luke 11:27-28
+
[assumption-of-the-blessed-virgin-mary]
date = 08-15
rank = class-1
@@ -2289,6 +2321,14 @@ name.en = St. Thomas
reading.first = Eph 2:19-22
reading.gospel = John 20:24-29
+[vigil-of-christmas]
+date = 12-24
+rank = class-1
+colour = violet
+name.en = Vigil of Christmas
+reading.first = Rom 1:1-6
+reading.gospel = Matt 1:18-21
+
[stephen]
date = 12-26
rank = class-2
diff --git a/internal/caldata/tridentine-lectionary.ini b/internal/caldata/tridentine-lectionary.ini
index 372be6b..cef3f2d 100644
--- a/internal/caldata/tridentine-lectionary.ini
+++ b/internal/caldata/tridentine-lectionary.ini
@@ -1,30 +1,6 @@
; EF (1962) temporal lectionary, keyed by lectio's computed temporal-day slug.
; Generated from missalemeum (Divinum Officium). Epistle (first) + Gospel citations.
-[ef-advent-1-friday]
-first = Heb 13:7-17
-gospel = Matt 25:14-23
-
-[ef-advent-1-monday]
-first = Sir 51:13-17.
-gospel = Matt 13:44-52.
-
-[ef-advent-1-saturday]
-first = 2 Tim 4:1-8
-gospel = Matt 5:13-19
-
-[ef-advent-1-tuesday]
-first = Rom 10:10-18
-gospel = Mark 16:15-18
-
-[ef-advent-1-wednesday]
-first = 2 Tim 4:1-8
-gospel = Matt 5:13-19
-
-[ef-advent-2-friday]
-first = 2 Cor 10:17-18; 11:1-2
-gospel = Matt 13:44-52.
-
[ef-advent-2-monday]
first = Rom 15:4-13
gospel = Matt 11:2-10
@@ -41,42 +17,18 @@ gospel = Matt 11:2-10
first = Rom 15:4-13
gospel = Matt 11:2-10
-[ef-advent-2-wednesday]
-first = 1 Pet 5:1-4; 5:10-11.
-gospel = Matt 16:13-19
-
-[ef-advent-3-friday]
+[ef-advent-ember-fri]
first = Isa 11:1-5
gospel = Luke 1:39-47
-[ef-advent-3-monday]
-first = 2 Cor. 1:3-7
-gospel = Matt 16:24-27.
-
-[ef-advent-3-saturday]
+[ef-advent-ember-sat]
first = Eph 2:19-22
gospel = John 20:24-29
-[ef-advent-3-wednesday]
+[ef-advent-ember-wed]
first = Isa 7:10-15
gospel = Luke 1:26-38
-[ef-advent-4-friday]
-first = Rom 1:1-6
-gospel = Matt 1:18-21
-
-[ef-advent-4-thursday]
-first = Rom 1:1-6
-gospel = Matt 1:18-21
-
-[ef-advent-4-tuesday]
-first = Rom 1:1-6
-gospel = Matt 1:18-21
-
-[ef-advent-4-wednesday]
-first = Rom 1:1-6
-gospel = Matt 1:18-21
-
[ef-advent-sunday-1]
first = Rom 13:11-14
gospel = Luke 21:25-33
@@ -97,6 +49,10 @@ gospel = Luke 3:1-6
first = Acts 1:1-11
gospel = Mark 16:14-20
+[ef-ascension-vigil]
+first = Eph. 4:7-13.
+gospel = John 17:1-11.
+
[ef-ash-wednesday]
first = Joel 2:12-19
gospel = Matt 6:16-21
@@ -133,93 +89,21 @@ gospel = Luke 24:36-47
first = Acts 3:13-15; 3:17-19
gospel = John 21:1-14
-[ef-easter-2-monday]
-first = Isa 7:10-15
-gospel = Luke 1:26-38
-
[ef-easter-2-saturday]
-first = Wis 10:10-14
-gospel = Luke 14:26-33.
-
-[ef-easter-2-thursday]
-first = 1 Pet 5:1-4; 5:10-11.
-gospel = Matt 16:13-19
+first = Ecclus 24:14-16
+gospel = John 19:25-27
[ef-easter-3-saturday]
first = Ecclus 24:14-16
gospel = John 19:25-27
-[ef-easter-4-friday]
-first = 1 Pet 5:1-4; 5:10-11.
-gospel = Matt 16:13-19
-
-[ef-easter-4-monday]
-first = 1 Pet 5:1-4; 5:10-11.
-gospel = Matt 16:13-19
-
-[ef-easter-4-saturday]
-first = 2 Tim 4:1-8
-gospel = Matt 5:13-19
-
-[ef-easter-4-thursday]
-first = Ezek 1:10-14
-gospel = Luke 10:1-9
-
-[ef-easter-4-wednesday]
-first = Wis 5:1-5
-gospel = John 15:1-7
-
-[ef-easter-5-monday]
-first = 2 Tim. 2:8-10; 3:10-12.
-gospel = Matt 10:34-42
-
[ef-easter-5-saturday]
-first = 1 Tim. 5:3-10.
-gospel = Luke 7:11-16
-
-[ef-easter-5-thursday]
-first = 2 Cor. 4:5-14
-gospel = Matt 10:23-28
-
-[ef-easter-5-tuesday]
-first = 2 Cor 10:17-18; 11:1-2
-gospel = Matt 25:1-13.
-
-[ef-easter-5-wednesday]
-first = Col. 3:14-15, 17, 23-24
-gospel = Matt 13:54-58
-
-[ef-easter-6-friday]
-first = Sir 44:16-27; 45:3-20
-gospel = Matt 25:14-23
+first = Ecclus 24:14-16
+gospel = John 19:25-27
[ef-easter-6-saturday]
-first = Wis. 5:1-5
-gospel = John 14:1-13
-
-[ef-easter-6-tuesday]
-first = Wis 5:1-5
-gospel = John 15:1-7
-
-[ef-easter-6-wednesday]
-first = Eph. 4:7-13.
-gospel = John 17:1-11.
-
-[ef-easter-7-friday]
-first = Sir 31:8-11
-gospel = Luke 12:35-40
-
-[ef-easter-7-monday]
-first = Wis 7:7-14.
-gospel = Matt 5:13-19
-
-[ef-easter-7-saturday]
-first = Acts 19:1-8.
-gospel = John 14:15-21.
-
-[ef-easter-7-wednesday]
-first = Sir 31:8-11
-gospel = Matt 18:1-5
+first = Ecclus 24:14-16
+gospel = John 19:25-27
[ef-easter-8-friday]
first = Joel 2:23-24; 26-27
@@ -282,8 +166,8 @@ first = 1 Thess. 5:14-23
gospel = Matt 17:1-9
[ef-lent-1-thursday]
-first = 1 Pet 1:1-7
-gospel = Matt 16:13-19
+first = Ezech 18:1-9
+gospel = Matt 15:21-28
[ef-lent-1-tuesday]
first = Isa 55:6-11
@@ -433,6 +317,22 @@ gospel = John 10:22-38
first = Acts 2:1-11.
gospel = John 14:23-31.
+[ef-pentecost-vigil]
+first = Acts 19:1-8.
+gospel = John 14:15-21.
+
+[ef-september-ember-fri]
+first = Osee 14:2-10
+gospel = Luke 7:36-50
+
+[ef-september-ember-sat]
+first = Ezek 1:10-14
+gospel = Matt 9:9-13
+
+[ef-september-ember-wed]
+first = 2 Esd. 8:1-10
+gospel = Mark 9:16-28
+
[ef-septuagesima-sunday-1]
first = 1 Cor. 9:24-27; 10:1-5
gospel = Matt 20:1-16
@@ -458,8 +358,8 @@ first = Rom 12:16-21
gospel = Matt 8:1-13
[ef-time-after-epiphany-sunday-4]
-first = Mal 3:1-4
-gospel = Luke 2:22-32
+first = Rom 13:8-10
+gospel = Matt 8:23-27
[ef-time-after-epiphany-sunday-5]
first = Col 3:12-17
diff --git a/internal/calendar/calendar.go b/internal/calendar/calendar.go
index e0f5217..99a24b3 100644
--- a/internal/calendar/calendar.go
+++ b/internal/calendar/calendar.go
@@ -29,7 +29,8 @@ func computeEF(date time.Time, sel Selection, layers []Layer) LiturgicalDay {
if !ok {
continue
}
- if sameDay(when, date) {
+ effective := transferIfImpededEF(cel, when)
+ if sameDay(effective, date) {
cands = append(cands, candidate{Cel: cel, Temporal: false, Season: td.Season})
}
}
@@ -75,6 +76,13 @@ func computeOF(date time.Time, sel Selection, layers []Layer) LiturgicalDay {
cands = append(cands, candidate{Cel: cel, Temporal: false, Season: td.Season})
}
}
+ // Movable Marian memorials that have no fixed date (so they cannot live in the
+ // sanctoral file): Mary, Mother of the Church, and the Immaculate Heart.
+ for _, mm := range movableMemorialsOF(date.Year()) {
+ if sameDay(mm.when, date) {
+ cands = append(cands, candidate{Cel: mm.cel, Temporal: false, Season: td.Season})
+ }
+ }
obs, others := pick(cands)
day := LiturgicalDay{
@@ -104,6 +112,31 @@ func celebrationDate(cel Celebration, year int, sel Selection) (time.Time, bool)
return resolveDate(cel.Date, year, Easter(year))
}
+// movableCel is a celebration whose date is computed (relative to Easter), not
+// read from a fixed-date layer.
+type movableCel struct {
+ when time.Time
+ cel Celebration
+}
+
+// movableMemorialsOF returns the Ordinary Form's two movable Marian memorials,
+// which have no fixed calendar date: Mary, Mother of the Church (the Monday after
+// Pentecost) and the Immaculate Heart of Mary (the Saturday after the Sacred
+// Heart, i.e. easter+69).
+func movableMemorialsOF(y int) []movableCel {
+ e := Easter(y)
+ mk := func(off int, slug, en, pl string) movableCel {
+ return movableCel{when: e.AddDate(0, 0, off), cel: Celebration{
+ Slug: slug, Rank: RankMemorial, Class: ClassBVM, Colour: White, Layer: "universal",
+ Name: map[string]string{"en": en, "pl": pl},
+ }}
+ }
+ return []movableCel{
+ mk(50, "mary-mother-of-the-church", "Mary, Mother of the Church", "Najświętszej Maryi Panny, Matki Kościoła"),
+ mk(69, "the-immaculate-heart-of-the-blessed-virgin-mary", "The Immaculate Heart of the Blessed Virgin Mary", "Niepokalanego Serca Najświętszej Maryi Panny"),
+ }
+}
+
// transferIfImpeded moves a solemnity off a band-1/2 temporal day to the next
// free day. Non-solemnities are never transferred (they yield instead).
func transferIfImpeded(cel Celebration, when time.Time, sel Selection) time.Time {
@@ -126,6 +159,33 @@ func transferIfImpeded(cel Celebration, when time.Time, sel Selection) time.Time
return day
}
+// transferIfImpededEF moves an EF sanctoral feast off a date it cannot be kept
+// on. Two rules cover the 1962 cases: All Souls, when Nov 2 is a Sunday, is
+// transferred to the Monday (Nov 3); and a I class feast impeded by a I class
+// temporal day (Holy Week, the Easter octave) is pushed forward to the next free
+// day — for the Annunciation in Holy Week this lands on the Monday after Low
+// Sunday, past the whole privileged octave. Lower-rank feasts are only
+// commemorated, never transferred.
+func transferIfImpededEF(cel Celebration, when time.Time) time.Time {
+ if when.Month() == time.November && when.Day() == 2 && when.Weekday() == time.Sunday &&
+ strings.Contains(cel.Slug, "all-souls") {
+ return when.AddDate(0, 0, 1)
+ }
+ if cel.Rank != RankClass1 {
+ return when
+ }
+ day := when
+ for i := 0; i < 30; i++ {
+ b := temporalEF(day)
+ if precedenceEF(candidate{Cel: b.Cel, Temporal: true, Season: b.Season}) <= 2 {
+ day = day.AddDate(0, 0, 1) // impeded by a I class temporal day; push forward
+ continue
+ }
+ return day
+ }
+ return day
+}
+
// buildCelebration types a RawCelebration.
func buildCelebration(slug string, rc RawCelebration) Celebration {
c := Celebration{Slug: strings.TrimSpace(slug), Name: map[string]string{}, Layer: rc.Fields["layer"]}
diff --git a/internal/calendar/coverage_test.go b/internal/calendar/coverage_test.go
new file mode 100644
index 0000000..e6555b9
--- /dev/null
+++ b/internal/calendar/coverage_test.go
@@ -0,0 +1,75 @@
+package calendar_test
+
+import (
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/lukaszkasprzak/lectio/internal/caldata"
+ "github.com/lukaszkasprzak/lectio/internal/calendar"
+)
+
+func day(y int, m time.Month, d int) time.Time {
+ return time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
+}
+
+// TestEFCoverage checks the EF special days added for full coverage: the movable
+// vigils, the September/Advent Ember days, and the transfer of an impeded feast.
+// (2027: Easter is March 28.)
+func TestEFCoverage(t *testing.T) {
+ sel := calendar.DefaultSelection()
+ sel.Form = "old"
+ layers := []calendar.Layer{caldata.Tridentine()}
+
+ cases := []struct {
+ date time.Time
+ slug string
+ rank calendar.Rank
+ }{
+ {day(2027, time.September, 22), "ef-september-ember-wed", calendar.RankClass2},
+ {day(2027, time.December, 15), "ef-advent-ember-wed", calendar.RankClass2},
+ {day(2027, time.May, 5), "ef-ascension-vigil", calendar.RankClass2},
+ {day(2027, time.May, 15), "ef-pentecost-vigil", calendar.RankClass1},
+ {day(2027, time.June, 23), "vigil-of-the-nativity-of-st-john-the-baptist", calendar.RankClass2},
+ // Annunciation (Mar 25) is in Holy Week in 2027, so it is transferred to
+ // the Monday after Low Sunday (Apr 5); Mar 25 itself keeps the Triduum.
+ {day(2027, time.April, 5), "annunciation-of-the-blessed-virgin-mary", calendar.RankClass1},
+ }
+ for _, c := range cases {
+ got := calendar.Compute(c.date, sel, layers)
+ if got.Observed.Slug != c.slug || got.Observed.Rank != c.rank {
+ t.Errorf("%s: observed %q/%v, want %q/%v", c.date.Format("2006-01-02"),
+ got.Observed.Slug, got.Observed.Rank, c.slug, c.rank)
+ }
+ }
+ // The Annunciation must NOT also appear on its impeded fixed date.
+ if got := calendar.Compute(day(2027, time.March, 25), sel, layers); strings.Contains(got.Observed.Slug, "annunciation") {
+ t.Errorf("2027-03-25 (Holy Thursday) wrongly observes %q", got.Observed.Slug)
+ }
+ // All Souls: Nov 2 2025 is a Sunday, so it is transferred to Nov 3.
+ if got := calendar.Compute(day(2025, time.November, 3), sel, layers); got.Observed.Slug != "commemoration-of-all-souls" {
+ t.Errorf("2025-11-03: observed %q, want the transferred All Souls", got.Observed.Slug)
+ }
+}
+
+// TestOFMovableMemorials checks Mary, Mother of the Church, the Immaculate Heart,
+// and the two-obligatory-memorials rule.
+func TestOFMovableMemorials(t *testing.T) {
+ sel := calendar.DefaultSelection() // Ordinary Form
+ layers := []calendar.Layer{caldata.Universal()}
+
+ // Mary, Mother of the Church: Monday after Pentecost (2027: May 17).
+ if got := calendar.Compute(day(2027, time.May, 17), sel, layers); got.Observed.Slug != "mary-mother-of-the-church" {
+ t.Errorf("2027-05-17: observed %q, want mary-mother-of-the-church", got.Observed.Slug)
+ }
+ // Two obligatory memorials coincide (St Anthony of Padua + the Immaculate
+ // Heart, 2026-06-13): both become optional and the ferial is observed.
+ if got := calendar.Compute(day(2026, time.June, 13), sel, layers); got.Observed.Rank != calendar.RankFerial {
+ t.Errorf("2026-06-13: observed %q/%v, want the ferial", got.Observed.Slug, got.Observed.Rank)
+ }
+ // But Mary, Mother of the Church keeps precedence over a coinciding memorial
+ // (2028-06-05, with St Boniface).
+ if got := calendar.Compute(day(2028, time.June, 5), sel, layers); got.Observed.Slug != "mary-mother-of-the-church" {
+ t.Errorf("2028-06-05: observed %q, want mary-mother-of-the-church", got.Observed.Slug)
+ }
+}
diff --git a/internal/calendar/precedence.go b/internal/calendar/precedence.go
index 88cc0d8..3b7c0a6 100644
--- a/internal/calendar/precedence.go
+++ b/internal/calendar/precedence.go
@@ -84,5 +84,36 @@ func pick(cands []candidate) (candidate, []candidate) {
}
return sorted[i].Cel.Slug < sorted[j].Cel.Slug
})
+ // General Norms 60: when two obligatory memorials fall on the same day, both
+ // become optional and the weekday (ferial) is celebrated instead -- UNLESS one
+ // is Mary, Mother of the Church, whose 2018 institution decree gives it
+ // precedence over a coinciding obligatory memorial (then it is observed).
+ if len(sorted) >= 2 && !sorted[0].Temporal && !sorted[1].Temporal &&
+ sorted[0].Cel.Rank == RankMemorial && sorted[1].Cel.Rank == RankMemorial {
+ if i := slugIndex(sorted, "mary-mother-of-the-church"); i >= 0 {
+ motc := sorted[i]
+ sorted = append(sorted[:i], sorted[i+1:]...)
+ sorted = append([]candidate{motc}, sorted...)
+ } else {
+ for i, c := range sorted {
+ if c.Temporal {
+ ferial := sorted[i]
+ sorted = append(sorted[:i], sorted[i+1:]...)
+ sorted = append([]candidate{ferial}, sorted...)
+ break
+ }
+ }
+ }
+ }
return sorted[0], sorted[1:]
}
+
+// slugIndex returns the position of the candidate with the given slug, or -1.
+func slugIndex(cands []candidate, slug string) int {
+ for i, c := range cands {
+ if c.Cel.Slug == slug {
+ return i
+ }
+ }
+ return -1
+}
diff --git a/internal/calendar/temporal_ef.go b/internal/calendar/temporal_ef.go
index 2ead3e3..877545c 100644
--- a/internal/calendar/temporal_ef.go
+++ b/internal/calendar/temporal_ef.go
@@ -114,6 +114,10 @@ func temporalEF(date time.Time) temporalDay {
return efCel(TimeAfterPentecost, "ef-sacred-heart", White, RankClass1, 0)
case sameDay(date, efChristTheKing(y)):
return efCel(TimeAfterPentecost, "ef-christ-the-king", White, RankClass1, 0)
+ case sameDay(date, easter.AddDate(0, 0, 38)): // Wednesday: Vigil of the Ascension (II class)
+ return efCel(Easter_, "ef-ascension-vigil", White, RankClass2, 0)
+ case sameDay(date, easter.AddDate(0, 0, 48)): // Saturday: Vigil of Pentecost / Whitsun Eve (I class)
+ return efCel(Easter_, "ef-pentecost-vigil", Red, RankClass1, 0)
}
// Sundays vs ferias of the current season.
@@ -146,12 +150,52 @@ func temporalEF(date time.Time) temporalDay {
// the temporal office win, matching the 1962 occurrence rules.
rank = RankClass1
}
+ // The Ember Days of September and Advent (Wednesday, Friday, Saturday after
+ // the third Sunday of the month/season) are II class privileged ferias in
+ // violet; they displace a III class saint (only commemorated). The Lenten
+ // Ember days are already III class Lenten ferias, and the Whitsun Ember days
+ // fall inside the I class Pentecost octave handled above.
+ if es, ok := efEmberSlug(date, y); ok {
+ return efCel(season, es, Violet, RankClass2, week)
+ }
// Unique ferial slug (season-week-weekday) so proper ferias (Lent, Advent,
// Holy Week, Ember days) can key their own readings; green-season ferias
// simply have no lectionary entry and fall back to the Sunday.
return efCel(season, "ef-"+string(season)+"-"+strconv.Itoa(week)+"-"+weekdayLower(date), col, rank, week)
}
+// thirdSundayOfSeptember returns the third Sunday in September of year y.
+func thirdSundayOfSeptember(y int) time.Time {
+ d := time.Date(y, time.September, 1, 0, 0, 0, 0, time.UTC)
+ for d.Weekday() != time.Sunday {
+ d = d.AddDate(0, 0, 1)
+ }
+ return d.AddDate(0, 0, 14)
+}
+
+// efEmberSlug returns the Ember-day slug for date, if it is an Ember Wednesday,
+// Friday or Saturday of September or Advent (the day after the third Sunday +
+// 3/5/6). Returns ("", false) otherwise.
+func efEmberSlug(date time.Time, y int) (string, bool) {
+ for _, e := range []struct {
+ third time.Time
+ season string
+ }{
+ {thirdSundayOfSeptember(y), "september"},
+ {adventStart(y).AddDate(0, 0, 14), "advent"}, // 3rd Sunday of Advent
+ } {
+ switch {
+ case sameDay(date, e.third.AddDate(0, 0, 3)):
+ return "ef-" + e.season + "-ember-wed", true
+ case sameDay(date, e.third.AddDate(0, 0, 5)):
+ return "ef-" + e.season + "-ember-fri", true
+ case sameDay(date, e.third.AddDate(0, 0, 6)):
+ return "ef-" + e.season + "-ember-sat", true
+ }
+ }
+ return "", false
+}
+
func weekdayLower(d time.Time) string {
return strings.ToLower(d.Weekday().String())
}
diff --git a/internal/config/config.go b/internal/config/config.go
index d4de9c4..91944df 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -63,7 +63,7 @@ const configHeader = `# lectio configuration (INI). Full-line comments only (# o
// Version is lectio's release version, shared by every binary's
// -v/--version output (lectio, lectio-ui, lectio-web).
-const Version = "0.38.0"
+const Version = "0.39.0"
// validVersions are the five scripture versions lectio understands.
var validVersions = map[string]bool{