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/cli | |
| 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/cli')
| -rw-r--r-- | internal/cli/cli.go | 100 | ||||
| -rw-r--r-- | internal/cli/cli_test.go | 29 |
2 files changed, 126 insertions, 3 deletions
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"}, |
