summaryrefslogtreecommitdiff
path: root/internal/cli/feed.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/cli/feed.go')
-rw-r--r--internal/cli/feed.go130
1 files changed, 130 insertions, 0 deletions
diff --git a/internal/cli/feed.go b/internal/cli/feed.go
new file mode 100644
index 0000000..f7e7bab
--- /dev/null
+++ b/internal/cli/feed.go
@@ -0,0 +1,130 @@
+package cli
+
+import (
+ "fmt"
+ "io"
+ "strconv"
+ "time"
+
+ "github.com/lukaszkasprzak/lectio/internal/caldata"
+ "github.com/lukaszkasprzak/lectio/internal/calendar"
+ "github.com/lukaszkasprzak/lectio/internal/calfeed"
+ "github.com/lukaszkasprzak/lectio/internal/config"
+)
+
+// cliMaxSpanDays caps --from/--to at 100 years. The CLI is a local, trusted
+// surface (unlike the web endpoints' tighter 1830-day cap), so this exists
+// only to catch a fat-fingered year, not to bound resource use.
+const cliMaxSpanDays = 36525
+
+// minCalendarYear/maxCalendarYear bound --year to the domain the Gregorian
+// Computus (Easter algorithm) is valid for; years before the 1582 reform are
+// rejected.
+const (
+ minCalendarYear = 1583
+ maxCalendarYear = 9999
+)
+
+// runFeed handles --format json|ical (+ --from/--to, --year, --form): builds
+// the requested day range with the shared calfeed.Build/caldata.Readings
+// pipeline (the same one --liturgy uses) and writes the rendered feed to
+// stdout. date is the positional DATE (already defaulted to today by
+// extractDate); it is used only when neither --from/--to nor --year is
+// given. Every validation failure is a fixed stderr message and exit 2;
+// nothing is echoed back beyond the offending flag's own value.
+func runFeed(cfg config.Config, format, date, fromFlag, toFlag, yearFlag, formFlag string, stdout, stderr io.Writer) int {
+ if format == "" {
+ fmt.Fprintln(stderr, "lectio: --from/--to/--year/--form require --format json|ical")
+ return 2
+ }
+ if format != "json" && format != "ical" {
+ fmt.Fprintf(stderr, "lectio: invalid --format %q (want json|ical)\n", format)
+ return 2
+ }
+
+ from, to, err := feedRange(date, fromFlag, toFlag, yearFlag)
+ if err != nil {
+ fmt.Fprintln(stderr, "lectio:", err)
+ return 2
+ }
+
+ sel := cfg.Selection()
+ if formFlag != "" {
+ if formFlag != "old" && formFlag != "new" {
+ fmt.Fprintf(stderr, "lectio: invalid --form %q (want old|new)\n", formFlag)
+ return 2
+ }
+ sel.Form = formFlag
+ }
+
+ dir, _ := config.CalendarsDir()
+ layers, errs := caldata.Stack(sel.Form, dir, cfg.Use)
+ for _, e := range errs {
+ fmt.Fprintln(stderr, "lectio: warning:", e)
+ }
+
+ days := calfeed.Build(from, to, cfg.UILanguage, sel, layers, func(d time.Time, day calendar.LiturgicalDay) []calendar.Reading {
+ return caldata.Readings(sel, layers, d, day)
+ })
+
+ var out []byte
+ switch format {
+ case "json":
+ out, err = calfeed.JSON(sel.Form, days)
+ if err != nil {
+ fmt.Fprintln(stderr, "lectio:", err)
+ return 1
+ }
+ case "ical":
+ out = calfeed.ICal(sel.Form, days, time.Now())
+ }
+ _, _ = stdout.Write(out)
+ return 0
+}
+
+// feedRange resolves --from/--to/--year (mutually exclusive) or, absent
+// both, dateStr (the positional DATE) into an inclusive [from, to] range.
+// Dates are parsed strictly with time.Parse("2006-01-02", …); --year is
+// bounded to [minCalendarYear, maxCalendarYear]; from must not be after to;
+// the span is capped at cliMaxSpanDays. Every branch returns a fixed,
+// input-scoped error message.
+func feedRange(dateStr, fromStr, toStr, yearStr string) (from, to time.Time, err error) {
+ switch {
+ case yearStr != "" && (fromStr != "" || toStr != ""):
+ return time.Time{}, time.Time{}, fmt.Errorf("--year cannot be combined with --from/--to")
+ case fromStr != "" && toStr == "":
+ return time.Time{}, time.Time{}, fmt.Errorf("--from requires --to")
+ case toStr != "" && fromStr == "":
+ return time.Time{}, time.Time{}, fmt.Errorf("--to requires --from")
+ case yearStr != "":
+ year, cerr := strconv.Atoi(yearStr)
+ if cerr != nil || year < minCalendarYear || year > maxCalendarYear {
+ return time.Time{}, time.Time{}, fmt.Errorf("invalid --year %q (want an integer %d-%d)", yearStr, minCalendarYear, maxCalendarYear)
+ }
+ from = time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC)
+ to = time.Date(year, 12, 31, 0, 0, 0, 0, time.UTC)
+ return from, to, nil
+ case fromStr != "" && toStr != "":
+ from, err = time.Parse("2006-01-02", fromStr)
+ if err != nil {
+ return time.Time{}, time.Time{}, fmt.Errorf("invalid --from %q (want YYYY-MM-DD)", fromStr)
+ }
+ to, err = time.Parse("2006-01-02", toStr)
+ if err != nil {
+ return time.Time{}, time.Time{}, fmt.Errorf("invalid --to %q (want YYYY-MM-DD)", toStr)
+ }
+ if to.Before(from) {
+ return time.Time{}, time.Time{}, fmt.Errorf("--from %s is after --to %s", fromStr, toStr)
+ }
+ if days := int(to.Sub(from).Hours()/24) + 1; days > cliMaxSpanDays {
+ return time.Time{}, time.Time{}, fmt.Errorf("range too large (%d days; max %d)", days, cliMaxSpanDays)
+ }
+ return from, to, nil
+ default:
+ d, derr := time.Parse("2006-01-02", dateStr)
+ if derr != nil {
+ return time.Time{}, time.Time{}, fmt.Errorf("invalid date %q (want YYYY-MM-DD)", dateStr)
+ }
+ return d, d, nil
+ }
+}