diff options
| author | Łukasz <lukasz@arcofasiagroup.com> | 2026-07-23 11:50:16 +0200 |
|---|---|---|
| committer | Łukasz <lukasz@arcofasiagroup.com> | 2026-07-23 11:50:16 +0200 |
| commit | 6d31f48ee0603b847223722ee0179dda08cb529c (patch) | |
| tree | 2dd2fd22d81fc1217e27138b3858ab8b268bd070 /docs/superpowers | |
| download | lectio-6d31f48ee0603b847223722ee0179dda08cb529c.tar.gz lectio-6d31f48ee0603b847223722ee0179dda08cb529c.zip | |
Design spec: lectio Go rewrite (CLI + colored TUI, self-contained)
Forked from the Python daily-reading tool. Two binaries (lectio,
lectio-ui) over shared internal packages, modelled on bread-calc:
Bubble Tea + Lipgloss TUI, go:embed for the four Bible corpora
(self-contained, no external tools), subcommand CLI, TOML config,
colored reader TUI, psalm versification ported as-is.
Diffstat (limited to 'docs/superpowers')
| -rw-r--r-- | docs/superpowers/specs/2026-07-23-lectio-go-rewrite-design.md | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-07-23-lectio-go-rewrite-design.md b/docs/superpowers/specs/2026-07-23-lectio-go-rewrite-design.md new file mode 100644 index 0000000..9cc8cb3 --- /dev/null +++ b/docs/superpowers/specs/2026-07-23-lectio-go-rewrite-design.md @@ -0,0 +1,269 @@ +# lectio — design spec + +Date: 2026-07-23 +Status: approved (brainstorm), pre-plan + +## Goal + +Reimplement the Python `daily-reading` tool (`ewangelia.py`) as a self-contained +Go project with two binaries: + +- **`lectio`** — terminal CLI (subcommand-based). +- **`lectio-ui`** — TUI reader (Bubble Tea), modelled on the user's `bread-calc`. + +It fetches the daily Catholic liturgy readings from niezbednik.niedziela.pl and +shows them in Polish plus four other versions (Wujek Polish, Vulgate Latin, +Greek, Douay-Rheims English), with correct psalm versification across them. + +This is a **fork**: the Python `daily-reading` project is left untouched and +keeps working. `lectio` is a fresh project. + +## Non-goals + +- Offline lectionary computation (computing the day's citations without the + site) remains the separate `offline_readings` effort. `lectio` fetches the + site like `ewangelia.py`; when an offline engine exists it slots in behind + `internal/liturgy` without changing consumers. +- No new translations beyond the five already supported. + +## Key decisions + +| Decision | Choice | +|---|---| +| Language / stack | Go; Bubble Tea + Lipgloss (TUI), go-toml/v2 (config). Match bread-calc. | +| Binaries | `lectio` (CLI), `lectio-ui` (TUI). | +| Self-contained | Embed all four Bible corpora via `go:embed`; verse lookup in Go. No external `vul`/`grb`/`wuj`/`drb` tools. | +| Daily data | Still fetched from niedziela.pl (source of citations + Polish text); cached on disk. | +| CLI shape | Subcommands (hand-rolled dispatch, no cobra). | +| TUI shape | Reader + version-switch: full day in one pane, `tab` cycles versions, arrows change date. | +| Config | `~/.config/lectio/config.toml`, auto-seeded, flags override. | +| Versification | Port `psalm_versify.py` behaviour exactly. | + +## Project layout + +Module: `github.com/lukaszkasprzak/lectio`. Location: `~/git/projects/lectio`. + +``` +cmd/lectio/main.go -> cli.Run(args, stdin, stdout, stderr) int +cmd/lectio-ui/main.go -> load config+data, tui.New(...), tea.NewProgram(...).Run() +internal/liturgy/ fetch + parse niedziela.pl into []Section +internal/bible/ embedded corpora + reference lookup + book aliases +internal/psalter/ psalm versification (port of psalm_versify.py) +internal/render/ text rendering: compare columns, section text (CLI) +internal/config/ TOML config load + auto-seed +internal/cli/ subcommand dispatch +internal/tui/ Bubble Tea reader +Makefile, README.md, LICENSE, go.mod +``` + +Each `internal/*` package has one clear purpose, a small exported surface, and +is unit-testable in isolation. CLI and TUI are thin front-ends over the same +core (liturgy + bible + psalter + render). + +## Data architecture + +### Embedded corpora +`internal/bible` embeds four TSV files with `go:embed`: + +- `wuj.tsv` — Biblia Wujka (Polish), from `offline_readings/corpus/wuj.tsv`. +- `drb.tsv` — Douay-Rheims (English), from `offline_readings/drb/drb.tsv`. +- `vul.tsv` — Vulgate (Latin), extracted from the installed `vul` tool + (`sed '1,/^#EOF$/d' $(command -v vul) | tar xzf - -O vul.tsv`). +- `grb.tsv` — Greek, extracted from the installed `grb` tool the same way. + +All four share the 6-column format `Book | Abbrev | BookNum | Chapter | Verse | Text` +(the kjv-family format). Total ~15 MB, embedded into the binary. + +### Reference lookup (replaces the awk engine + shell-out) +`internal/bible` reimplements the reference grammar in Go. It must support the +subset the tool actually feeds it: + +- Book match: canonical English name (`John`), English prefix (`Joh`), and the + Polish/English alias table (`J`, `Łk`, `1 Kor`, `Jana`, `Mdr`, ...) resolved + to a canonical book. Longest alias wins; exact before prefix (fixing the old + `J`→Joshua bug). +- Chapter+verse forms: `Book C:V`, `Book C:V-V` (range), `Book C:V,V,...` + (list). Mixed lists like `John 20:1,11-18` are split into single-group + queries and merged (port of `split_ref`). +- Returns `[]Verse{Chapter, Verse, Text}` in reference order; reports the groups + a corpus lacked (for the "brak w …" note). + +### Daily readings +`internal/liturgy`: + +- `Fetch(date, refresh) (html, error)` — GET + `https://niezbednik.niedziela.pl/liturgia/{date}/Ewangelia` with a browser + User-Agent; cache to `~/.cache/lectio/{date}.html` (honor `XDG_CACHE_HOME`). + Cache only fully-published pages. +- `Parse(html) ([]Section, error)` — pick the **new** lectionary tab + (`tabnowy0all`), fall back to `tabstary0all` with a warning; extract each + section's heading, subtitle, citation, and paragraphs. Fail loudly if the tab + is present but empty or absent (layout change). +- `Section{Heading, Subtitle, Citation, Paragraphs}`. + +## Versions and psalm systems + +Ported from `ewangelia.py` constants: + +``` +versions: pl, wuj, vul, grb, drb (canonical order; = config `versions` default) +labels: pl="Polski (niedziela.pl)" wuj="Wujek (pol.)" vul="Wulgata (lac.)" + grb="Grecki" drb="Douay-Rheims (ang.)" +bible tools: wuj, vul, grb, drb (pl comes from the fetched paragraphs) +psalm system: vul/grb/wuj -> "vulgate" drb -> "drb" +``` + +`lectio show VERSION` accepts any of the five (pl renders the fetched paragraphs; +the others render looked-up verses). + +`pl` renders the fetched Polish paragraphs (dropping the "Słowa Ewangelii" +incipit, and de-duplicating a repeated responsorial refrain). The four bible +versions render verses looked up from the embedded corpora, with the citation +converted per that version's psalm system. + +## Reference conversion & psalm versification + +`internal/bible` (conversion) + `internal/psalter` (versification) port +`to_english_ref` / `_psalm_ref` / `psalm_versify.py`: + +- Strip a leading `por.` (compare marker) and a trailing `(R.: ...)` + responsorial refrain from the citation. +- Convert the Polish citation to English style and normalise verse groups + (`Mt 7, 1-5` -> `Mat 7:1-5`; disjoint groups preserved as a comma list). +- Psalms: the lectionary cites `Ps H (V)`. The Vulgate versions use chapter `V`, + verses unchanged. `drb` uses chapter `H` and shifts each verse by the + title-fold count `k` for that psalm: `drb_verse = lectionary_verse - k` + (clamped ≥ 1). `k` comes from the `DRB_TITLE_FOLD` table (1 or 2 title lines; + 0 for untitled), derived by aligning the Wujek/DRB corpora. ~140 psalms exact, + ~10 approximate (documented in the package). + +## CLI surface (`internal/cli`) + +Hand-rolled subcommand dispatch. `lectio` with no args = `lectio today`. + +``` +lectio today [--all] [--raw] [--width N] [--refresh] +lectio date D [--all] [--raw] [--width N] [--refresh] # D = YYYY-MM-DD +lectio compare LIST [--date D] [--all] [--width N] [--refresh] + LIST = comma versions (default: config `versions`) +lectio show VERSION [--date D] [--all] [--refresh] # one version's text +lectio --version | lectio -v +lectio help | lectio -h | lectio <cmd> -h +``` + +- `--all` shows every reading (1st/2nd, psalm, acclamation, gospel); default is + the gospel only, unless config `all = true`. +- `--raw` drops banner/headings for piping. +- `--refresh` bypasses the cache. +- Flags override config. Exit codes: 0 ok, 1 runtime error (fetch/parse), 2 + usage error. + +## TUI (`internal/tui`) + +Bubble Tea Elm architecture, per the approved mockup (reader + version-switch): + +- One scrolling pane showing the whole day's readings for the **active version**. +- Header: date + active version label. Footer: keybar. +- Keys: `tab`/`shift+tab` cycle versions (order = config `versions`, starting at + `default_version`); `←/→` change date (async re-fetch with a loading state); + `j/k` and `space`/`b` scroll; `g/G` top/bottom; `r` refresh; `q`/`ctrl+c` quit. +- Async fetch via `tea.Cmd` returning a `readingsMsg` or `errMsg`; a spinner or + "ładowanie…" line while in flight. +- Lipgloss styling, theme-neutral (works on light/dark terminals); width-aware + wrapping from `tea.WindowSizeMsg`. +- `Model{ config, cache, date, version, sections, scroll, width, loading, err }`. + +The TUI shows one version at a time (not columns); the CLI `compare` is where +side-by-side lives. + +### Colour scheme +Colours distinguish the parts of a reading. Each role is a named Lipgloss style +in one place (`internal/tui`), using `lipgloss.AdaptiveColor` so it reads on both +light and dark terminals; a `NO_COLOR` env / non-TTY output degrades to plain. + +| Role | Style | +|---|---| +| Section heading (`1. czytanie`, `Ewangelia`) | bold, accent colour | +| Citation (`Pnp 8, 6-7`) | dim / muted, next to the heading | +| Verse number (`20:1`) | distinct muted colour, separated from text | +| Verse text | default foreground | +| Responsorial refrain (psalm) | italic / secondary colour | +| Header (date + active version) | accent background or bold | +| Footer keybar | dim | + +Exact hues are finalised in planning, but the role → style mapping above is +fixed. The CLI stays plain text (colour is a TUI concern). + +## Config (`internal/config`) + +`Config` struct loaded via go-toml. Resolution: `LECTIO_CONFIG` env → +`~/.config/lectio/config.toml` (auto-seeded from an embedded default on first +run, honoring `XDG_CONFIG_HOME`) → built-in defaults. Flags override. + +```toml +schema_version = 1 +versions = ["pl", "wuj", "vul", "grb", "drb"] # compare set + TUI cycle order +default_version = "pl" # TUI start / `lectio show` default +width = 0 # CLI wrap width; 0 = detect terminal +all = false # default to all readings (true) or just the gospel (false) +``` + +Unknown versions in config are rejected with a clear error. Invalid TOML falls +back to defaults with a stderr warning. + +## Porting map (Python → Go) + +| Python (`ewangelia.py` / `psalm_versify.py`) | Go | +|---|---| +| `fetch`, `parse_sections`, `html_to_lines`, `extract_reference` | `internal/liturgy` | +| awk engine, `run_bible_tool`, `split_ref` | `internal/bible` (lookup) | +| `POLISH_TO_EN`, `to_english_ref`, `_psalm_ref` | `internal/bible` (aliases + conversion) | +| `psalm_versify.py` (`DRB_TITLE_FOLD`, `drb_verse`, chapter map) | `internal/psalter` | +| `gather_version`, `run_bible_section`, `render_compare`, `render_section`, refrain dedup | `internal/render` | +| `VERSION_LABELS`, `PSALM_SYSTEM`, `COMPARE_CODES`, `BIBLE_TOOLS` | `internal/bible` / `internal/render` constants | +| `argparse` main | `internal/cli` | + +## Error handling + +- Network failure / unpublished date: clear stderr message, exit 1 (CLI) or an + error line in the TUI (stay usable, let the user change date). +- Layout change (tab missing/empty): explicit "site layout may have changed" + error, exit 1. +- A corpus lacking a passage (e.g. deuterocanonical book absent from a version): + per-section note, not a crash; other versions still render. +- Unknown version code: usage error, exit 2. + +## Testing + +- **bible**: table tests for book matching (incl. `J`→John, `Łk`→Luke, `1 Kor`), + reference grammar (ranges, lists, split groups), and known verses across all + four corpora (Gen 1:1, John 20:1, a deuterocanonical, a psalm). +- **psalter**: unit tests for `drb_verse` on titled/untitled/2-line-title psalms. +- **liturgy.Parse**: fixture tests against a few cached HTML pages (a normal day, + a split-reading feast, an unpublished date), asserting sections/citations. +- **render**: golden-text tests for a compare block and a section, including the + refrain dedup and psalm-number divergence. +- **config**: load/seed/override tests with a temp `XDG_CONFIG_HOME`. +- `go vet ./...` clean; `gofmt`-formatted. + +## Build & install + +Makefile cloned from bread-calc: + +``` +make build -> ./lectio and ./lectio-ui +make install -> $(PREFIX)/bin (default ~/.local/bin) +make cross -> dist/ for linux/darwin/windows amd64+arm64 +make test | vet | fmt | clean +``` + +`.gitignore`: `/lectio`, `/lectio-ui`, `dist/`, `*.test`, coverage, cache. + +## Open items (decide during planning) + +- Exact Lipgloss hues for the colour roles (mapping is fixed in the TUI section; + only the specific colours are open). +- Whether `lectio show` and the TUI share a single "gather one version" function + (they should). +- Whether to vendor the extracted `vul.tsv`/`grb.tsv` into the repo or fetch them + at build time (lean: vendor them under `internal/bible/`, like wuj/drb). |
