// Command lectio-ui is lectio's interactive Bubble Tea reader. package main import ( "flag" "fmt" "io" "os" "regexp" tea "github.com/charmbracelet/bubbletea" "github.com/lukaszkasprzak/lectio/internal/bible" "github.com/lukaszkasprzak/lectio/internal/config" "github.com/lukaszkasprzak/lectio/internal/tui" ) const helpText = `lectio-ui — interactive daily liturgy reader (Polish + 4 versions) Usage: lectio-ui [DATE] [flags] DATE = YYYY-MM-DD (default: today), any position Flags: -b, --bible VER start on version: bt,wuj,vul,grb,drb -R, --reader open the Bible reader (book picker) instead of the daily view -a, --all show all parts (override config) -o, --offline cache/sigla only, no network -l, --lectionary WHICH new|trad (trad -> traditional) -g, --lang LANG traditional lectionary language: pl|en -v, --version print the version and exit -h, --help this help Versions: bt (Biblia Tysiąclecia) wuj (Wujek) vul (Wulgata) grb (Grecki) drb (Douay-Rheims) Flags override config. Exit codes: 0 ok, 1 runtime error, 2 usage error. ` var dateRe = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`) func main() { os.Exit(run(os.Args[1:], os.Stdout, os.Stderr)) } // run is lectio-ui's testable entry point: parse args, validate, load // config, apply overrides, and launch the TUI. Returns a process exit code. func run(args []string, stdout, stderr io.Writer) int { if wantsHelp(args) { fmt.Fprint(stdout, helpText) return 0 } if wantsVersion(args) { fmt.Fprintln(stdout, "lectio-ui "+config.Version) return 0 } date, rest, err := extractDate(args) if err != nil { fmt.Fprintln(stderr, "lectio-ui:", err) return 2 } cfg, err := config.Load() if err != nil { fmt.Fprintln(stderr, "lectio-ui:", err) return 1 } var all, offline, reader bool var bibleVer, lectionary, lang string fs := flag.NewFlagSet("lectio-ui", flag.ContinueOnError) fs.SetOutput(stderr) fs.Usage = func() { fmt.Fprint(stderr, helpText) } fs.StringVar(&bibleVer, "b", "", "start on version: bt,wuj,vul,grb,drb") fs.StringVar(&bibleVer, "bible", "", "start on version: bt,wuj,vul,grb,drb") fs.BoolVar(&reader, "R", false, "open the Bible reader (fuzzy book picker) instead of the daily view") fs.BoolVar(&reader, "reader", false, "open the Bible reader (fuzzy book picker) instead of the daily view") fs.BoolVar(&all, "a", cfg.All, "show all parts (override config)") fs.BoolVar(&all, "all", cfg.All, "show all parts (override config)") fs.BoolVar(&offline, "o", cfg.Offline, "cache/sigla only, no network") fs.BoolVar(&offline, "offline", cfg.Offline, "cache/sigla only, no network") fs.StringVar(&lectionary, "l", "", "new|trad") fs.StringVar(&lectionary, "lectionary", "", "new|trad") fs.StringVar(&lang, "g", "", "pl|en") fs.StringVar(&lang, "lang", "", "pl|en") if err := fs.Parse(rest); err != nil { return 2 } if fs.NArg() > 0 { fmt.Fprintf(stderr, "lectio-ui: unexpected argument %q; see 'lectio-ui -h'\n", fs.Arg(0)) return 2 } lectionary, err = normalizeLectionary(lectionary) if err != nil { fmt.Fprintln(stderr, "lectio-ui:", err) return 2 } if lang != "" && lang != "pl" && lang != "en" { fmt.Fprintf(stderr, "lectio-ui: invalid --lang %q (want pl|en)\n", lang) return 2 } if bibleVer != "" && !config.ValidVersion(bibleVer) { fmt.Fprintf(stderr, "lectio-ui: unknown version %q (want one of bt, wuj, vul, grb, drb)\n", bibleVer) return 2 } cfg.All = all cfg.Offline = offline if lectionary != "" { cfg.Lectionary = lectionary } if lang != "" { cfg.TraditionalLang = lang } var mdl tea.Model = tui.New(cfg, date, bibleVer) if reader { tbl, _ := bible.LoadBookTable(config.UserBooksTOML()) mdl = tui.NewReader(cfg, tbl) } if _, err := tea.NewProgram(mdl, tea.WithAltScreen()).Run(); err != nil { fmt.Fprintln(stderr, err) return 1 } return 0 } // wantsHelp reports whether -h/--help appears anywhere in args. func wantsHelp(args []string) bool { for _, a := range args { if a == "-h" || a == "--help" { return true } } return false } // wantsVersion reports whether -v/--version appears anywhere in args. func wantsVersion(args []string) bool { for _, a := range args { if a == "-v" || a == "--version" { return true } } return false } // extractDate pulls the single positional DATE token (YYYY-MM-DD, matching // dateRe) out of args, wherever it appears, and returns it along with the // remaining tokens for flag.FlagSet to parse. Defaults to "" (New's // today-fallback) when no date token is present; errors if more than one is // found. func extractDate(args []string) (date string, rest []string, err error) { found := false for _, a := range args { if dateRe.MatchString(a) { if found { return "", nil, fmt.Errorf("multiple dates given (%q and %q)", date, a) } date = a found = true continue } rest = append(rest, a) } return date, rest, nil } // normalizeLectionary maps -l/--lectionary's accepted spellings via // config.NormalizeLectionary; "" (flag not given) passes through unchanged. func normalizeLectionary(lectionary string) (string, error) { if lectionary == "" { return "", nil } v, ok := config.NormalizeLectionary(lectionary) if !ok { return "", fmt.Errorf("invalid --lectionary %q (want new|trad)", lectionary) } return v, nil }