summaryrefslogtreecommitdiff
path: root/cmd/prognosis/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/prognosis/main.go')
-rw-r--r--cmd/prognosis/main.go106
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]