diff options
Diffstat (limited to 'internal/caldata')
| -rw-r--r-- | internal/caldata/caldata.go | 43 | ||||
| -rw-r--r-- | internal/caldata/stack_test.go | 56 |
2 files changed, 97 insertions, 2 deletions
diff --git a/internal/caldata/caldata.go b/internal/caldata/caldata.go index 5deb895..9272fbb 100644 --- a/internal/caldata/caldata.go +++ b/internal/caldata/caldata.go @@ -6,6 +6,8 @@ package caldata import ( _ "embed" "fmt" + "os" + "path/filepath" "github.com/lukaszkasprzak/lectio/internal/calendar" "github.com/lukaszkasprzak/lectio/internal/ini" @@ -17,14 +19,51 @@ var romanCalendar []byte // Universal parses the embedded universal calendar. It panics on a malformed // embedded file (a build-time bug caught by tests), never at the request layer. func Universal() calendar.Layer { - l, err := parse(romanCalendar) + l, err := ParseLayer(romanCalendar) if err != nil { panic(fmt.Sprintf("caldata: embedded roman-calendar.ini invalid: %v", err)) } return l } -func parse(data []byte) (calendar.Layer, error) { +// LoadLayer reads and parses a user calendar layer file. id (the filename stem) +// is used as the layer's ID when the file's [layer] header omits one. +func LoadLayer(path, id string) (calendar.Layer, error) { + data, err := os.ReadFile(path) + if err != nil { + return calendar.Layer{}, err + } + l, err := ParseLayer(data) + if err != nil { + return calendar.Layer{}, fmt.Errorf("%s: %w", path, err) + } + if l.ID == "" { + l.ID = id + } + return l, nil +} + +// Stack returns the ordered layer stack for the engine: the universal base +// first, then each user layer named in use (matched to "<dir>/<id>.ini"), in +// order. A layer that fails to load is skipped and reported in the error slice +// so a single bad file never breaks calendar computation. +func Stack(dir string, use []string) ([]calendar.Layer, []error) { + layers := []calendar.Layer{Universal()} + var errs []error + for _, id := range use { + l, err := LoadLayer(filepath.Join(dir, id+".ini"), id) + if err != nil { + errs = append(errs, err) + continue + } + layers = append(layers, l) + } + return layers, errs +} + +// ParseLayer parses INI bytes (a [layer] header + [slug]/[slug/variant] +// celebration sections) into a calendar.Layer. +func ParseLayer(data []byte) (calendar.Layer, error) { secs, err := ini.Parse(data) if err != nil { return calendar.Layer{}, err diff --git a/internal/caldata/stack_test.go b/internal/caldata/stack_test.go new file mode 100644 index 0000000..b9d5bfb --- /dev/null +++ b/internal/caldata/stack_test.go @@ -0,0 +1,56 @@ +package caldata + +import ( + "os" + "path/filepath" + "testing" +) + +func TestStack(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "poland.ini"), + []byte("[layer]\nid = poland\ntype = national\n\n[our-lady-czestochowa]\ndate = 08-26\nrank = solemnity\nclass = bvm\ncolour = white\nname.pl = NMP Częstochowskiej\n"), 0o644) + os.WriteFile(filepath.Join(dir, "krakow.ini"), + []byte("[layer]\nid = krakow\ntype = diocesan\n\n[st-stanislaus]\ndate = 05-08\nrank = solemnity\nclass = saint\ncolour = red\nname.pl = Św. Stanisława\n"), 0o644) + + layers, errs := Stack(dir, []string{"poland", "krakow"}) + if len(errs) != 0 { + t.Fatalf("unexpected errors: %v", errs) + } + if len(layers) != 3 { + t.Fatalf("want 3 layers (universal+2), got %d", len(layers)) + } + if layers[0].Type != "universal" || layers[1].ID != "poland" || layers[2].ID != "krakow" { + t.Errorf("order wrong: %q %q %q", layers[0].Type, layers[1].ID, layers[2].ID) + } + if _, ok := layers[1].Cels["our-lady-czestochowa"]; !ok { + t.Error("poland layer missing its celebration") + } +} + +func TestStackMissingLayerSkips(t *testing.T) { + dir := t.TempDir() + os.WriteFile(filepath.Join(dir, "good.ini"), + []byte("[layer]\nid = good\n\n[x]\ndate = 01-02\nrank = memorial\nname.en = X\n"), 0o644) + layers, errs := Stack(dir, []string{"good", "missing"}) + if len(errs) != 1 { + t.Fatalf("want 1 error for the missing layer, got %v", errs) + } + if len(layers) != 2 { // universal + good; missing skipped + t.Fatalf("want 2 layers, got %d", len(layers)) + } +} + +func TestLoadLayerDefaultsIDToStem(t *testing.T) { + dir := t.TempDir() + // no id in the [layer] header -> defaults to the stem + os.WriteFile(filepath.Join(dir, "myparish.ini"), + []byte("[layer]\ntype = particular\n\n[dedication]\ndate = 06-15\nrank = solemnity\nname.en = Dedication\n"), 0o644) + l, err := LoadLayer(filepath.Join(dir, "myparish.ini"), "myparish") + if err != nil { + t.Fatal(err) + } + if l.ID != "myparish" { + t.Errorf("ID = %q, want myparish (from stem)", l.ID) + } +} |
