aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/caldata/caldata.go43
-rw-r--r--internal/caldata/stack_test.go56
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)
+ }
+}