diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-24 15:25:50 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-24 15:25:50 +0200 |
| commit | ed7717fef53db3b3aa5593fbb2649313e75a0f31 (patch) | |
| tree | 023601285808a4704f5646bf6e4777beea6dd93a /internal | |
| parent | 1e4939874a14d8abb13e82b2273bfc5f9d653295 (diff) | |
| download | lectio-ed7717fef53db3b3aa5593fbb2649313e75a0f31.tar.gz lectio-ed7717fef53db3b3aa5593fbb2649313e75a0f31.zip | |
cli: --rand/--rand-v (random verse) + --rand-ch (random chapter) from the corpus, using the config default version; v0.16.0
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/bible/bible.go | 12 | ||||
| -rw-r--r-- | internal/bible/bible_test.go | 19 | ||||
| -rw-r--r-- | internal/bible/booktable.go | 9 | ||||
| -rw-r--r-- | internal/cli/cli.go | 100 | ||||
| -rw-r--r-- | internal/cli/cli_test.go | 29 | ||||
| -rw-r--r-- | internal/config/config.go | 2 |
6 files changed, 167 insertions, 4 deletions
diff --git a/internal/bible/bible.go b/internal/bible/bible.go index 312daeb..1603188 100644 --- a/internal/bible/bible.go +++ b/internal/bible/bible.go @@ -71,3 +71,15 @@ func Chapters(version, book string) []int { sort.Ints(chaps) return chaps } + +// CorpusBooks returns the book names present in a version's corpus, sorted +// (empty for a version with no embedded corpus, e.g. "bt"). +func CorpusBooks(version string) []string { + c := load(version) + books := make([]string, 0, len(c.books)) + for b := range c.books { + books = append(books, b) + } + sort.Strings(books) + return books +} diff --git a/internal/bible/bible_test.go b/internal/bible/bible_test.go index 804b8c4..71a2c89 100644 --- a/internal/bible/bible_test.go +++ b/internal/bible/bible_test.go @@ -52,6 +52,25 @@ func TestVul2Kings(t *testing.T) { } } +func TestCorpusBooks(t *testing.T) { + books := CorpusBooks("wuj") + if len(books) == 0 { + t.Fatal("wuj corpus has no books") + } + found := false + for _, b := range books { + if b == "John" { + found = true + } + } + if !found { + t.Errorf("wuj CorpusBooks missing John") + } + if got := CorpusBooks("bt"); len(got) != 0 { + t.Errorf("bt has no corpus, got %d books", len(got)) + } +} + func TestChapters(t *testing.T) { ch := Chapters("wuj", "John") if len(ch) == 0 || ch[0] != 1 { diff --git a/internal/bible/booktable.go b/internal/bible/booktable.go index 9ec95be..ac7ea95 100644 --- a/internal/bible/booktable.go +++ b/internal/bible/booktable.go @@ -154,6 +154,15 @@ func (t *BookTable) shortcut(dialect, canonical string) string { return "" } +// Abbrev returns the dialect's primary abbreviation for a canonical book, +// falling back to the canonical name when the dialect has no entry. +func (t *BookTable) Abbrev(dialect, canonical string) string { + if a := t.shortcut(dialect, canonical); a != "" { + return a + } + return canonical +} + // FormatRef renders a canonical English reference ("John 20:1,11-18") in the // dialect's sigla: the dialect's book shortcut plus its number style -- English // "Jn 20:1,11-18" (colon chapter:verse, comma groups); Polish "J 20, 1. 11-18" diff --git a/internal/cli/cli.go b/internal/cli/cli.go index ef00010..ff1299a 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -7,6 +7,7 @@ import ( "flag" "fmt" "io" + "math/rand" "os" "regexp" "strconv" @@ -45,6 +46,8 @@ Flags: --list list all books + abbreviations (dialect from sigla_style) --citation print the day's gospel reference (scripts/cron) and exit --week list the coming week's gospel references and exit + --rand, --rand-v print a random verse (corpus version) and exit + --rand-ch print a random chapter and exit -v, --version print the version and exit -h, --help this help @@ -63,6 +66,7 @@ Examples: lectio --list list every book + abbreviations lectio --citation today's gospel reference lectio --week 2026-07-22 a week of gospel references from a date + lectio --rand-v a random verse (for cron/prompt) Flags override config. Exit codes: 0 ok, 1 runtime error (fetch/parse), 2 usage error (bad flag, bad date, bad version, bad --lectionary/--lang). @@ -101,6 +105,7 @@ func Run(args []string, stdin io.Reader, stdout, stderr io.Writer) int { } var all, raw, refresh, offline, update, clean, pagerFlag, noPager, citation, week bool + var randV, randCh bool var bibleVer, compareList, lectionary, lang string var width int var list bool @@ -140,6 +145,9 @@ func Run(args []string, stdin io.Reader, stdout, stderr io.Writer) int { fs.BoolVar(&list, "list", false, "list all books + abbreviations (in your sigla_style)") fs.BoolVar(&citation, "citation", false, "print the day's gospel reference and exit") fs.BoolVar(&week, "week", false, "list the coming week's gospel references and exit") + fs.BoolVar(&randV, "rand", false, "print a random verse from the corpus and exit") + fs.BoolVar(&randV, "rand-v", false, "print a random verse from the corpus and exit") + fs.BoolVar(&randCh, "rand-ch", false, "print a random chapter from the corpus and exit") if err := fs.Parse(rest); err != nil { return 2 @@ -181,12 +189,21 @@ func Run(args []string, stdin io.Reader, stdout, stderr io.Writer) int { cfg.TraditionalLang = lang } - if citation || week { + if citation || week || randV || randCh { tbl, _ := bible.LoadBookTable(userBooksTOML()) - if citation { + switch { + case randV || randCh: + ver, verr := randVersion(cfg, bibleVer) + if verr != nil { + fmt.Fprintln(stderr, "lectio:", verr) + return 2 + } + return runRand(cfg, tbl, ver, randCh, raw, width, stdout, stderr) + case citation: return runCitation(cfg, tbl, date, refresh, stdout, stderr) + default: + return runWeek(cfg, tbl, date, refresh, stdout, stderr) } - return runWeek(cfg, tbl, date, refresh, stdout, stderr) } effAll := all || cfg.All @@ -693,6 +710,83 @@ func renderCompare(cfg config.Config, list, date string, all, raw bool, width in return 0 } +// randVersion picks the corpus version for --rand: an explicit -b VER (rejected +// if it has no corpus), else the configured default_version if it has a corpus, +// else the first corpus version in cfg.Versions, else "wuj". So a config whose +// default is "bt" (no corpus) still yields a random passage. +func randVersion(cfg config.Config, bibleVer string) (string, error) { + if bibleVer != "" { + if !isCorpusVersion(bibleVer) { + return "", fmt.Errorf("--rand needs a corpus version; %q has no full text (use wuj, vul, grb, drb)", bibleVer) + } + return bibleVer, nil + } + if isCorpusVersion(cfg.DefaultVersion) { + return cfg.DefaultVersion, nil + } + for _, v := range cfg.Versions { + if isCorpusVersion(v) { + return v, nil + } + } + return "wuj", nil +} + +// runRand prints a random verse (chapter=false) or whole chapter (chapter=true) +// from version's embedded corpus, for a "verse of the day" cron/pipe. It picks a +// random book -> chapter -> verse (not perfectly uniform across all verses, but +// fine for the purpose). Non-raw prints the reference (in cfg's sigla dialect) +// then the text; raw prints just the text (verse-numbered for a chapter). +func runRand(cfg config.Config, tbl *bible.BookTable, version string, chapter, raw bool, width int, stdout, stderr io.Writer) int { + books := bible.CorpusBooks(version) + if len(books) == 0 { + fmt.Fprintf(stderr, "lectio: no corpus for version %q\n", version) + return 1 + } + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + + var book string + var chap int + var verses []bible.Verse + for tries := 0; tries < 20 && len(verses) == 0; tries++ { + book = books[rng.Intn(len(books))] + chaps := bible.Chapters(version, book) + if len(chaps) == 0 { + continue + } + chap = chaps[rng.Intn(len(chaps))] + verses = bible.Verses(version, book, chap) + } + if len(verses) == 0 { + fmt.Fprintln(stderr, "lectio: could not pick a random passage") + return 1 + } + + w := resolveWidth(width, false, stdout) + dialect := cfg.SiglaLang() + + if chapter { + if !raw { + fmt.Fprintln(stdout, fmt.Sprintf("%s %d", tbl.Abbrev(dialect, book), chap)) + fmt.Fprintln(stdout) + } + lines := make([]string, 0, len(verses)) + for _, v := range verses { + lines = append(lines, wrapText(fmt.Sprintf("%d %s", v.Verse, v.Text), w)) + } + fmt.Fprintln(stdout, strings.Join(lines, "\n")) + return 0 + } + + v := verses[rng.Intn(len(verses))] + if !raw { + fmt.Fprintln(stdout, tbl.FormatRef(dialect, fmt.Sprintf("%s %d:%d", book, chap, v.Verse))) + fmt.Fprintln(stdout) + } + fmt.Fprintln(stdout, wrapText(v.Text, w)) + return 0 +} + // isCorpusVersion reports whether v is a scripture-text version with an // embedded corpus (wuj/vul/grb/drb) -- i.e. a valid version that is not "bt" // (the niedziela.pl scrape, which has no full text to look a passage up in). diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index 374d723..58b2d00 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -353,6 +353,35 @@ func TestRefCompare(t *testing.T) { } } +func TestRandVerse(t *testing.T) { + // Random content varies, but structure/exit codes are deterministic. + var out, errb bytes.Buffer + if code := Run([]string{"--rand-v", "-b", "vul"}, nil, &out, &errb); code != 0 { + t.Fatalf("rand-v code=%d stderr=%q", code, errb.String()) + } + // en dialect (default config): the reference line carries a colon. + if s := out.String(); strings.TrimSpace(s) == "" || !strings.Contains(s, ":") { + t.Errorf("rand-v output = %q", s) + } +} + +func TestRandChapter(t *testing.T) { + var out, errb bytes.Buffer + if code := Run([]string{"--rand-ch", "-b", "wuj"}, nil, &out, &errb); code != 0 { + t.Fatalf("rand-ch code=%d stderr=%q", code, errb.String()) + } + if strings.TrimSpace(out.String()) == "" { + t.Errorf("rand-ch produced no output") + } +} + +func TestRandRejectsBT(t *testing.T) { + var out, errb bytes.Buffer + if code := Run([]string{"--rand-v", "-b", "bt"}, nil, &out, &errb); code != 2 { + t.Errorf("rand -b bt code=%d want 2 (stderr=%q)", code, errb.String()) + } +} + func TestGospelCitationHelpers(t *testing.T) { secs := []liturgy.Section{ {Heading: "1. czytanie (Iz 1, 1)", PartID: "pierwsze_czytanie"}, diff --git a/internal/config/config.go b/internal/config/config.go index 0648b66..3087d6f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -25,7 +25,7 @@ var seedTOML []byte // Version is lectio's release version, shared by every binary's // -v/--version output (lectio, lectio-ui, lectio-web). -const Version = "0.15.0" +const Version = "0.16.0" // validVersions are the five scripture versions lectio understands. var validVersions = map[string]bool{ |
