summaryrefslogtreecommitdiff
path: root/README.md
blob: ca16b79a4795acfb91b93e675967dc6a98bf170d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# lectio

`lectio` is a self-contained Go rewrite of the Python `daily-reading` tool
(`ewangelia.py`). It **computes** the day's Catholic liturgical calendar and
readings entirely offline — Ordinary Form (modern) or Extraordinary Form
(1962) — and shows the text across public-domain scripture corpora with
correct psalm versification. There is no network dependency at all: the
calendar and the lectionary citations are embedded in the binary. Only the
Latin Vulgate corpus is embedded by default, so the shipped binary carries no
third-party scripture text; the other translations are opt-in (see
[Scripture corpora](#scripture-corpora)).

Three binaries share the same config and reading pipeline:

- **`lectio`** — the CLI (daily readings, version compare, passage lookup, calendar export)
- **`lectio-ui`** — a colored, scrollable Bubble Tea terminal reader
- **`lectio-web`** — a local HTMX browser UI (bundled, no CDN — works offline)

## Install

```sh
make build     # ./lectio, ./lectio-ui, ./lectio-web in the current directory
make install   # builds and installs all three to $PREFIX/bin (default ~/.local/bin)
make uninstall # removes them again
```

`PREFIX` can be overridden, e.g. `make install PREFIX=/usr/local`.

Cross-compiled binaries for linux/darwin (amd64+arm64) and windows/amd64
land in `dist/` via `make cross`.

Requires Go 1.24+ to build. No other dependencies (`go.mod` only pulls in
Bubble Tea/Lipgloss for the TUI and go-toml for config parsing).

## `lectio` — the CLI

```
lectio [today]  [--all] [--raw] [--width N] [--refresh]
lectio date D   [--all] [--raw] [--width N] [--refresh]       D = YYYY-MM-DD
lectio show VERSION [--date D] [--all] [--raw] [--width N] [--refresh]
lectio compare LIST [--date D] [--all] [--width N] [--refresh] [--raw]
lectio update [--days N] [--from D]
lectio --version | -v
lectio help | -h | lectio <cmd> -h
```

Versions (public-domain): `vul` (Wulgata, embedded by default), plus `wuj`
(Wujek), `grb` (Grecki), `drb` (Douay-Rheims) — opt-in (see
[Scripture corpora](#scripture-corpora)).

Flags:

- `--lectionary new|traditional` — override config `lectionary` for this run
- `--ui-lang pl|en` — override the interface/citation language for this run
- `--all` — show every reading part (1st/2nd reading, psalm, acclamation,
  gospel); default is the gospel only, unless config `all = true`
- `--raw` — omit the banner/headings (for piping)
- `--width N` — wrap width; `0` detects the terminal width, else falls
  back to 80 (120 for `compare`)

Exit codes: `0` ok, `1` runtime error, `2` usage error.

### Subcommands

`today` (also the default with no subcommand) — the gospel (or `--all`)
for today, in the config's `default_version`:

```sh
lectio
lectio today --all --width 100
```

`date D` — same, for an arbitrary date:

```sh
lectio date 2026-12-25 --all
```

`show VERSION` — one version's text, defaulting to today:

```sh
lectio show wuj --date 2026-12-25
```

`compare LIST` — several versions side by side (LIST is comma-separated;
falls back to config `versions` if omitted):

```sh
lectio compare wuj,drb --date 2026-04-05
```

### Calendar API — JSON & iCal

Emit the computed liturgical calendar (day identity + reading citations) as
JSON or iCal, offline. Honours the config's form (OF/EF) and custom calendar
layers.

```sh
lectio 2026-01-06 --format json                       # one day, JSON
lectio --format ical --year 2026 > lectio-2026.ics    # a year, iCal feed
lectio --format json --from 2026-01-01 --to 2026-01-31 # a range
lectio --format ical --year 2026 --form new           # override the form
```

`lectio-web` also serves the same data for `webcal://` subscription:

- `GET /api/calendar.json?date=…` or `?from=&to=` or `?year=` (+ optional `&form=old|new`)
- `GET /calendar.ics?year=…` or `?from=&to=` — served as `text/calendar`
- `GET /source` — plain-text source offer (see [License](#license))

The web endpoints validate every input and cap a request's span at ~5 years
(1830 days); JSON carries a stable `"schema": "lectio.calendar/1"`.

Both feeds also carry `source` / `license` (JSON) and `X-LECTIO-SOURCE` /
`X-LECTIO-LICENSE` (iCal), so a subscriber who never opens the HTML UI still
gets the AGPL source offer. If you fork lectio and run it as a service, set
`config.SourceURL` to your own repository.

## `lectio-ui` — the terminal reader

```sh
lectio-ui
```

A full-day, scrollable, colored view built on Bubble Tea. Key bindings:

| Key | Action |
|---|---|
| `tab` / `shift+tab` | cycle to next/previous version |
| `←` / `→` | previous/next day |
| `j`/`↓`, `k`/`↑` | scroll down/up one line |
| `space` / `b` | page down/up |
| `g` / `G` | jump to top/bottom |
| `q` / `ctrl+c` | quit |

Colors are a fixed light/dark-adaptive palette (Catppuccin-style, not
configurable per instance): the heading is accented, the citation line is
faint, verse numbers are muted, verse text is the default foreground, and
the header/footer bars use an inverted background. Colors degrade
automatically to plain text when `NO_COLOR` is set or the terminal doesn't
support them.

## `lectio-web` — the browser UI

```sh
lectio-web
```

Starts a local HTTP server, prints its URL, and best-effort opens it in
your default browser (failure to open — headless box, no browser — is
silent; just visit the printed URL). The port comes from config
`web_port`: `0` (the default) tries `1099` first, then falls back to any
free OS-assigned port if `1099` is taken; a non-zero value binds that
port exactly and fails if it's already in use.

All static assets (htmx, base CSS, theme CSS) are embedded in the binary
and served locally — there is no CDN, so the page works fully offline
once running.

Page controls:

- **Date navigation** — a date picker plus `←`/`→` buttons
- **Lekcjonarz (lectionary)** — new vs traditional, same as `--lectionary`
- **Version checkboxes** — wuj/vul/grb/drb, independent of any one
  visitor's config
- **Zakres (all-parts toggle)** — gospel-only vs every part, same as `--all`
- **Układ (display mode)** — horizontal / vertical / interlinear, see below
- **Motyw (theme picker)** — swaps the theme stylesheet client-side
  instantly, no server round trip

### Display modes (`web_display`)

- **horizontal** (default) — each version's text stacked in its own block
  under a shared heading
- **vertical** — the same per-version text laid out as side-by-side
  columns, like the CLI's `compare`
- **interlinear** — every version interleaved verse-by-verse by
  chapter:verse

## Configuration

Config file: `~/.config/lectio/config.toml` (XDG-aware; auto-seeded with
the built-in defaults on first run), or the path in `$LECTIO_CONFIG` if
set. An invalid file falls back to defaults with a warning on stderr.

```toml
schema_version   = 1
lectionary       = "new"    # "new" (Ordinary Form) or "traditional" (Extraordinary Form, 1962)
versions         = ["wuj", "vul", "grb", "drb"]  # compare set + TUI cycle order; default = corpora available in the build (vul only unless you add more)
default_version  = "vul"    # TUI start / `lectio show` default
width            = 0        # CLI wrap width; 0 = detect terminal
all              = false    # default to all parts (true) or just the gospel (false)
reading_version  = ""       # force a reading corpus (e.g. "wuj", "drb"); blank = auto/Latin
web_theme        = "transfiguration"  # built-in order/season theme or a user theme in ~/.config/lectio/themes/
web_port         = 0        # lectio-web port; 0 = try 1099, then any free port
web_display      = "horizontal"  # lectio-web layout: "horizontal" (stacked), "vertical" (columns), "interlinear" (verse-by-verse)

# Which parts to show. Both tables are commented out -> every part is shown.
# Uncomment a table and set a part to false to hide it; parts you don't list
# stay shown. (Parsed as a map: a part is hidden only if explicitly false.)
#
# [parts.new]
# pierwsze_czytanie = true   # 1. czytanie (1st reading)
# psalm             = true   # Psalm
# drugie_czytanie   = true   # 2. czytanie (2nd reading, on feasts)
# aklamacja         = true   # Aklamacja (acclamation)
# ewangelia         = true   # Ewangelia (gospel)
#
# [parts.traditional]
# introitus    = true   # Introit
# oratio       = true   # Collect
# lectio       = true   # Epistle
# graduale     = true   # Gradual / Alleluia / Tract
# evangelium   = true   # Gospel
# offertorium  = true   # Offertory
# secreta      = true   # Secret
# communio     = true   # Communion
# postcommunio = true   # Postcommunion
```

CLI flags (`--lectionary`, `--ui-lang`, `--all`, …) and `lectio-web` query
parameters override the corresponding config values for that run/request
only; the file itself is never rewritten.

## Themes

`lectio-web` ships 14 built-in, colour-only themes (they set colours — with
all spacing, borders and layout fixed in `base.css`). Each theme now also
sets a distinct typeface — serif variants (plain, old-style, classic or
Iowan), humanist sans, or plain sans — so themes are told apart by face as
well as palette, on top of which a monospace reading face can be turned on
for any theme (see `web_mono` and the "mono" top-bar toggle below):

Religious orders: `transfiguration` (dark, gold/olive, serif — the
default), `desert_fathers` (light, sand/umber, Iowan serif), `benedictines`
(dark, black/gold, old-style serif), `franciscans` (dark, brown/terracotta,
humanist sans), `memento_mori` (dark, bare monospace — no role colours, just
plain text), `dominicans` (black & white, classic serif).

Liturgical seasons and feasts: `advent` (dark violet, classic serif),
`nativity` (light white/gold, old-style serif), `lent` (dark ashen violet,
serif), `easter` (light radiant gold, Iowan serif), `pentecost`
(red/white/gold, humanist sans), `ordinary` (light green, sans), `epiphany`
(dark indigo/gold, serif), `marian` (light blue/white/gold, old-style
serif).

Set the default with `web_theme` in config, or pick one live from the
"motyw" dropdown in the browser UI.

To write your own theme, drop a CSS file defining the required role
variables/classes (`--bg`, `--fg`, `.heading`, `.citation`, `.vnum`,
`.refrain`, link colours, border colours — see `docs/THEMES.md` for the
full list and a template) at:

```
~/.config/lectio/themes/<yourname>.css
```

Restart (or reload) `lectio-web`; `<yourname>` appears in the theme list
and overrides any built-in theme of the same name.

## Offline by design

Everything the engine needs ships in the binary — there is no cache, no harvest
step, and no network access. For any date, `lectio` computes the liturgical day
(season, precedence, calendar of saints) and resolves its reading citations from
the embedded lectionary tables, then renders the verse text from a scripture
corpus — the embedded Latin Vulgate, or another corpus you compiled in or
dropped into the corpora dir (see [Scripture corpora](#scripture-corpora)).
Reading citations are stored in English-canonical form; the
Psalms are renumbered per corpus (Hebrew→Vulgate for `wuj`/`vul`/`grb`, kept
modern for `drb`), and the citation shown in the heading is formatted in your
sigla dialect (`sigla_style` / `ui_language`).

The reading text comes from the configured `reading_version` (else the corpus
that matches your language, else the complete Latin Vulgate). Where the chosen
corpus lacks a passage, the `-L`/liturgy view falls back to the Latin Vulgate,
which carries the full canon.

> The modern Polish text (Biblia Tysiąclecia) is copyrighted and cannot be
> embedded; the public-domain Polish corpus is Wujek (`wuj`), which is
> ~16th-century and incomplete. Add `wuj` (build with `make build-full`, or drop
> `wuj.tsv`/`wuj.ini` into your corpora dir — see [Scripture corpora](#scripture-corpora))
> and set `reading_version = "wuj"` for Polish text, or keep the default Latin
> Vulgate for a complete, consistent reading.

## Lectionaries

Both are computed offline from the embedded calendar engine and lectionary
data — no external service:

- **new** (default) — the Ordinary Form (post-1969) calendar and lectionary,
  with the 3-year Sunday cycle (A/B/C) and 2-year weekday cycle (I/II).
- **traditional** — the Extraordinary Form (1962 Roman Missal): the temporal
  and sanctoral of the day with its epistle and gospel.

Switch per run with `--lectionary new|traditional`, or set `lectionary`
in config for a permanent default.

## Calendar of saints

The universal sanctorale (saints, ranks, colours, names) is embedded and
oracle-validated, so it works out of the box. Because the Church changes it over
time, it can be updated or customised without a rebuild, in two tiers — both plain
INI:

- **Local additions overlay on top.** Drop `~/.config/lectio/calendars/<name>.ini`
  and enable it with `use = <name>` (comma list, later wins). A layer **adds** a
  saint, **overrides** any field of an existing one (rank, colour, name, readings),
  or **suppresses** it (`suppress = true`). This is the place for diocesan/national
  propers and one-off rank fixes — you only write the deltas. `examples/sanctorale-of.ini`
  is a ready-made template: every universal entry, commented out — copy it into
  `calendars/`, uncomment a day, and edit it.
- **Replace the whole base.** Drop `~/.config/lectio/sanctorale/of.ini` (Ordinary
  Form) or `ef.ini` (Extraordinary Form) and it is used *instead of* the embedded
  calendar — for shipping a full refreshed calendar (regenerate with
  `scripts/gen-sanctoral.go`). A missing or malformed file falls back to the
  embedded default, so the tool stays self-contained and correct.

The temporal cycle and the precedence rules (Table of Liturgical Days) are
computed in code — only the sanctorale is data.

## Scripture corpora

Only the Latin **Vulgate** (`vul`) — public domain, lectio's own hand-curation —
is embedded in the default binary. The other translations (`wuj`, `drb`, `grb`)
are kept out of the shipped binary so it carries no third-party scripture text,
and they are **not covered by lectio's licence** (see [License](#license)). Add
them in either of two ways:

- **Compile them in** — `make build-full` (or `go build -tags fullbible ...`)
  embeds `wuj`, `drb` and `grb` alongside the Vulgate. Do this only where you may
  redistribute those corpora.
- **Drop them in at runtime** — place `<code>.tsv` and `<code>.ini` in your
  corpora dir (`~/.config/lectio/corpora/`). The repo ships the files under
  `internal/bible/corpora_optional/`; copy the ones you want. A corpus found
  there is used by all three binaries without a rebuild and overrides an embedded
  corpus of the same code.

Add your own translation the same way: a tab-separated `<code>.tsv`
(`book⇥abbr⇥booknum⇥chapter⇥verse⇥text`) plus a `<code>.ini` sidecar (`lang`,
`name`, `psalm_system`); validate with `lectio --corpus-check <path>`. A version
code lectio can't find renders as `(not in <code>)` — it never fails the day.

## Localising to your language

`ui_language` accepts **any** code, and *everything visible* is translatable from
plain-text files — no rebuild. Set `ui_language = <code>` and provide the files
below; each overrides the English baseline key by key, so a partial translation
just leaves the rest in English. The reading text stays in its source language
(that comes from the corpus, not the interface).

- **Interface chrome** (button labels, keybar, menu words, column headers) lives
  in `<code>.ini` in your UI dir (`~/.config/lectio/ui/`). English and Polish ship
  built in; use the shipped `internal/i18n/lang/en.ini` as the full key list to
  translate (e.g. `bookmarks = Zakładki`, `banner_gospel = Évangile`). Only the
  built-in `en`/`pl` need no file.
- **Day names** (e.g. "3rd Sunday in Ordinary Time") are generated. English is
  built in and Polish ships with lectio: `ui_language = pl` →
  "3. Niedziela Okresu Zwykłego". To add or customise a language, drop a
  `<code>.ini` into your names dir (`~/.config/lectio/names/`) and set
  `ui_language = <code>`. It overrides the English baseline key by key — translate
  only what you want; the rest stays English. Use the shipped
  `internal/naming/lang/pl.ini` as a template: it maps weekdays, months, season
  names, ordinals and a few format templates, so word order and grammatical case
  can follow the target language.
- **Saint names** live in the calendar data (`name.<lang>`). English, Latin and
  Polish are complete for the universal calendar. To supply or override a saint's
  name in another language, add a calendar layer
  (`~/.config/lectio/calendars/<layer>.ini`) with `name.<code> = …` entries and
  enable it via `use`. A name lectio lacks in your language falls back to English,
  then Latin, then the generated day name.

## License

Copyright (C) 2026 Łukasz Kasprzak

lectio is free software: you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>.

Because this is the **Affero** GPL, section 13 applies to `lectio-web`: if you
run a modified version and let people reach it over a network, you must offer
those users the source of your modified version.

The AGPL covers lectio's own code and data. It does **not** cover the bible
corpora: the embedded Latin Vulgate and the opt-in `wuj`/`drb`/`grb` are separate
public-domain scripture texts with their own provenance, redistributed under
their own terms, not lectio's. See `NOTICE`.

Full text in `LICENSE`; third-party data attributions in `NOTICE`.