From 7b220084cf3951c8cde0582efdfcf628afc64336 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Tue, 28 Jul 2026 12:50:31 +0200 Subject: refactor: remove the niedziela/missalemeum scrapers, bt, traditional_lang The daily view now computes entirely offline (previous commit), so retire the network path and everything that served it: - Delete internal/tradlit (missalemeum) and the niedziela scraper from internal/liturgy (fetch/store/parse + fixtures); keep Section, DayInfo and ExtractCitation. - Remove the "bt" version everywhere (render gatherBT + branches, config, i18n, web form, TUI) and bible.ToEnglishRef (Polish citation converter). A legacy config carrying "bt" migrates to "wuj" on load (config.migrateBT). - Remove the traditional_lang config field and the -g/--lang flag from all three binaries. - Drop the now-dead flags -R/--refresh, -o/--offline, -u/--update, -C/--clean and the harvest/clean commands. - New defaults: versions = wuj,vul,grb,drb; default_version = vul. Update the README (offline-by-design, no harvest/update), help text, and stale niedziela/bt doc comments. Tests updated for the offline reality; go test ./... and go vet ./... are clean, all three binaries build and run offline (OF + EF, compare, web). --- internal/tradlit/tradlit.go | 216 -------------------------------------------- 1 file changed, 216 deletions(-) delete mode 100644 internal/tradlit/tradlit.go (limited to 'internal/tradlit/tradlit.go') diff --git a/internal/tradlit/tradlit.go b/internal/tradlit/tradlit.go deleted file mode 100644 index b464e56..0000000 --- a/internal/tradlit/tradlit.go +++ /dev/null @@ -1,216 +0,0 @@ -// Package tradlit fetches and parses the Traditional (1962 Missal) Latin -// propers from the missalemeum JSON API into the shared liturgy.Section -// type, so the rest of the app can treat the traditional and modern -// (niedziela.pl) lectionaries interchangeably. -package tradlit - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "regexp" - "strings" - - "github.com/lukaszkasprzak/lectio/internal/liturgy" -) - -// baseURL is the missalemeum proper-of-the-day API template ("%s" is lang, -// then date, YYYY-MM-DD). It is a package var so tests can point it at an -// httptest server. -var baseURL = "https://www.missalemeum.com/%s/api/v5/proper/%s" - -// userAgent is sent on every fetch; matches the browser UA used elsewhere -// in this project (see the plan's Global Constraints). -const userAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 " + - "(KHTML, like Gecko) Chrome/124.0 Safari/537.36" - -// citationRe matches the first *...* marker in a section's body text, e.g. -// "*Luke 7:36-50*" or "*Ps 44:2*". -var citationRe = regexp.MustCompile(`\*([^*\n]+)\*`) - -// apiResponse mirrors the shape of the missalemeum proper-of-the-day API: -// a single-element list of { info, sections }. -type apiResponse struct { - Info apiInfo `json:"info"` - Sections []apiSection `json:"sections"` -} - -// apiInfo mirrors the missalemeum API's "info" object: the day's celebration -// title, its temporal context ("tempora"), and its liturgical colour code(s) -// -- see dayInfoFromAPI, which turns it into a liturgy.DayInfo. -type apiInfo struct { - Title string `json:"title"` - Tempora string `json:"tempora"` - Colors []string `json:"colors"` -} - -type apiSection struct { - ID string `json:"id"` - Label string `json:"label"` - Body [][]string `json:"body"` -} - -// tradColours maps missalemeum's single-letter liturgical colour codes to -// DayInfo's normalized colour names; a code not listed here (or an empty -// Colors list) leaves DayInfo.Colour "". -var tradColours = map[string]string{ - "w": "white", - "r": "red", - "v": "violet", - "g": "green", - "p": "rose", -} - -// dayInfoFromAPI turns a response's info object into a liturgy.DayInfo: -// Title -> Name, Tempora -> Season, and the first Colors code -> Colour via -// tradColours (unknown/missing -> ""). A zero-value apiInfo (no "info" key -// in the response) yields a zero DayInfo. -func dayInfoFromAPI(info apiInfo) liturgy.DayInfo { - colour := "" - if len(info.Colors) > 0 { - colour = tradColours[strings.ToLower(info.Colors[0])] - } - return liturgy.DayInfo{ - Name: info.Title, - Season: info.Tempora, - Colour: colour, - } -} - -// Parse decodes a missalemeum proper-of-the-day API response body into -// liturgy.Sections plus the day's liturgical identity (its "info" object, -// see dayInfoFromAPI). Sections with an empty id or empty body are skipped. -func Parse(jsonBody []byte) ([]liturgy.Section, liturgy.DayInfo, error) { - var resp []apiResponse - if err := json.Unmarshal(jsonBody, &resp); err != nil { - return nil, liturgy.DayInfo{}, fmt.Errorf("tradlit: parse: %w", err) - } - if len(resp) == 0 { - return nil, liturgy.DayInfo{}, fmt.Errorf("tradlit: parse: empty response") - } - - info := dayInfoFromAPI(resp[0].Info) - - var out []liturgy.Section - for _, sec := range resp[0].Sections { - if sec.ID == "" || len(sec.Body) == 0 || len(sec.Body[0]) == 0 { - continue - } - - text := strings.Join(sec.Body[0], "\n") - - var lines []string - for _, line := range strings.Split(text, "\n") { - line = strings.TrimSpace(line) - if line != "" { - lines = append(lines, line) - } - } - - citation := "" - if m := citationRe.FindStringSubmatch(text); m != nil { - citation = strings.TrimSpace(m[1]) - } - - out = append(out, liturgy.Section{ - Heading: sec.Label, - Citation: citation, - PartID: strings.ToLower(sec.ID), - Paragraphs: [][]string{lines}, - }) - } - return out, info, nil -} - -// cachePath returns where Load caches a (date, lang) day's raw API response: -// /.trad..json -- date-prefixed (like the modern -// lectionary's own cache files) so liturgy.CleanCache can prune it by date. -func cachePath(date, lang string) string { - return filepath.Join(liturgy.CacheDir(), date+".trad."+lang+".json") -} - -// Load returns a day's traditional propers for lang, either read from the -// on-disk cache (offline) or fetched live from missalemeum and cached for -// next time (online). Both paths share Parse, so cached and live results -// are identical. -// -// - offline: reads the cache file written by a prior online Load (see -// cachePath); if it doesn't exist, returns a clear error telling the -// caller to go online or run 'lectio update' first. -// - online: fetches https://www.missalemeum.com/{lang}/api/v5/proper/{date} -// as before. On a successful 200, the raw response body is written to -// the cache path (best-effort -- a cache-write failure never fails the -// request) before being parsed. On HTTP 404 (no propers published for -// that date) it returns a clear error and writes nothing to the cache. -func Load(date, lang string, offline bool) ([]liturgy.Section, liturgy.DayInfo, error) { - if offline { - return loadCached(date, lang) - } - return loadLive(date, lang) -} - -// loadCached implements Load's offline path. -func loadCached(date, lang string) ([]liturgy.Section, liturgy.DayInfo, error) { - body, err := os.ReadFile(cachePath(date, lang)) - if err != nil { - return nil, liturgy.DayInfo{}, fmt.Errorf("tradlit: no cached traditional propers for %s (%s); view it online or run 'lectio update' first", date, lang) - } - return Parse(body) -} - -// loadLive implements Load's online path: fetch, best-effort cache the raw -// body, then parse. -func loadLive(date, lang string) ([]liturgy.Section, liturgy.DayInfo, error) { - body, err := fetch(date, lang) - if err != nil { - return nil, liturgy.DayInfo{}, err - } - writeCache(date, lang, body) - return Parse(body) -} - -// fetch GETs the day's proper-of-the-day JSON from missalemeum and returns -// the raw response body. On HTTP 404 (no propers published for that date) -// it returns a clear error rather than the raw 404 body. -func fetch(date, lang string) ([]byte, error) { - url := fmt.Sprintf(baseURL, lang, date) - - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - return nil, fmt.Errorf("tradlit: load: %w", err) - } - req.Header.Set("User-Agent", userAgent) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, fmt.Errorf("tradlit: load %s: %w", date, err) - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusNotFound { - return nil, fmt.Errorf("tradlit: no propers published for %s", date) - } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("tradlit: load %s: unexpected status %s", date, resp.Status) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("tradlit: load %s: %w", date, err) - } - return body, nil -} - -// writeCache best-effort writes a day's raw API response body to its cache -// path; a failure to cache (e.g. an unwritable cache dir) must never fail -// the live request that produced body. -func writeCache(date, lang string, body []byte) { - path := cachePath(date, lang) - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { - return - } - _ = os.WriteFile(path, body, 0o644) -} -- cgit v1.3