summaryrefslogtreecommitdiff
path: root/internal/liturgy/fetch.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-24 10:32:28 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-24 10:32:28 +0200
commitc9f3bf46f0de09edb796e35f3dcff349febf75c9 (patch)
treec1a0b98b484e4da557c1ab205dfff0d92ce8ac36 /internal/liturgy/fetch.go
parenta865374755480a4aef2a6156afccdf08a91422ea (diff)
downloadlectio-c9f3bf46f0de09edb796e35f3dcff349febf75c9.tar.gz
lectio-c9f3bf46f0de09edb796e35f3dcff349febf75c9.zip
dayinfo: show feast/day name + colour (modern niedziela + traditional missalemeum) in cli/tui/web; v0.5.0
Diffstat (limited to 'internal/liturgy/fetch.go')
-rw-r--r--internal/liturgy/fetch.go84
1 files changed, 52 insertions, 32 deletions
diff --git a/internal/liturgy/fetch.go b/internal/liturgy/fetch.go
index 8c887f4..e66b77f 100644
--- a/internal/liturgy/fetch.go
+++ b/internal/liturgy/fetch.go
@@ -76,25 +76,29 @@ func CacheDir() string {
return filepath.Join(base, "lectio")
}
-// Load returns the day's reading sections, from the modern (niedziela.pl)
-// source, preferring cache over network:
+// Load returns the day's reading sections and DayInfo, from the modern
+// (niedziela.pl) source, preferring cache over network:
//
-// 0. If Offline, skip straight to LoadOffline (the harvested sigla TSV).
-// 1. Unless Refresh, the parsed JSON cache ({date}.json).
-// 2. Unless Refresh, the raw HTML cache ({date}.html) -- parsed, and the
-// result written to the JSON cache for next time.
+// 0. If Offline, skip straight to LoadOffline (the harvested sigla TSV) --
+// that store carries citations only, so DayInfo comes back zero.
+// 1. Unless Refresh, the parsed JSON cache ({date}.json), which round-trips
+// DayInfo alongside the sections (see cachedDay/loadCache).
+// 2. Unless Refresh, the raw HTML cache ({date}.html) -- parsed (both
+// sections and DayInfo re-derived from the same HTML via Parse/
+// ParseDayInfo), and the result written to the JSON cache for next time.
// 3. Otherwise, fetch the page over the network, parse it, and -- only if
// the page is fully published -- write both cache layers. A fetch error
// here (e.g. no network) falls back to LoadOffline for this date if it
// has been harvested, and only surfaces the original fetch error if
// that fallback also fails.
-func Load(opts Options) ([]Section, error) {
+func Load(opts Options) ([]Section, DayInfo, error) {
if !dateRe.MatchString(opts.Date) {
- return nil, fmt.Errorf("invalid date %q: want YYYY-MM-DD", opts.Date)
+ return nil, DayInfo{}, fmt.Errorf("invalid date %q: want YYYY-MM-DD", opts.Date)
}
if opts.Offline {
- return LoadOffline(opts.Date)
+ secs, err := LoadOffline(opts.Date)
+ return secs, DayInfo{}, err
}
dir := CacheDir()
@@ -102,27 +106,30 @@ func Load(opts Options) ([]Section, error) {
htmlPath := filepath.Join(dir, opts.Date+".html")
if !opts.Refresh {
- if secs, err := loadJSONCache(jsonPath); err == nil {
- return secs, nil
+ if secs, info, err := loadCache(jsonPath); err == nil {
+ return secs, info, nil
}
if page, err := os.ReadFile(htmlPath); err == nil {
- secs, err := Parse(string(page))
+ pageStr := string(page)
+ secs, err := Parse(pageStr)
if err != nil {
- return nil, err
+ return nil, DayInfo{}, err
}
- writeJSONCache(jsonPath, secs)
- return secs, nil
+ info := ParseDayInfo(pageStr)
+ writeCache(jsonPath, secs, info)
+ return secs, info, nil
}
}
page, err := fetch(opts.Date)
if err != nil {
// Network is unreachable: fall back to a prior harvest of this date
- // if there is one, rather than failing outright.
+ // if there is one, rather than failing outright. The sigla store
+ // carries no DayInfo, so this path always reports it zero.
if secs, offErr := LoadOffline(opts.Date); offErr == nil {
- return secs, nil
+ return secs, DayInfo{}, nil
}
- return nil, err
+ return nil, DayInfo{}, err
}
// Parse itself reports an unpublished date as "no reading published for
@@ -130,19 +137,20 @@ func Load(opts Options) ([]Section, error) {
// page is neither cached nor returned as sections here.
secs, err := Parse(page)
if err != nil {
- return nil, err
+ return nil, DayInfo{}, err
}
+ info := ParseDayInfo(page)
// Cache only fully-published pages, so an as-yet-unpublished future date
// keeps being retried instead of caching a "no reading" placeholder.
if publishedRe.MatchString(page) {
if err := os.MkdirAll(dir, 0o755); err == nil {
_ = os.WriteFile(htmlPath, []byte(page), 0o644)
- writeJSONCache(jsonPath, secs)
+ writeCache(jsonPath, secs, info)
}
}
- return secs, nil
+ return secs, info, nil
}
// CleanCache removes cached readings whose date is before `before` from
@@ -189,23 +197,35 @@ func CleanCache(before time.Time) (removed int, freed int64, err error) {
return removed, freed, nil
}
-// loadJSONCache reads and unmarshals the parsed-sections cache file.
-func loadJSONCache(path string) ([]Section, error) {
+// cachedDay is the on-disk shape of the parsed-sections JSON cache
+// ({date}.json): the sections plus the day's liturgical identity, so a
+// cache hit round-trips DayInfo without re-parsing the HTML cache. (An
+// older cache file written before DayInfo existed was a bare JSON array,
+// not an object -- json.Unmarshal into cachedDay then fails, which
+// loadCache treats as an ordinary cache miss, falling through to the HTML
+// cache or the network like any other stale/missing cache entry.)
+type cachedDay struct {
+ Sections []Section
+ DayInfo DayInfo
+}
+
+// loadCache reads and unmarshals the parsed-sections + DayInfo cache file.
+func loadCache(path string) ([]Section, DayInfo, error) {
data, err := os.ReadFile(path)
if err != nil {
- return nil, err
+ return nil, DayInfo{}, err
}
- var secs []Section
- if err := json.Unmarshal(data, &secs); err != nil {
- return nil, err
+ var cd cachedDay
+ if err := json.Unmarshal(data, &cd); err != nil {
+ return nil, DayInfo{}, err
}
- return secs, nil
+ return cd.Sections, cd.DayInfo, nil
}
-// writeJSONCache best-effort writes the parsed sections cache; a failure to
-// cache should never fail the load itself.
-func writeJSONCache(path string, secs []Section) {
- data, err := json.Marshal(secs)
+// writeCache best-effort writes the parsed sections + DayInfo cache; a
+// failure to cache should never fail the load itself.
+func writeCache(path string, secs []Section, info DayInfo) {
+ data, err := json.Marshal(cachedDay{Sections: secs, DayInfo: info})
if err != nil {
return
}