// This code represents wttr.in view v1. // It is based on wego (github.com/schachmat/wego) from which it diverged back in 2016. //nolint:forbidigo,funlen,gocognit,cyclop package v1 import ( "encoding/json" "errors" "flag" "fmt" "io/ioutil" "log" "os" "os/user" "path" "regexp" "strings" "github.com/mattn/go-colorable" "github.com/mattn/go-runewidth" ) type Configuration struct { APIKey string City string Numdays int Imperial bool WindUnit bool Inverse bool Lang string Narrow bool LocationName string WindMS bool RightToLeft bool } type global struct { ansiEsc *regexp.Regexp config Configuration configpath string debug bool } const ( wuri = "http://127.0.0.1:5001/premium/v1/weather.ashx?" suri = "http://127.0.0.1:5001/premium/v1/search.ashx?" slotcount = 4 ) func (g *global) configload() error { b, err := ioutil.ReadFile(g.configpath) if err == nil { return json.Unmarshal(b, &g.config) } return err } func (g *global) configsave() error { j, err := json.MarshalIndent(g.config, "", "\t") if err == nil { return ioutil.WriteFile(g.configpath, j, 0o600) } return err } func (g *global) init() { flag.IntVar(&g.config.Numdays, "days", 3, "Number of days of weather forecast to be displayed") flag.StringVar(&g.config.Lang, "lang", "en", "Language of the report") flag.StringVar(&g.config.City, "city", "New York", "City to be queried") flag.BoolVar(&g.debug, "debug", false, "Print out raw json response for debugging purposes") flag.BoolVar(&g.config.Imperial, "imperial", false, "Use imperial units") flag.BoolVar(&g.config.Inverse, "inverse", false, "Use inverted colors") flag.BoolVar(&g.config.Narrow, "narrow", false, "Narrow output (two columns)") flag.StringVar(&g.config.LocationName, "location_name", "", "Location name (used in the caption)") flag.BoolVar(&g.config.WindMS, "wind_in_ms", false, "Show wind speed in m/s") flag.BoolVar(&g.config.RightToLeft, "right_to_left", false, "Right to left script") g.configpath = os.Getenv("WEGORC") if g.configpath == "" { usr, err := user.Current() if err != nil { log.Fatalf("%v\nYou can set the environment variable WEGORC to point to your config file as a workaround.", err) } g.configpath = path.Join(usr.HomeDir, ".wegorc") } g.config.APIKey = "" g.config.Imperial = false g.config.Lang = "en" err := g.configload() var pathError *os.PathError if errors.Is(err, pathError) { log.Printf("No config file found. Creating %s ...", g.configpath) if err2 := g.configsave(); err2 != nil { log.Fatal(err2) } } else if err != nil { log.Fatalf("could not parse %v: %v", g.configpath, err) } g.ansiEsc = regexp.MustCompile("\033.*?m") } func Cmd() error { g := global{} g.init() flag.Parse() r, err := g.getDataFromAPI() if err != nil { return err } if r.Data.Req == nil || len(r.Data.Req) < 1 { if r.Data.Err != nil && len(r.Data.Err) >= 1 { log.Fatal(r.Data.Err[0].Msg) } log.Fatal("Malformed response.") } locationName := r.Data.Req[0].Query if g.config.LocationName != "" { locationName = g.config.LocationName } if g.config.Lang == "he" || g.config.Lang == "ar" || g.config.Lang == "fa" { g.config.RightToLeft = true } if caption, ok := localizedCaption()[g.config.Lang]; !ok { fmt.Printf("Weather report: %s\n\n", locationName) } else { if g.config.RightToLeft { caption = locationName + " " + caption space := strings.Repeat(" ", 125-runewidth.StringWidth(caption)) fmt.Printf("%s%s\n\n", space, caption) } else { fmt.Printf("%s %s\n\n", caption, locationName) } } stdout := colorable.NewColorableStdout() if r.Data.Cur == nil || len(r.Data.Cur) < 1 { log.Fatal("No weather data available.") } out := g.formatCond(make([]string, 5), r.Data.Cur[0], true) for _, val := range out { if g.config.RightToLeft { fmt.Fprint(stdout, strings.Repeat(" ", 94)) } else { fmt.Fprint(stdout, " ") } fmt.Fprintln(stdout, val) } if g.config.Numdays == 0 { return nil } if r.Data.Weather == nil { log.Fatal("No detailed weather forecast available.") } for _, d := range r.Data.Weather { lines, err := g.printDay(d) if err != nil { return err } for _, val := range lines { fmt.Fprintln(stdout, val) } } return nil }