aboutsummaryrefslogtreecommitdiff
path: root/cmd/prognosis/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/prognosis/main.go')
-rw-r--r--cmd/prognosis/main.go94
1 files changed, 59 insertions, 35 deletions
diff --git a/cmd/prognosis/main.go b/cmd/prognosis/main.go
index 6e29c8e..3075563 100644
--- a/cmd/prognosis/main.go
+++ b/cmd/prognosis/main.go
@@ -3,7 +3,6 @@
package main
import (
- "bufio"
"errors"
"flag"
"fmt"
@@ -19,7 +18,9 @@ import (
"github.com/lukaszkasprzak/prognosis/internal/render"
)
-const wegorc = ".wegorc"
+// 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"
func main() { os.Exit(run()) }
@@ -28,12 +29,13 @@ func run() int {
configureResolver()
var (
- location = flag.String("l", "", "place to query (default: config, then ~/.wegorc)")
+ 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")
@@ -41,6 +43,7 @@ func run() int {
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")
)
flag.Usage = usage
// Go's flag package stops at the first non-flag argument, so
@@ -59,6 +62,11 @@ func run() int {
rest = flag.Args()[1:]
}
+ if *showVer {
+ fmt.Printf("prognosis %s\n", version)
+ return 0
+ }
+
cfgPath := config.Path()
if *showCfg {
fmt.Println(cfgPath)
@@ -87,6 +95,16 @@ func run() int {
if *lang != "" {
cfg.DisplayLang = *lang
}
+ if *pollen != "" {
+ switch *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
+ }
+ }
if *noGraph {
cfg.Graph = false
}
@@ -147,10 +165,9 @@ func run() int {
cfg.Location = place
}
if cfg.Location == "" {
- cfg.Location = locationFromWegorc()
- }
- if cfg.Location == "" {
- fmt.Fprintf(os.Stderr, "prognosis: no location set in %s and none in ~/%s\n", cfgPath, wegorc)
+ fmt.Fprintf(os.Stderr,
+ "prognosis: no location set. Put one in %s:\n\n location=Krakow\n\n"+
+ "or pass it for one run: prognosis -l Krakow\n", cfgPath)
return 2
}
@@ -180,6 +197,17 @@ func run() int {
fmt.Fprintf(os.Stderr, "note: %d %s %d\n", len(data.Rows), cat.Word("hours_available"), cfg.Hours)
}
+ // Custom air-quality columns need a second request, made only when the
+ // config actually declares one.
+ if fields := cfg.AirFields(); len(fields) > 0 {
+ hourly, err := openmeteo.AirHourly(geo.Lat, geo.Lon, cfg.Hours, fields)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "note: air-quality data unavailable: %v\n", err)
+ } else {
+ mergeCustom(data.Rows, hourly, cfg)
+ }
+ }
+
view := render.View{
Label: geo.Label, TZ: data.TZ, Rows: data.Rows,
Sun: data.Sun, Daily: data.Daily,
@@ -350,33 +378,6 @@ func parseCoords(s string) (float64, float64, bool) {
return a, b, true
}
-// locationFromWegorc reads location= from wego's config, so the two tools never
-// disagree about where you are.
-func locationFromWegorc() string {
- home, err := os.UserHomeDir()
- if err != nil {
- return ""
- }
- f, err := os.Open(home + "/" + wegorc)
- if err != nil {
- return ""
- }
- defer f.Close()
- sc := bufio.NewScanner(f)
- for sc.Scan() {
- line := strings.TrimSpace(sc.Text())
- if line == "" || strings.HasPrefix(line, "#") {
- continue
- }
- if k, v, ok := strings.Cut(line, "="); ok && strings.TrimSpace(k) == "location" {
- if v = strings.TrimSpace(v); v != "" {
- return v
- }
- }
- }
- return ""
-}
-
func shouldColour(mode string) bool {
switch mode {
case "never":
@@ -424,18 +425,41 @@ func usage() {
usage: prognosis [flags] [place]
flags:
- -l PLACE place to query (default: config, then ~/.wegorc)
+ -l PLACE place to query (default: location= in the config file)
-pick N choose the Nth place when the name matches several
-n N hours ahead to show
-d N days ahead to show, 24h each (max %d)
-columns LIST comma-separated columns; valid: %s
-icons SET nerd, emoji or none
-lang LANG en or pl
+ -pollen LIST pollen species to show: a list, or all / none
-no-graph table only
-weather forecast only: no sun times, summary, pollen or chart
-no-warnings omit IMGW warnings
-ascii ASCII only, so an SMS stays in GSM-7 (160 chars, not 70)
-no-color plain output
-config print the config file path and exit
+ -version print the version and exit
`, openmeteo.MaxForecastDays-1, strings.Join(config.ValidColumns(), ", "))
}
+
+// mergeCustom copies air-quality values onto the matching forecast hour.
+//
+// Custom values are stored under the column's own name, prefixed, so they can
+// never collide with an API field name already in the row.
+func mergeCustom(rows []openmeteo.Row, hourly map[string]map[string]float64, cfg config.Config) {
+ for i, r := range rows {
+ byField, ok := hourly[r.When.Format("2006-01-02T15:04")]
+ if !ok {
+ continue
+ }
+ for name, cc := range cfg.Custom {
+ if cc.Source != "air" {
+ continue
+ }
+ if v, ok := byField[cc.Field]; ok {
+ rows[i].Vals[render.CustomKey(name)] = v
+ }
+ }
+ }
+}