aboutsummaryrefslogtreecommitdiff

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).

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

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).

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:

lectio
lectio today --all --width 100

date D — same, for an arbitrary date:

lectio date 2026-12-25 --all

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

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):

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.

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)

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

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

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.

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). 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) 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. lectio --cal-dump <name> writes a ready-made template into calendars/: every universal entry, commented out — uncomment a day and edit it (add -l trad to dump the 1962 form).
  • 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). Add them in either of two ways:

  • Compile them inmake 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.