diff options
Diffstat (limited to 'cmd/prognosis/main.go')
| -rw-r--r-- | cmd/prognosis/main.go | 106 |
1 files changed, 61 insertions, 45 deletions
diff --git a/cmd/prognosis/main.go b/cmd/prognosis/main.go index 3075563..99d9918 100644 --- a/cmd/prognosis/main.go +++ b/cmd/prognosis/main.go @@ -6,6 +6,7 @@ import ( "errors" "flag" "fmt" + "io" "os" "strconv" "strings" @@ -18,6 +19,35 @@ import ( "github.com/lukaszkasprzak/prognosis/internal/render" ) +// options is every command line flag. Defined in one place so that run() and +// the test asserting each one is documented cannot disagree about the set. +type options struct { + location, columns, icons, lang, pollen *string + hours, days, pick *int + noGraph, weather, noWarn, asciiOut, noColor *bool + showCfg, showVer *bool +} + +func defineFlags(fs *flag.FlagSet) *options { + return &options{ + location: fs.String("l", "", "place to query (default: location= in the config file)"), + hours: fs.Int("n", 0, "hours ahead to show"), + days: fs.Int("d", 0, "days ahead to show, 24h each"), + columns: fs.String("columns", "", "comma-separated columns to display"), + icons: fs.String("icons", "", "icon set: nerd, emoji or none"), + lang: fs.String("lang", "", "display language: en or pl"), + pollen: fs.String("pollen", "", "pollen species to show: a list, or all / none"), + noGraph: fs.Bool("no-graph", false, "table only, no chart"), + weather: fs.Bool("weather", false, "forecast only: no sun times, summary, pollen or chart"), + noWarn: fs.Bool("no-warnings", false, "omit IMGW warnings"), + asciiOut: fs.Bool("ascii", false, "ASCII only, so an SMS stays in GSM-7"), + noColor: fs.Bool("no-color", false, "plain output"), + pick: fs.Int("pick", 0, "choose the Nth place when the name is ambiguous"), + showCfg: fs.Bool("config", false, "print the config file path and exit"), + showVer: fs.Bool("version", false, "print the version and exit"), + } +} + // version is stamped at build time: -ldflags "-X main.version=$(git describe)". // "dev" means someone built it straight from a working tree. var version = "dev" @@ -28,23 +58,7 @@ func run() int { // Android has no /etc/resolv.conf; without this every lookup fails. configureResolver() - var ( - location = flag.String("l", "", "place to query (default: location= in the config file)") - hours = flag.Int("n", 0, "hours ahead to show") - days = flag.Int("d", 0, "days ahead to show, 24h each") - columns = flag.String("columns", "", "comma-separated columns to display") - icons = flag.String("icons", "", "icon set: nerd, emoji or none") - lang = flag.String("lang", "", "display language: en or pl") - pollen = flag.String("pollen", "", "pollen species to show: a list, or all / none") - noGraph = flag.Bool("no-graph", false, "table only, no chart") - weather = flag.Bool("weather", false, "forecast only: no sun times, summary, pollen or chart") - noWarn = flag.Bool("no-warnings", false, "omit IMGW warnings") - asciiOut = flag.Bool("ascii", false, "ASCII only, so an SMS stays in GSM-7") - noColor = flag.Bool("no-color", false, "plain output") - pick = flag.Int("pick", 0, "choose the Nth place when the name is ambiguous") - showCfg = flag.Bool("config", false, "print the config file path and exit") - showVer = flag.Bool("version", false, "print the version and exit") - ) + opt := defineFlags(flag.CommandLine) flag.Usage = usage // Go's flag package stops at the first non-flag argument, so // "prognosis 52.52,13.40 -n 3" would swallow the flags into the place name. @@ -62,13 +76,13 @@ func run() int { rest = flag.Args()[1:] } - if *showVer { + if *opt.showVer { fmt.Printf("prognosis %s\n", version) return 0 } cfgPath := config.Path() - if *showCfg { + if *opt.showCfg { fmt.Println(cfgPath) return 0 } @@ -86,66 +100,66 @@ func run() int { } // Flags override the file. The place is settled later, by placeFromArgs. - if *columns != "" { - cfg.Columns = splitList(*columns) + if *opt.columns != "" { + cfg.Columns = splitList(*opt.columns) } - if *icons != "" { - cfg.Icons = *icons + if *opt.icons != "" { + cfg.Icons = *opt.icons } - if *lang != "" { - cfg.DisplayLang = *lang + if *opt.lang != "" { + cfg.DisplayLang = *opt.lang } - if *pollen != "" { - switch *pollen { + if *opt.pollen != "" { + switch *opt.pollen { case "all": cfg.Pollen, cfg.PollenExplicit = append([]string(nil), config.AllSpecies...), false case "none": cfg.Pollen, cfg.PollenExplicit = nil, false default: - cfg.Pollen, cfg.PollenExplicit = splitList(*pollen), true + cfg.Pollen, cfg.PollenExplicit = splitList(*opt.pollen), true } } - if *noGraph { + if *opt.noGraph { cfg.Graph = false } - if *weather { + if *opt.weather { // Meant for piping to someone else: the forecast and nothing else. cfg.Minimal = true cfg.Graph = false } - if *noWarn { + if *opt.noWarn { cfg.Warnings = false } - if *asciiOut { + if *opt.asciiOut { cfg.ASCII = true } if cfg.ASCII { // Both glyph sets are non-ASCII by definition. cfg.Icons = "none" } - if *noColor { + if *opt.noColor { cfg.Color = "never" } switch { - case *days > 0 && *hours > 0: + case *opt.days > 0 && *opt.hours > 0: fmt.Fprintln(os.Stderr, "prognosis: -n and -d cannot be combined") return 2 - case *days > 0: - if *days > openmeteo.MaxForecastDays-1 { + case *opt.days > 0: + if *opt.days > openmeteo.MaxForecastDays-1 { fmt.Fprintf(os.Stderr, "prognosis: -d must be between 1 and %d\n", openmeteo.MaxForecastDays-1) return 2 } - cfg.Hours = *days * 24 - case *hours > 0: - cfg.Hours = *hours - case *days < 0 || *hours < 0: + cfg.Hours = *opt.days * 24 + case *opt.hours > 0: + cfg.Hours = *opt.hours + case *opt.days < 0 || *opt.hours < 0: fmt.Fprintln(os.Stderr, "prognosis: hours and days must be positive") return 2 } // -icons only chooses what the icon column draws. Without that column it // changes nothing, which looks like the flag being ignored. - if *icons != "" && !cfg.Has("icon") { + if *opt.icons != "" && !cfg.Has("icon") { fmt.Fprintf(os.Stderr, "note: -icons has no effect: %q is not in columns (add it: -columns %s)\n", "icon", strings.Join(append([]string{"hour", "icon"}, cfg.Columns[1:]...), ",")) @@ -156,7 +170,7 @@ func run() int { return 2 } - place, err := placeFromArgs(*location, positional) + place, err := placeFromArgs(*opt.location, positional) if err != nil { fmt.Fprintf(os.Stderr, "prognosis: %v\n", err) return 2 @@ -172,7 +186,7 @@ func run() int { } store := cache.New(cache.DefaultPath()) - geo, err := resolve(store, cfg.Location, *pick) + geo, err := resolve(store, cfg.Location, *opt.pick) if err != nil { var amb *ambiguousError if errors.As(err, &amb) { @@ -419,8 +433,10 @@ func splitList(v string) []string { return out } -func usage() { - fmt.Fprintf(os.Stderr, `prognosis - hour-by-hour forecast, with IMGW warnings for Poland +func usage() { usageTo(os.Stderr) } + +func usageTo(w io.Writer) { + fmt.Fprintf(w, `prognosis - hour-by-hour forecast, with IMGW warnings for Poland usage: prognosis [flags] [place] |
