[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\n# See https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates for options\nupdates:\n  - package-ecosystem: \"gomod\"\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"monthly\"\n  - package-ecosystem: \"docker\"\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"monthly\"\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\""
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "### Motivation and Context\n<!-- Why is this change required? What problem does it solve? -->\n<!-- If it fixes an open issue, please link to the issue here. -->\n\n### Description\n<!-- Describe your changes in detail, what does your code do? How does it do it? -->\n\n### Steps for Testing\n<!-- Please describe in detail how a reviewer can test your changes. -->\n\n### Screenshots\n<!-- Add screenshots to demonstrate the changes. -->"
  },
  {
    "path": ".github/workflows/go-build-test.yml",
    "content": "name: Go Build and Test\n\non:\n  push:\n    branches:\n      - main\n      - master\n  pull_request:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n    - name: Set up Go\n      uses: actions/setup-go@v5\n      with:\n        cache-dependency-path: './go.sum'\n        go-version-file: './go.mod'\n    - name: Build\n      run: go build -v ./...\n    - name: Test\n      run: go test -v ./..."
  },
  {
    "path": ".github/workflows/golangci-lint.yml",
    "content": "name: golangci-lint\non:\n  push:\n    branches:\n      - main\n      - master\n  pull_request:\n\npermissions:\n  contents: read\n  # Optional: allow read access to pull request. Use with `only-new-issues` option.\n  pull-requests: read\n  # Optional: allow write access to checks to allow the action to annotate code in the PR.\n  checks: write\n\njobs:\n  golangci:\n    name: lint\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-go@v5\n        with:\n          go-version: stable\n      - name: golangci-lint\n        uses: golangci/golangci-lint-action@v6\n        with:\n          version: v1.60\n          only-new-issues: true"
  },
  {
    "path": ".gitignore",
    "content": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n/wego\n\n# Folders\n_obj\n_test\n.idea\n\n# Architecture specific extensions/prefixes\n*.[568vq]\n[568vq].out\n\n*.cgo1.go\n*.cgo2.c\n_cgo_defun.c\n_cgo_gotypes.go\n_cgo_export.*\n\n_testmain.go\n\n*.exe\n*.test\n*.prof\n\n# vim temp files\n*~\n*.swp\n\n# go modules\nvendor\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to wego\n\n* Add new backends only if they at least offer a free tier\n* Don't add other go dependencies. This makes packaging harder.\n* Have fun and don't break people!\n"
  },
  {
    "path": "LICENSE",
    "content": "ISC License\n\nCopyright (c) 2014-2017,  <teichm@in.tum.de>\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "**wego** is a weather client for the terminal.\n\n![Screenshots](http://schachmat.github.io/wego/wego.gif)\n\n## Features\n\n* show forecast for 1 to 7 days\n* nice ASCII art icons\n* displayed info (metric or imperial units):\n  * temperature range ([felt](https://en.wikipedia.org/wiki/Wind_chill) and measured)\n  * windspeed and direction\n  * viewing distance\n  * precipitation amount and probability\n* ssl, so the NSA has a harder time learning where you live or plan to go\n* multi language support\n* config file for default location which can be overridden by commandline\n* Automatic config management with [ingo](https://github.com/schachmat/ingo)\n\n## Dependencies\n\n* A [working](https://golang.org/doc/install#testing) [Go](https://golang.org/)\n  [1.20](https://golang.org/doc/go1.20) environment \n* utf-8 terminal with 256 colors\n* A monospaced font containing all the required runes (I use `dejavu sans\n  mono`)\n* An API key for the backend (see Setup below)\n\n## Installation\n\nCheck your distribution for packaging:\n\n[![Packaging status](https://repology.org/badge/vertical-allrepos/wego.svg)](https://repology.org/project/wego/versions)\n\nTo directly install or update the wego binary from Github into your `$GOPATH` as usual, run:\n```shell\ngo install github.com/schachmat/wego@latest\n```\n\n## Setup\n\n0. Run `wego` once. You will get an error message, but the `.wegorc` config file\n   will be generated in your `$HOME` directory (it will be hidden in some file\n   managers due to the filename starting with a dot).\n0. __With an [Openweathermap](https://home.openweathermap.org/) account__\n    * You can create an account and get a free API key by [signing up](https://home.openweathermap.org/users/sign_up)\n    * Update the following `.wegorc` config variables to fit your needs:\n    ```\n      backend=openweathermap\n      location=New York\n      owm-api-key=YOUR_OPENWEATHERMAP_API_KEY_HERE\n    ```\n0. __With a [Worldweatheronline](http://www.worldweatheronline.com/) account__\n    * Worldweatheronline no longer gives out free API keys. [#83](https://github.com/schachmat/wego/issues/83)\n    * Update the following `.wegorc` config variables to fit your needs:\n    ```\n      backend=worldweatheronline\n      location=New York\n      wwo-api-key=YOUR_WORLDWEATHERONLINE_API_KEY_HERE\n    ```\n0. You may want to adjust other preferences like `days`, `units` and `…-lang` as\n   well. Save the file.\n0. Run `wego` once again and you should get the weather forecast for the current\n   and next few days for your chosen location.\n0. If you're visiting someone in e.g. London over the weekend, just run `wego 4\n   London` or `wego London 4` (the ordering of arguments makes no difference) to\n   get the forecast for the current and the next 3 days.\n\nYou can set the `$WEGORC` environment variable to override the default config\nfile location.\n\n## Todo\n\n* more [backends and frontends](https://github.com/schachmat/wego/wiki/How-to-write-a-new-backend-or-frontend)\n* resolve ALL the [issues](https://github.com/schachmat/wego/issues)\n* don't forget the [TODOs in the code](https://github.com/schachmat/wego/search?q=TODO&type=Code)\n\n## License - ISC\n\nCopyright (c) 2014-2017,  <teichm@in.tum.de>\n\nPermission to use, copy, modify, and/or distribute this software for any purpose\nwith or without fee is hereby granted, provided that the above copyright notice\nand this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\nFITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\nOF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\nTORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\nTHIS SOFTWARE.\n"
  },
  {
    "path": "backends/caiyun.go",
    "content": "package backends\n\nimport (\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/schachmat/wego/iface\"\n)\n\nconst (\n\tCAIYUNAPI       = \"http://api.caiyunapp.com/v2.6/%s/%s/weather?lang=%s&dailysteps=%s&hourlysteps=%s&alert=true&unit=metric:v2&begin=%s&granu=%s\"\n\tCAIYUNDATE_TMPL = \"2006-01-02T15:04-07:00\"\n)\n\ntype CaiyunConfig struct {\n\tapiKey string\n\tlang   string\n\tdebug  bool\n}\n\nfunc (c *CaiyunConfig) Setup() {\n\tflag.StringVar(&c.apiKey, \"caiyun-api-key\", \"\", \"caiyun backend: the api `KEY` to use\")\n\tflag.StringVar(&c.lang, \"caiyun-lang\", \"en\", \"caiyun backend: the `LANGUAGE` to request from caiyunapp.com/\")\n\tflag.BoolVar(&c.debug, \"caiyun-debug\", true, \"caiyun backend: print raw requests and responses\")\n}\n\nvar SkyconToIfaceCode map[string]iface.WeatherCode\n\nfunc init() {\n\tSkyconToIfaceCode = map[string]iface.WeatherCode{\n\t\t\"CLEAR_DAY\":           iface.CodeSunny,\n\t\t\"CLEAR_NIGHT\":         iface.CodeSunny,\n\t\t\"PARTLY_CLOUDY_DAY\":   iface.CodePartlyCloudy,\n\t\t\"PARTLY_CLOUDY_NIGHT\": iface.CodePartlyCloudy,\n\t\t\"CLOUDY\":              iface.CodeCloudy,\n\t\t\"LIGHT_HAZE\":          iface.CodeUnknown,\n\t\t\"MODERATE_HAZE\":       iface.CodeUnknown,\n\t\t\"HEAVY_HAZE\":          iface.CodeUnknown,\n\t\t\"LIGHT_RAIN\":          iface.CodeLightRain,\n\t\t\"MODERATE_RAIN\":       iface.CodeLightRain,\n\t\t\"HEAVY_RAIN\":          iface.CodeHeavyRain,\n\t\t\"STORM_RAIN\":          iface.CodeHeavyRain,\n\t\t\"FOG\":                 iface.CodeFog,\n\t\t\"LIGHT_SNOW\":          iface.CodeLightSnow,\n\t\t\"MODERATE_SNOW\":       iface.CodeLightSnow,\n\t\t\"HEAVY_SNOW\":          iface.CodeHeavySnow,\n\t\t\"STORM_SNOW\":          iface.CodeHeavySnow,\n\t\t\"DUST\":                iface.CodeUnknown,\n\t\t\"SAND\":                iface.CodeUnknown,\n\t\t\"WIND\":                iface.CodeUnknown,\n\t}\n}\n\nfunc ParseCoordinates(latlng string) (float64, float64, error) {\n\ts := strings.Split(latlng, \",\")\n\tif len(s) != 2 {\n\t\treturn 0, 0, fmt.Errorf(\"input %v split to %v parts\", latlng, len(s))\n\t}\n\n\tlat, err := strconv.ParseFloat(s[0], 64)\n\tif err != nil {\n\t\treturn 0, 0, fmt.Errorf(\"parse Coodinates failed input %v get parts %v\", latlng, s[0])\n\t}\n\n\tlng, err := strconv.ParseFloat(s[1], 64)\n\tif err != nil {\n\t\treturn 0, 0, fmt.Errorf(\"parse Coodinates failed input %v get parts %v\", latlng, s[1])\n\t}\n\treturn lat, lng, nil\n}\n\nfunc (c *CaiyunConfig) GetWeatherDataFromLocalBegin(lng float64, lat float64, numdays int) (*CaiyunWeather, error) {\n\tcyLocation := fmt.Sprintf(\"%v,%v\", lng, lat)\n\n\tlocalBegin, err := func() (*time.Time, error) {\n\t\tnow := time.Now()\n\t\turl := fmt.Sprintf(\n\t\t\tCAIYUNAPI, c.apiKey, cyLocation, c.lang,\n\t\t\tstrconv.FormatInt(int64(numdays), 10), strconv.FormatInt(int64(numdays)*24, 10),\n\t\t\tstrconv.FormatInt(now.Unix(), 10),\n\t\t\t\"realtime\",\n\t\t)\n\t\turl += \"fields=temperature\"\n\t\tresp, err := http.Get(url)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer resp.Body.Close()\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif c.debug {\n\t\t\tlog.Printf(\"caiyun request phase 1 %v \\n%v\\n\", url, string(body))\n\t\t}\n\t\tweatherData := &CaiyunWeather{}\n\t\tif err := json.Unmarshal(body, weatherData); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tloc, err := time.LoadLocation(weatherData.Timezone)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tlocalNow := now.In(loc)\n\t\tlocalBegin := time.Date(localNow.Year(), localNow.Month(), localNow.Day(), 0, 0, 0, 0, loc)\n\t\treturn &localBegin, nil\n\t}()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\turl := fmt.Sprintf(\n\t\tCAIYUNAPI, c.apiKey, cyLocation, c.lang,\n\t\tstrconv.FormatInt(int64(numdays), 10), strconv.FormatInt(int64(numdays)*24, 10),\n\t\tstrconv.FormatInt(localBegin.Unix(), 10),\n\t\t\"realtime,minutely,hourly,daily\",\n\t)\n\tresp, err := http.Get(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif c.debug {\n\t\tlog.Printf(\"caiyun request phase 2 %v \\n%v\\n\", url, string(body))\n\t}\n\tweatherData := &CaiyunWeather{}\n\tif err := json.Unmarshal(body, weatherData); err != nil {\n\t\treturn nil, err\n\t}\n\treturn weatherData, nil\n}\n\nfunc (c *CaiyunConfig) Fetch(location string, numdays int) iface.Data {\n\tif c.debug {\n\t\tlog.Printf(\"caiyun location %v\", location)\n\t}\n\tres := iface.Data{}\n\tlat, lng, err := ParseCoordinates(location)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tweatherData, err := c.GetWeatherDataFromLocalBegin(lng, lat, numdays)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tres.Current.Desc = weatherData.Result.Minutely.Description + \"\\t\" + weatherData.Result.Hourly.Description\n\n\tres.Current.TempC = func() *float32 {\n\t\tx := float32(weatherData.Result.Realtime.Temperature)\n\t\treturn &x\n\t}()\n\tif code, ok := SkyconToIfaceCode[weatherData.Result.Realtime.Skycon]; ok {\n\t\tres.Current.Code = code\n\t} else {\n\t\tres.Current.Code = iface.CodeUnknown\n\t}\n\tif adcodes := weatherData.Result.Alert.Adcodes; len(adcodes) != 0 {\n\t\tif len(adcodes) == 3 {\n\t\t\tres.Location = adcodes[1].Name + adcodes[2].Name\n\t\t}\n\t\tif len(adcodes) == 2 {\n\t\t\tres.Location = adcodes[0].Name + adcodes[1].Name\n\t\t}\n\t} else {\n\t\tres.Location = \"第三红岸基地\"\n\t}\n\tres.Current.WinddirDegree = func() *int {\n\t\tx := int(weatherData.Result.Realtime.Wind.Direction)\n\t\treturn &x\n\t}()\n\tres.Current.WindspeedKmph = func() *float32 {\n\t\tx := float32(weatherData.Result.Realtime.Wind.Speed)\n\t\treturn &x\n\t}()\n\tres.Current.PrecipM = func() *float32 {\n\t\tx := float32(weatherData.Result.Realtime.Precipitation.Local.Intensity) / 1000\n\t\treturn &x\n\t}()\n\tres.Current.FeelsLikeC = func() *float32 {\n\t\tx := float32(weatherData.Result.Realtime.ApparentTemperature)\n\t\treturn &x\n\t}()\n\tres.Current.Humidity = func() *int {\n\t\tx := int(weatherData.Result.Realtime.Humidity * 100)\n\t\treturn &x\n\t}()\n\tres.Current.ChanceOfRainPercent = func() *int {\n\t\tx := int(weatherData.Result.Minutely.Probability[0] * 100)\n\t\treturn &x\n\t}()\n\tres.Current.VisibleDistM = func() *float32 {\n\t\tx := float32(weatherData.Result.Realtime.Visibility)\n\t\treturn &x\n\t}()\n\tres.Current.Time = func() time.Time {\n\t\tloc, err := time.LoadLocation(weatherData.Timezone)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\treturn time.Now().In(loc)\n\t}()\n\tdailyDataSlice := []iface.Day{}\n\tfor i := 0; i < numdays; i++ {\n\t\tweatherDailyData := weatherData.Result.Daily\n\n\t\tdailyData := iface.Day{\n\t\t\tDate: func() time.Time {\n\t\t\t\tx, err := time.Parse(CAIYUNDATE_TMPL, weatherDailyData.Temperature[i].Date)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t\treturn x\n\t\t\t}(),\n\t\t\tSlots: []iface.Cond{},\n\t\t}\n\n\t\tdailyData.Astronomy = iface.Astro{\n\t\t\tSunrise: func() time.Time {\n\t\t\t\ts := strings.Split(weatherDailyData.Astro[i].Sunset.Time, \":\")\n\t\t\t\thourStr := s[0]\n\t\t\t\tminuteStr := s[1]\n\t\t\t\thour, err := strconv.Atoi(hourStr)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t\tminute, err := strconv.Atoi(minuteStr)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t\tx := time.Date(dailyData.Date.Year(), dailyData.Date.Month(), dailyData.Date.Day(), hour, minute, 0, 0, dailyData.Date.Location())\n\t\t\t\treturn x\n\t\t\t}(),\n\t\t\tSunset: func() time.Time {\n\t\t\t\ts := strings.Split(weatherDailyData.Astro[i].Sunset.Time, \":\")\n\t\t\t\thourStr := s[0]\n\t\t\t\tminuteStr := s[1]\n\t\t\t\thour, err := strconv.Atoi(hourStr)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t\tminute, err := strconv.Atoi(minuteStr)\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t\tx := time.Date(dailyData.Date.Year(), dailyData.Date.Month(), dailyData.Date.Day(), hour, minute, 0, 0, dailyData.Date.Location())\n\t\t\t\treturn x\n\t\t\t}(),\n\t\t}\n\n\t\tdateStr := weatherDailyData.Temperature[i].Date[0:10]\n\n\t\tweatherHourlyData := weatherData.Result.Hourly\n\n\t\tfor index, houryTmp := range weatherData.Result.Hourly.Temperature {\n\t\t\tif !strings.Contains(houryTmp.Datetime, dateStr) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdailyData.Slots = append(dailyData.Slots, iface.Cond{\n\t\t\t\tTempC: func() *float32 {\n\t\t\t\t\tx := float32(weatherData.Result.Hourly.Temperature[index].Value)\n\t\t\t\t\treturn &x\n\t\t\t\t}(),\n\t\t\t\tVisibleDistM: func() *float32 {\n\t\t\t\t\tx := float32(weatherHourlyData.Visibility[index].Value)\n\t\t\t\t\treturn &x\n\t\t\t\t}(),\n\t\t\t\tHumidity: func() *int {\n\t\t\t\t\tx := int(weatherHourlyData.Humidity[index].Value)\n\t\t\t\t\treturn &x\n\t\t\t\t}(),\n\t\t\t\tWindspeedKmph: func() *float32 {\n\t\t\t\t\tx := float32(weatherHourlyData.Wind[index].Speed)\n\t\t\t\t\treturn &x\n\t\t\t\t}(),\n\t\t\t\tWinddirDegree: func() *int {\n\t\t\t\t\tx := int(weatherHourlyData.Wind[index].Direction)\n\t\t\t\t\treturn &x\n\t\t\t\t}(),\n\t\t\t\tTime: func() time.Time {\n\t\t\t\t\tx, err := time.Parse(CAIYUNDATE_TMPL, houryTmp.Datetime)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t}\n\t\t\t\t\treturn x\n\t\t\t\t}(),\n\t\t\t\tCode: func() iface.WeatherCode {\n\t\t\t\t\tif code, ok := SkyconToIfaceCode[weatherHourlyData.Skycon[index].Value]; ok {\n\t\t\t\t\t\treturn code\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn iface.CodeUnknown\n\t\t\t\t\t}\n\t\t\t\t}(),\n\t\t\t\tPrecipM: func() *float32 {\n\t\t\t\t\tx := float32(weatherHourlyData.Precipitation[index].Value) / 1000\n\t\t\t\t\treturn &x\n\t\t\t\t}(),\n\t\t\t\tFeelsLikeC: func() *float32 {\n\t\t\t\t\tx := float32(weatherData.Result.Hourly.ApparentTemperature[index].Value)\n\t\t\t\t\treturn &x\n\t\t\t\t}(),\n\t\t\t})\n\t\t}\n\n\t\tdailyDataSlice = append(dailyDataSlice, dailyData)\n\t}\n\tres.Forecast = dailyDataSlice\n\n\tres.GeoLoc = &iface.LatLon{\n\t\tLatitude:  float32(weatherData.Location[0]),\n\t\tLongitude: float32(weatherData.Location[1]),\n\t}\n\treturn res\n}\n\nfunc init() {\n\tiface.AllBackends[\"caiyunapp.com\"] = &CaiyunConfig{}\n}\n\ntype CaiyunWeather struct {\n\tStatus     string    `json:\"status\"`\n\tAPIVersion string    `json:\"api_version\"`\n\tAPIStatus  string    `json:\"api_status\"`\n\tLang       string    `json:\"lang\"`\n\tUnit       string    `json:\"unit\"`\n\tTzshift    int       `json:\"tzshift\"`\n\tTimezone   string    `json:\"timezone\"`\n\tServerTime int       `json:\"server_time\"`\n\tLocation   []float64 `json:\"location\"`\n\tResult     struct {\n\t\tAlert struct {\n\t\t\tStatus  string `json:\"status\"`\n\t\t\tContent []struct {\n\t\t\t\tProvince      string    `json:\"province\"`\n\t\t\t\tStatus        string    `json:\"status\"`\n\t\t\t\tCode          string    `json:\"code\"`\n\t\t\t\tDescription   string    `json:\"description\"`\n\t\t\t\tRegionID      string    `json:\"regionId\"`\n\t\t\t\tCounty        string    `json:\"county\"`\n\t\t\t\tPubtimestamp  int       `json:\"pubtimestamp\"`\n\t\t\t\tLatlon        []float64 `json:\"latlon\"`\n\t\t\t\tCity          string    `json:\"city\"`\n\t\t\t\tAlertID       string    `json:\"alertId\"`\n\t\t\t\tTitle         string    `json:\"title\"`\n\t\t\t\tAdcode        string    `json:\"adcode\"`\n\t\t\t\tSource        string    `json:\"source\"`\n\t\t\t\tLocation      string    `json:\"location\"`\n\t\t\t\tRequestStatus string    `json:\"request_status\"`\n\t\t\t} `json:\"content\"`\n\t\t\tAdcodes []struct {\n\t\t\t\tAdcode int    `json:\"adcode\"`\n\t\t\t\tName   string `json:\"name\"`\n\t\t\t} `json:\"adcodes\"`\n\t\t} `json:\"alert\"`\n\t\tRealtime struct {\n\t\t\tStatus      string  `json:\"status\"`\n\t\t\tTemperature float64 `json:\"temperature\"`\n\t\t\tHumidity    float64 `json:\"humidity\"`\n\t\t\tCloudrate   float64 `json:\"cloudrate\"`\n\t\t\tSkycon      string  `json:\"skycon\"`\n\t\t\tVisibility  float64 `json:\"visibility\"`\n\t\t\tDswrf       float64 `json:\"dswrf\"`\n\t\t\tWind        struct {\n\t\t\t\tSpeed     float64 `json:\"speed\"`\n\t\t\t\tDirection float64 `json:\"direction\"`\n\t\t\t} `json:\"wind\"`\n\t\t\tPressure            float64 `json:\"pressure\"`\n\t\t\tApparentTemperature float64 `json:\"apparent_temperature\"`\n\t\t\tPrecipitation       struct {\n\t\t\t\tLocal struct {\n\t\t\t\t\tStatus     string  `json:\"status\"`\n\t\t\t\t\tDatasource string  `json:\"datasource\"`\n\t\t\t\t\tIntensity  float64 `json:\"intensity\"`\n\t\t\t\t} `json:\"local\"`\n\t\t\t\tNearest struct {\n\t\t\t\t\tStatus    string  `json:\"status\"`\n\t\t\t\t\tDistance  float64 `json:\"distance\"`\n\t\t\t\t\tIntensity float64 `json:\"intensity\"`\n\t\t\t\t} `json:\"nearest\"`\n\t\t\t} `json:\"precipitation\"`\n\t\t\tAirQuality struct {\n\t\t\t\tPm25 int     `json:\"pm25\"`\n\t\t\t\tPm10 int     `json:\"pm10\"`\n\t\t\t\tO3   int     `json:\"o3\"`\n\t\t\t\tSo2  int     `json:\"so2\"`\n\t\t\t\tNo2  int     `json:\"no2\"`\n\t\t\t\tCo   float64 `json:\"co\"`\n\t\t\t\tAqi  struct {\n\t\t\t\t\tChn int `json:\"chn\"`\n\t\t\t\t\tUsa int `json:\"usa\"`\n\t\t\t\t} `json:\"aqi\"`\n\t\t\t\tDescription struct {\n\t\t\t\t\tChn string `json:\"chn\"`\n\t\t\t\t\tUsa string `json:\"usa\"`\n\t\t\t\t} `json:\"description\"`\n\t\t\t} `json:\"air_quality\"`\n\t\t\tLifeIndex struct {\n\t\t\t\tUltraviolet struct {\n\t\t\t\t\tIndex float64 `json:\"index\"`\n\t\t\t\t\tDesc  string  `json:\"desc\"`\n\t\t\t\t} `json:\"ultraviolet\"`\n\t\t\t\tComfort struct {\n\t\t\t\t\tIndex int    `json:\"index\"`\n\t\t\t\t\tDesc  string `json:\"desc\"`\n\t\t\t\t} `json:\"comfort\"`\n\t\t\t} `json:\"life_index\"`\n\t\t} `json:\"realtime\"`\n\t\tMinutely struct {\n\t\t\tStatus          string    `json:\"status\"`\n\t\t\tDatasource      string    `json:\"datasource\"`\n\t\t\tPrecipitation2H []float64 `json:\"precipitation_2h\"`\n\t\t\tPrecipitation   []float64 `json:\"precipitation\"`\n\t\t\tProbability     []float64 `json:\"probability\"`\n\t\t\tDescription     string    `json:\"description\"`\n\t\t} `json:\"minutely\"`\n\t\tHourly struct {\n\t\t\tStatus        string `json:\"status\"`\n\t\t\tDescription   string `json:\"description\"`\n\t\t\tPrecipitation []struct {\n\t\t\t\tDatetime string  `json:\"datetime\"`\n\t\t\t\tValue    float64 `json:\"value\"`\n\t\t\t} `json:\"precipitation\"`\n\t\t\tTemperature []struct {\n\t\t\t\tDatetime string  `json:\"datetime\"`\n\t\t\t\tValue    float64 `json:\"value\"`\n\t\t\t} `json:\"temperature\"`\n\t\t\tApparentTemperature []struct {\n\t\t\t\tDatetime string  `json:\"datetime\"`\n\t\t\t\tValue    float64 `json:\"value\"`\n\t\t\t} `json:\"apparent_temperature\"`\n\t\t\tWind []struct {\n\t\t\t\tDatetime  string  `json:\"datetime\"`\n\t\t\t\tSpeed     float64 `json:\"speed\"`\n\t\t\t\tDirection float64 `json:\"direction\"`\n\t\t\t} `json:\"wind\"`\n\t\t\tHumidity []struct {\n\t\t\t\tDatetime string  `json:\"datetime\"`\n\t\t\t\tValue    float64 `json:\"value\"`\n\t\t\t} `json:\"humidity\"`\n\t\t\tCloudrate []struct {\n\t\t\t\tDatetime string  `json:\"datetime\"`\n\t\t\t\tValue    float64 `json:\"value\"`\n\t\t\t} `json:\"cloudrate\"`\n\t\t\tSkycon []struct {\n\t\t\t\tDatetime string `json:\"datetime\"`\n\t\t\t\tValue    string `json:\"value\"`\n\t\t\t} `json:\"skycon\"`\n\t\t\tPressure []struct {\n\t\t\t\tDatetime string  `json:\"datetime\"`\n\t\t\t\tValue    float64 `json:\"value\"`\n\t\t\t} `json:\"pressure\"`\n\t\t\tVisibility []struct {\n\t\t\t\tDatetime string  `json:\"datetime\"`\n\t\t\t\tValue    float64 `json:\"value\"`\n\t\t\t} `json:\"visibility\"`\n\t\t\tDswrf []struct {\n\t\t\t\tDatetime string  `json:\"datetime\"`\n\t\t\t\tValue    float64 `json:\"value\"`\n\t\t\t} `json:\"dswrf\"`\n\t\t\tAirQuality struct {\n\t\t\t\tAqi []struct {\n\t\t\t\t\tDatetime string `json:\"datetime\"`\n\t\t\t\t\tValue    struct {\n\t\t\t\t\t\tChn int `json:\"chn\"`\n\t\t\t\t\t\tUsa int `json:\"usa\"`\n\t\t\t\t\t} `json:\"value\"`\n\t\t\t\t} `json:\"aqi\"`\n\t\t\t\tPm25 []struct {\n\t\t\t\t\tDatetime string `json:\"datetime\"`\n\t\t\t\t\tValue    int    `json:\"value\"`\n\t\t\t\t} `json:\"pm25\"`\n\t\t\t} `json:\"air_quality\"`\n\t\t} `json:\"hourly\"`\n\t\tDaily struct {\n\t\t\tStatus string `json:\"status\"`\n\t\t\tAstro  []struct {\n\t\t\t\tDate    string `json:\"date\"`\n\t\t\t\tSunrise struct {\n\t\t\t\t\tTime string `json:\"time\"`\n\t\t\t\t} `json:\"sunrise\"`\n\t\t\t\tSunset struct {\n\t\t\t\t\tTime string `json:\"time\"`\n\t\t\t\t} `json:\"sunset\"`\n\t\t\t} `json:\"astro\"`\n\t\t\tPrecipitation []struct {\n\t\t\t\tDate string  `json:\"date\"`\n\t\t\t\tMax  float64 `json:\"max\"`\n\t\t\t\tMin  float64 `json:\"min\"`\n\t\t\t\tAvg  float64 `json:\"avg\"`\n\t\t\t} `json:\"precipitation\"`\n\t\t\tTemperature []struct {\n\t\t\t\tDate string  `json:\"date\"`\n\t\t\t\tMax  float64 `json:\"max\"`\n\t\t\t\tMin  float64 `json:\"min\"`\n\t\t\t\tAvg  float64 `json:\"avg\"`\n\t\t\t} `json:\"temperature\"`\n\t\t\tTemperature08H20H []struct {\n\t\t\t\tDate string  `json:\"date\"`\n\t\t\t\tMax  float64 `json:\"max\"`\n\t\t\t\tMin  float64 `json:\"min\"`\n\t\t\t\tAvg  float64 `json:\"avg\"`\n\t\t\t} `json:\"temperature_08h_20h\"`\n\t\t\tTemperature20H32H []struct {\n\t\t\t\tDate string  `json:\"date\"`\n\t\t\t\tMax  float64 `json:\"max\"`\n\t\t\t\tMin  float64 `json:\"min\"`\n\t\t\t\tAvg  float64 `json:\"avg\"`\n\t\t\t} `json:\"temperature_20h_32h\"`\n\t\t\tWind []struct {\n\t\t\t\tDate string `json:\"date\"`\n\t\t\t\tMax  struct {\n\t\t\t\t\tSpeed     float64 `json:\"speed\"`\n\t\t\t\t\tDirection float64 `json:\"direction\"`\n\t\t\t\t} `json:\"max\"`\n\t\t\t\tMin struct {\n\t\t\t\t\tSpeed     float64 `json:\"speed\"`\n\t\t\t\t\tDirection float64 `json:\"direction\"`\n\t\t\t\t} `json:\"min\"`\n\t\t\t\tAvg struct {\n\t\t\t\t\tSpeed     float64 `json:\"speed\"`\n\t\t\t\t\tDirection float64 `json:\"direction\"`\n\t\t\t\t} `json:\"avg\"`\n\t\t\t} `json:\"wind\"`\n\t\t\tWind08H20H []struct {\n\t\t\t\tDate string `json:\"date\"`\n\t\t\t\tMax  struct {\n\t\t\t\t\tSpeed     float64 `json:\"speed\"`\n\t\t\t\t\tDirection float64 `json:\"direction\"`\n\t\t\t\t} `json:\"max\"`\n\t\t\t\tMin struct {\n\t\t\t\t\tSpeed     float64 `json:\"speed\"`\n\t\t\t\t\tDirection float64 `json:\"direction\"`\n\t\t\t\t} `json:\"min\"`\n\t\t\t\tAvg struct {\n\t\t\t\t\tSpeed     float64 `json:\"speed\"`\n\t\t\t\t\tDirection float64 `json:\"direction\"`\n\t\t\t\t} `json:\"avg\"`\n\t\t\t} `json:\"wind_08h_20h\"`\n\t\t\tWind20H32H []struct {\n\t\t\t\tDate string `json:\"date\"`\n\t\t\t\tMax  struct {\n\t\t\t\t\tSpeed     float64 `json:\"speed\"`\n\t\t\t\t\tDirection float64 `json:\"direction\"`\n\t\t\t\t} `json:\"max\"`\n\t\t\t\tMin struct {\n\t\t\t\t\tSpeed     float64 `json:\"speed\"`\n\t\t\t\t\tDirection float64 `json:\"direction\"`\n\t\t\t\t} `json:\"min\"`\n\t\t\t\tAvg struct {\n\t\t\t\t\tSpeed     float64 `json:\"speed\"`\n\t\t\t\t\tDirection float64 `json:\"direction\"`\n\t\t\t\t} `json:\"avg\"`\n\t\t\t} `json:\"wind_20h_32h\"`\n\t\t\tHumidity []struct {\n\t\t\t\tDate string  `json:\"date\"`\n\t\t\t\tMax  float64 `json:\"max\"`\n\t\t\t\tMin  float64 `json:\"min\"`\n\t\t\t\tAvg  float64 `json:\"avg\"`\n\t\t\t} `json:\"humidity\"`\n\t\t\tCloudrate []struct {\n\t\t\t\tDate string  `json:\"date\"`\n\t\t\t\tMax  float64 `json:\"max\"`\n\t\t\t\tMin  float64 `json:\"min\"`\n\t\t\t\tAvg  float64 `json:\"avg\"`\n\t\t\t} `json:\"cloudrate\"`\n\t\t\tPressure []struct {\n\t\t\t\tDate string  `json:\"date\"`\n\t\t\t\tMax  float64 `json:\"max\"`\n\t\t\t\tMin  float64 `json:\"min\"`\n\t\t\t\tAvg  float64 `json:\"avg\"`\n\t\t\t} `json:\"pressure\"`\n\t\t\tVisibility []struct {\n\t\t\t\tDate string  `json:\"date\"`\n\t\t\t\tMax  float64 `json:\"max\"`\n\t\t\t\tMin  float64 `json:\"min\"`\n\t\t\t\tAvg  float64 `json:\"avg\"`\n\t\t\t} `json:\"visibility\"`\n\t\t\tDswrf []struct {\n\t\t\t\tDate string  `json:\"date\"`\n\t\t\t\tMax  float64 `json:\"max\"`\n\t\t\t\tMin  float64 `json:\"min\"`\n\t\t\t\tAvg  float64 `json:\"avg\"`\n\t\t\t} `json:\"dswrf\"`\n\t\t\tAirQuality struct {\n\t\t\t\tAqi []struct {\n\t\t\t\t\tDate string `json:\"date\"`\n\t\t\t\t\tMax  struct {\n\t\t\t\t\t\tChn int `json:\"chn\"`\n\t\t\t\t\t\tUsa int `json:\"usa\"`\n\t\t\t\t\t} `json:\"max\"`\n\t\t\t\t\tAvg struct {\n\t\t\t\t\t\tChn float64 `json:\"chn\"`\n\t\t\t\t\t\tUsa float64 `json:\"usa\"`\n\t\t\t\t\t} `json:\"avg\"`\n\t\t\t\t\tMin struct {\n\t\t\t\t\t\tChn int `json:\"chn\"`\n\t\t\t\t\t\tUsa int `json:\"usa\"`\n\t\t\t\t\t} `json:\"min\"`\n\t\t\t\t} `json:\"aqi\"`\n\t\t\t\tPm25 []struct {\n\t\t\t\t\tDate string  `json:\"date\"`\n\t\t\t\t\tMax  int     `json:\"max\"`\n\t\t\t\t\tAvg  float64 `json:\"avg\"`\n\t\t\t\t\tMin  int     `json:\"min\"`\n\t\t\t\t} `json:\"pm25\"`\n\t\t\t} `json:\"air_quality\"`\n\t\t\tSkycon []struct {\n\t\t\t\tDate  string `json:\"date\"`\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t} `json:\"skycon\"`\n\t\t\tSkycon08H20H []struct {\n\t\t\t\tDate  string `json:\"date\"`\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t} `json:\"skycon_08h_20h\"`\n\t\t\tSkycon20H32H []struct {\n\t\t\t\tDate  string `json:\"date\"`\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t} `json:\"skycon_20h_32h\"`\n\t\t\tLifeIndex struct {\n\t\t\t\tUltraviolet []struct {\n\t\t\t\t\tDate  string `json:\"date\"`\n\t\t\t\t\tIndex string `json:\"index\"`\n\t\t\t\t\tDesc  string `json:\"desc\"`\n\t\t\t\t} `json:\"ultraviolet\"`\n\t\t\t\tCarWashing []struct {\n\t\t\t\t\tDate  string `json:\"date\"`\n\t\t\t\t\tIndex string `json:\"index\"`\n\t\t\t\t\tDesc  string `json:\"desc\"`\n\t\t\t\t} `json:\"carWashing\"`\n\t\t\t\tDressing []struct {\n\t\t\t\t\tDate  string `json:\"date\"`\n\t\t\t\t\tIndex string `json:\"index\"`\n\t\t\t\t\tDesc  string `json:\"desc\"`\n\t\t\t\t} `json:\"dressing\"`\n\t\t\t\tComfort []struct {\n\t\t\t\t\tDate  string `json:\"date\"`\n\t\t\t\t\tIndex string `json:\"index\"`\n\t\t\t\t\tDesc  string `json:\"desc\"`\n\t\t\t\t} `json:\"comfort\"`\n\t\t\t\tColdRisk []struct {\n\t\t\t\t\tDate  string `json:\"date\"`\n\t\t\t\t\tIndex string `json:\"index\"`\n\t\t\t\t\tDesc  string `json:\"desc\"`\n\t\t\t\t} `json:\"coldRisk\"`\n\t\t\t} `json:\"life_index\"`\n\t\t} `json:\"daily\"`\n\t\tPrimary          int    `json:\"primary\"`\n\t\tForecastKeypoint string `json:\"forecast_keypoint\"`\n\t} `json:\"result\"`\n}\n"
  },
  {
    "path": "backends/json.go",
    "content": "package backends\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"log\"\n\n\t\"github.com/schachmat/wego/iface\"\n)\n\ntype jsnConfig struct {\n}\n\nfunc (c *jsnConfig) Setup() {\n}\n\n// Fetch will try to open the file specified in the location string argument and\n// read it as json content to fill the data. The numdays argument will only work\n// to further limit the amount of days in the output. It obviously cannot\n// produce more data than is available in the file.\nfunc (c *jsnConfig) Fetch(loc string, numdays int) (ret iface.Data) {\n\tb, err := os.ReadFile(loc)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\terr = json.Unmarshal(b, &ret)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tif len(ret.Forecast) > numdays {\n\t\tret.Forecast = ret.Forecast[:numdays]\n\t}\n\treturn\n}\n\nfunc init() {\n\tiface.AllBackends[\"json\"] = &jsnConfig{}\n}\n"
  },
  {
    "path": "backends/open-meteo.com.go",
    "content": "package backends\n\nimport (\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/schachmat/wego/iface\"\n)\n\ntype openmeteoConfig struct {\n\tapiKey   string\n\tlanguage string\n\tdebug    bool\n}\n\ntype curCond struct {\n\tTime                int64    `json:\"time\"`\n\tInterval            int      `json:\"interval\"`\n\tTemperature2M       *float32 `json:\"temperature_2m\"`\n\tApparentTemperature *float32 `json:\"apparent_temperature\"`\n\tIsDay               int      `json:\"is_day\"`\n\tWeatherCode         int      `json:\"weather_code\"`\n\tWindDirection10M    *int     `json:\"wind_direction_10m\"`\n}\n\ntype Daily struct {\n\tTime                   []int64    `json:\"time\"`\n\tWeatherCode            []int      `json:\"weather_code\"`\n\tTemperature2MMax       []*float32 `json:\"temperature_2m_max\"`\n\tApparentTemperatureMax []*float32 `json:\"apparent_temperature_max\"`\n\tSunrise                []int64    `json:\"sunrise\"`\n\tSunset                 []int64    `json:\"sunset\"`\n}\ntype HourlyUnits struct {\n\tTime                string `json:\"time\"`\n\tTemperature2M       string `json:\"temperature_2m\"`\n\tApparentTemperature string `json:\"apparent_temperature\"`\n\tWeatherCode         string `json:\"weather_code\"`\n}\ntype Hourly struct {\n\tTime                []int64    `json:\"time\"`\n\tTemperature2M       []*float32 `json:\"temperature_2m\"`\n\tApparentTemperature []*float32 `json:\"apparent_temperature\"`\n\tWeatherCode         []int      `json:\"weather_code\"`\n\tWindDirection10M    []*int     `json:\"wind_direction_10m\"`\n}\n\ntype openmeteoResponse struct {\n\tLatitude             float64 `json:\"latitude\"`\n\tLongitude            float64 `json:\"longitude\"`\n\tGenerationtimeMs     float64 `json:\"generationtime_ms\"`\n\tUtcOffsetSeconds     int     `json:\"utc_offset_seconds\"`\n\tTimezone             string  `json:\"timezone\"`\n\tTimezoneAbbreviation string  `json:\"timezone_abbreviation\"`\n\tElevation            float64 `json:\"elevation\"`\n\tCurrentUnits         struct {\n\t\tTime                string `json:\"time\"`\n\t\tInterval            string `json:\"interval\"`\n\t\tTemperature2M       string `json:\"temperature_2m\"`\n\t\tApparentTemperature string `json:\"apparent_temperature\"`\n\t\tIsDay               string `json:\"is_day\"`\n\t\tWeatherCode         string `json:\"weather_code\"`\n\t} `json:\"current_units\"`\n\tCurrent     curCond     `json:\"current\"`\n\tHourlyUnits HourlyUnits `json:\"hourly_units\"`\n\tHourly      Hourly      `json:\"hourly\"`\n\tDailyUnits  struct {\n\t\tTime                   string `json:\"time\"`\n\t\tWeatherCode            string `json:\"weather_code\"`\n\t\tTemperature2MMax       string `json:\"temperature_2m_max\"`\n\t\tApparentTemperatureMax string `json:\"apparent_temperature_max\"`\n\t\tSunrise                string `json:\"sunrise\"`\n\t\tSunset                 string `json:\"sunset\"`\n\t} `json:\"daily_units\"`\n\tDaily Daily\n}\n\nconst (\n\topenmeteoURI = \"https://api.open-meteo.com/v1/forecast?\"\n)\n\nvar (\n\tcodemap = map[int]iface.WeatherCode{\n\t\t0:  iface.CodeSunny,\n\t\t1:  iface.CodePartlyCloudy,\n\t\t2:  iface.CodePartlyCloudy,\n\t\t3:  iface.CodePartlyCloudy,\n\t\t45: iface.CodeFog,\n\t\t48: iface.CodeFog,\n\t\t51: iface.CodeLightRain,\n\t\t53: iface.CodeLightRain,\n\t\t55: iface.CodeLightRain,\n\t\t56: iface.CodeLightSleet,\n\t\t57: iface.CodeLightSleet,\n\t\t61: iface.CodeLightShowers,\n\t\t63: iface.CodeLightShowers,\n\t\t65: iface.CodeLightShowers,\n\t\t66: iface.CodeHeavyRain,\n\t\t67: iface.CodeHeavyRain,\n\t}\n)\n\nfunc (opmeteo *openmeteoConfig) Setup() {\n\tflag.StringVar(&opmeteo.apiKey, \"openmeteo-api-key\", \"\", \"openmeteo backend: the api `KEY` to use if commercial usage\")\n\tflag.BoolVar(&opmeteo.debug, \"openmeteo-debug\", false, \"openmeteo backend: print raw requests and responses\")\n}\n\nfunc (opmeteo *openmeteoConfig) parseDaily(dailyInfo Hourly) []iface.Day {\n\tvar forecast []iface.Day\n\tvar day *iface.Day\n\n\tfor ind, dayTime := range dailyInfo.Time {\n\n\t\tcond := new(iface.Cond)\n\n\t\tcond.Code = codemap[dailyInfo.WeatherCode[ind]]\n\t\tcond.TempC = dailyInfo.Temperature2M[ind]\n\t\tcond.FeelsLikeC = dailyInfo.ApparentTemperature[ind]\n\t\tcond.Time = time.Unix(dayTime, 0)\n\t\tcond.WinddirDegree = dailyInfo.WindDirection10M[ind]\n\n\t\tif day == nil {\n\t\t\tday = new(iface.Day)\n\t\t\tday.Date = cond.Time\n\t\t}\n\t\tif day.Date.Day() == cond.Time.Day() {\n\t\t\tday.Slots = append(day.Slots, *cond)\n\t\t}\n\t\tif day.Date.Day() != cond.Time.Day() {\n\t\t\tforecast = append(forecast, *day)\n\n\t\t\tday = new(iface.Day)\n\t\t\tday.Date = cond.Time\n\t\t\tday.Slots = append(day.Slots, *cond)\n\t\t}\n\t}\n\n\treturn forecast\n}\n\nfunc parseCurCond(current curCond) (ret iface.Cond) {\n\n\tret.Time = time.Unix(current.Time, 0)\n\n\tret.Code = iface.CodeUnknown\n\tif val, ok := codemap[current.WeatherCode]; ok {\n\t\tret.Code = val\n\t}\n\n\tret.TempC = current.Temperature2M\n\tret.FeelsLikeC = current.ApparentTemperature\n\tret.WinddirDegree = current.WindDirection10M\n\treturn ret\n\n}\n\nfunc (opmeteo *openmeteoConfig) Fetch(location string, numdays int) iface.Data {\n\tvar ret iface.Data\n\tvar params []string\n\tvar loc string\n\n\tif numdays <= 0 {\n\t\tlog.Fatal(\"Number of days less than 1 \")\n\t}\n\n\tif matched, err := regexp.MatchString(`^-?[0-9]*(\\.[0-9]+)?,-?[0-9]*(\\.[0-9]+)?$`, location); matched && err == nil {\n\t\ts := strings.Split(location, \",\")\n\t\tloc = fmt.Sprintf(\"latitude=%s&longitude=%s\", s[0], s[1])\n\t}\n\tif len(location) > 0 {\n\t\tparams = append(params, loc)\n\t}\n\tparams = append(params, \"current=temperature_2m,apparent_temperature,is_day,weather_code,wind_direction_10m\")\n\tparams = append(params, \"hourly=temperature_2m,apparent_temperature,weather_code,wind_direction_10m\")\n\tparams = append(params, \"daily=weather_code,temperature_2m_max,apparent_temperature_max,sunrise,sunset\")\n\tparams = append(params, fmt.Sprintf(\"timeformat=unixtime&forecast_days=%d\", numdays))\n\n\trequri := openmeteoURI + strings.Join(params, \"&\")\n\n\tres, err := http.Get(requri)\n\tif err != nil {\n\t\tlog.Fatal(\"Unable to get weather data: \", err)\n\t} else if res.StatusCode != 200 {\n\t\tlog.Fatal(\"Unable to get weather data: http status \", res.StatusCode)\n\t}\n\tdefer res.Body.Close()\n\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tif opmeteo.debug {\n\t\tlog.Println(\"Weather request:\", requri)\n\t\tb, _ := json.MarshalIndent(body, \"\", \"\\t\")\n\t\tfmt.Println(\"Weather response:\", string(b))\n\t}\n\n\tvar resp openmeteoResponse\n\tif err = json.Unmarshal(body, &resp); err != nil {\n\t\tlog.Println(err)\n\t}\n\n\tret.Current = parseCurCond(resp.Current)\n\tret.Location = location\n\n\tforecast := opmeteo.parseDaily(resp.Hourly)\n\n\tfor i, _ := range forecast {\n\t\tforecast[i].Astronomy.Sunset = time.Unix(resp.Daily.Sunset[i], 0)\n\t\tforecast[i].Astronomy.Sunrise = time.Unix(resp.Daily.Sunrise[i], 0)\n\t}\n\tif len(forecast) > 0 {\n\t\tret.Forecast = forecast\n\t}\n\treturn ret\n}\n\nfunc init() {\n\tiface.AllBackends[\"openmeteo\"] = &openmeteoConfig{}\n}\n"
  },
  {
    "path": "backends/openweathermap.org.go",
    "content": "package backends\n\nimport (\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"github.com/schachmat/wego/iface\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype openWeatherConfig struct {\n\tapiKey string\n\tlang   string\n\tdebug  bool\n}\n\ntype openWeatherResponse struct {\n\tCod  string `json:\"cod\"`\n\tCity struct {\n\t\tName    string `json:\"name\"`\n\t\tCountry string `json:\"country\"`\n\t\tTimeZone int64 `json: \"timezone\"`\n\t\t// sunrise/sunset are once per call\n\t\tSunRise int64 `json: \"sunrise\"`\n\t\tSunSet int64 `json: \"sunset\"`\n\t} `json:\"city\"`\n\tList []dataBlock `json:\"list\"`\n}\n\ntype dataBlock struct {\n\tDt   int64 `json:\"dt\"`\n\tMain struct {\n\t\tTempC      float32 `json:\"temp\"`\n\t\tFeelsLikeC float32 `json:\"feels_like\"`\n\t\tHumidity   int     `json:\"humidity\"`\n\t} `json:\"main\"`\n\n\tWeather []struct {\n\t\tDescription string `json:\"description\"`\n\t\tID          int    `json:\"id\"`\n\t} `json:\"weather\"`\n\n\tWind struct {\n\t\tSpeed float32 `json:\"speed\"`\n\t\tDeg   float32 `json:\"deg\"`\n\t} `json:\"wind\"`\n\n\tRain struct {\n\t\tMM3h float32 `json:\"3h\"`\n\t} `json:\"rain\"`\n}\n\nconst (\n\topenweatherURI = \"http://api.openweathermap.org/data/2.5/forecast?%s&appid=%s&units=metric&lang=%s\"\n)\n\nfunc (c *openWeatherConfig) Setup() {\n\tflag.StringVar(&c.apiKey, \"owm-api-key\", \"\", \"openweathermap backend: the api `KEY` to use\")\n\tflag.StringVar(&c.lang, \"owm-lang\", \"en\", \"openweathermap backend: the `LANGUAGE` to request from openweathermap\")\n\tflag.BoolVar(&c.debug, \"owm-debug\", false, \"openweathermap backend: print raw requests and responses\")\n}\n\nfunc (c *openWeatherConfig) fetch(url string) (*openWeatherResponse, error) {\n\tres, err := http.Get(url)\n\tif c.debug {\n\t\tfmt.Printf(\"Fetching %s\\n\", url)\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\" Unable to get (%s) %v\", url, err)\n\t}\n\tdefer res.Body.Close()\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Unable to read response body (%s): %v\", url, err)\n\t}\n\n\tif c.debug {\n\t\tfmt.Printf(\"Response (%s):\\n%s\\n\", url, string(body))\n\t}\n\n\tvar resp openWeatherResponse\n\tif err = json.Unmarshal(body, &resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"Unable to unmarshal response (%s): %v\\nThe json body is: %s\", url, err, string(body))\n\t}\n\tif resp.Cod != \"200\" {\n\t\treturn nil, fmt.Errorf(\"Erroneous response body: %s\", string(body))\n\t}\n\treturn &resp, nil\n}\n\nfunc (c *openWeatherConfig) parseDaily(dataInfo []dataBlock, numdays int) []iface.Day {\n\tvar forecast []iface.Day\n\tvar day *iface.Day\n\n\tfor _, data := range dataInfo {\n\t\tslot, err := c.parseCond(data)\n\t\tif err != nil {\n\t\t\tlog.Println(\"Error parsing hourly weather condition:\", err)\n\t\t\tcontinue\n\t\t}\n\t\tif day == nil {\n\t\t\tday = new(iface.Day)\n\t\t\tday.Date = slot.Time\n\t\t}\n\t\tif day.Date.Day() == slot.Time.Day() {\n\t\t\tday.Slots = append(day.Slots, slot)\n\t\t}\n\t\tif day.Date.Day() != slot.Time.Day() {\n\t\t\tforecast = append(forecast, *day)\n\t\t\tif len(forecast) >= numdays {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tday = new(iface.Day)\n\t\t\tday.Date = slot.Time\n\t\t\tday.Slots = append(day.Slots, slot)\n\t\t}\n\n\t}\n\treturn forecast\n}\n\nfunc (c *openWeatherConfig) parseCond(dataInfo dataBlock) (iface.Cond, error) {\n\tvar ret iface.Cond\n\tcodemap := map[int]iface.WeatherCode{\n\t\t200: iface.CodeThunderyShowers,\n\t\t201: iface.CodeThunderyShowers,\n\t\t210: iface.CodeThunderyShowers,\n\t\t230: iface.CodeThunderyShowers,\n\t\t231: iface.CodeThunderyShowers,\n\t\t202: iface.CodeThunderyHeavyRain,\n\t\t211: iface.CodeThunderyHeavyRain,\n\t\t212: iface.CodeThunderyHeavyRain,\n\t\t221: iface.CodeThunderyHeavyRain,\n\t\t232: iface.CodeThunderyHeavyRain,\n\t\t300: iface.CodeLightRain,\n\t\t301: iface.CodeLightRain,\n\t\t310: iface.CodeLightRain,\n\t\t311: iface.CodeLightRain,\n\t\t313: iface.CodeLightRain,\n\t\t321: iface.CodeLightRain,\n\t\t302: iface.CodeHeavyRain,\n\t\t312: iface.CodeHeavyRain,\n\t\t314: iface.CodeHeavyRain,\n\t\t500: iface.CodeLightShowers,\n\t\t501: iface.CodeLightShowers,\n\t\t502: iface.CodeHeavyShowers,\n\t\t503: iface.CodeHeavyShowers,\n\t\t504: iface.CodeHeavyShowers,\n\t\t511: iface.CodeLightSleet,\n\t\t520: iface.CodeLightShowers,\n\t\t521: iface.CodeLightShowers,\n\t\t522: iface.CodeHeavyShowers,\n\t\t531: iface.CodeHeavyShowers,\n\t\t600: iface.CodeLightSnow,\n\t\t601: iface.CodeLightSnow,\n\t\t602: iface.CodeHeavySnow,\n\t\t611: iface.CodeLightSleet,\n\t\t612: iface.CodeLightSleetShowers,\n\t\t615: iface.CodeLightSleet,\n\t\t616: iface.CodeLightSleet,\n\t\t620: iface.CodeLightSnowShowers,\n\t\t621: iface.CodeLightSnowShowers,\n\t\t622: iface.CodeHeavySnowShowers,\n\t\t701: iface.CodeFog,\n\t\t711: iface.CodeFog,\n\t\t721: iface.CodeFog,\n\t\t741: iface.CodeFog,\n\t\t731: iface.CodeUnknown, // sand, dust whirls\n\t\t751: iface.CodeUnknown, // sand\n\t\t761: iface.CodeUnknown, // dust\n\t\t762: iface.CodeUnknown, // volcanic ash\n\t\t771: iface.CodeUnknown, // squalls\n\t\t781: iface.CodeUnknown, // tornado\n\t\t800: iface.CodeSunny,\n\t\t801: iface.CodePartlyCloudy,\n\t\t802: iface.CodeCloudy,\n\t\t803: iface.CodeVeryCloudy,\n\t\t804: iface.CodeVeryCloudy,\n\t\t900: iface.CodeUnknown, // tornado\n\t\t901: iface.CodeUnknown, // tropical storm\n\t\t902: iface.CodeUnknown, // hurricane\n\t\t903: iface.CodeUnknown, // cold\n\t\t904: iface.CodeUnknown, // hot\n\t\t905: iface.CodeUnknown, // windy\n\t\t906: iface.CodeUnknown, // hail\n\t\t951: iface.CodeUnknown, // calm\n\t\t952: iface.CodeUnknown, // light breeze\n\t\t953: iface.CodeUnknown, // gentle breeze\n\t\t954: iface.CodeUnknown, // moderate breeze\n\t\t955: iface.CodeUnknown, // fresh breeze\n\t\t956: iface.CodeUnknown, // strong breeze\n\t\t957: iface.CodeUnknown, // high wind, near gale\n\t\t958: iface.CodeUnknown, // gale\n\t\t959: iface.CodeUnknown, // severe gale\n\t\t960: iface.CodeUnknown, // storm\n\t\t961: iface.CodeUnknown, // violent storm\n\t\t962: iface.CodeUnknown, // hurricane\n\t}\n\n\tret.Code = iface.CodeUnknown\n\tret.Desc = dataInfo.Weather[0].Description\n\tret.Humidity = &(dataInfo.Main.Humidity)\n\tret.TempC = &(dataInfo.Main.TempC)\n\tret.FeelsLikeC = &(dataInfo.Main.FeelsLikeC)\n\tif &dataInfo.Wind.Deg != nil {\n\t\tp := int(dataInfo.Wind.Deg)\n\t\tret.WinddirDegree = &p\n\t}\n\tif &(dataInfo.Wind.Speed) != nil && (dataInfo.Wind.Speed) > 0 {\n\t\twindSpeed := (dataInfo.Wind.Speed * 3.6)\n\t\tret.WindspeedKmph = &(windSpeed)\n\t}\n\tif val, ok := codemap[dataInfo.Weather[0].ID]; ok {\n\t\tret.Code = val\n\t}\n\n\tif &dataInfo.Rain.MM3h != nil {\n\t\tmmh := (dataInfo.Rain.MM3h / 1000) / 3\n\t\tret.PrecipM = &mmh\n\t}\n\n\tret.Time = time.Unix(dataInfo.Dt, 0)\n\n\treturn ret, nil\n}\n\nfunc (c *openWeatherConfig) Fetch(location string, numdays int) iface.Data {\n\tvar ret iface.Data\n\tloc := \"\"\n\n\tif len(c.apiKey) == 0 {\n\t\tlog.Fatal(\"No openweathermap.org API key specified.\\nYou have to register for one at https://home.openweathermap.org/users/sign_up\")\n\t}\n\tif matched, err := regexp.MatchString(`^-?[0-9]*(\\.[0-9]+)?,-?[0-9]*(\\.[0-9]+)?$`, location); matched && err == nil {\n\t\ts := strings.Split(location, \",\")\n\t\tloc = fmt.Sprintf(\"lat=%s&lon=%s\", s[0], s[1])\n\t} else if matched, err = regexp.MatchString(`^[0-9].*`, location); matched && err == nil {\n\t\tloc = \"zip=\" + location\n\t} else {\n\t\tloc = \"q=\" + location\n\t}\n\n\tresp, err := c.fetch(fmt.Sprintf(openweatherURI, loc, c.apiKey, c.lang))\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to fetch weather data: %v\\n\", err)\n\t}\n\tret.Current, err = c.parseCond(resp.List[0])\n\tret.Location = fmt.Sprintf(\"%s, %s\", resp.City.Name, resp.City.Country)\n\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to fetch weather data: %v\\n\", err)\n\t}\n\n\tif numdays == 0 {\n\t\treturn ret\n\t}\n\tret.Forecast = c.parseDaily(resp.List, numdays)\n\n\t// add in the sunrise/sunset information to the first day\n\t// these maybe should deal with resp.City.TimeZone\n\tif len(ret.Forecast) > 0 {\n\t\tret.Forecast[0].Astronomy.Sunrise = time.Unix(resp.City.SunRise, 0)\n\t\tret.Forecast[0].Astronomy.Sunset = time.Unix(resp.City.SunSet, 0)\n\t}\n\n\treturn ret\n}\n\nfunc init() {\n\tiface.AllBackends[\"openweathermap\"] = &openWeatherConfig{}\n}\n"
  },
  {
    "path": "backends/smhi.go",
    "content": "package backends\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/schachmat/wego/iface\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype smhiConfig struct {\n}\n\ntype smhiDataPoint struct {\n\tLevel     int           `json:\"level\"`\n\tLevelType string        `json:\"levelType\"`\n\tName      string        `json:\"name\"`\n\tUnit      string        `json:\"unit\"`\n\tValues    []interface{} `json:\"values\"`\n}\n\ntype smhiTimeSeries struct {\n\tValidTime  string           `json:\"validTime\"`\n\tParameters []*smhiDataPoint `json:\"parameters\"`\n}\n\ntype smhiGeometry struct {\n\tCoordinates [][]float32 `json:\"coordinates\"`\n}\n\ntype smhiResponse struct {\n\tApprovedTime  string            `json:\"approvedTime\"`\n\tReferenceTime string            `json:\"referenceTime\"`\n\tGeometry      smhiGeometry      `json:\"geometry\"`\n\tTimeSeries    []*smhiTimeSeries `json:\"timeSeries\"`\n}\n\ntype smhiCondition struct {\n\tWeatherCode iface.WeatherCode\n\tDescription string\n}\n\nconst (\n\t// see http://opendata.smhi.se/apidocs/metfcst/index.html\n\tsmhiWuri = \"https://opendata-download-metfcst.smhi.se/api/category/pmp3g/version/2/geotype/point/lon/%s/lat/%s/data.json\"\n)\n\nvar (\n\tweatherConditions = map[int]smhiCondition{\n\t\t1:  {iface.CodeSunny, \"Clear Sky\"},\n\t\t2:  {iface.CodeSunny, \"Nearly Clear Sky\"},\n\t\t3:  {iface.CodePartlyCloudy, \"Variable cloudiness\"},\n\t\t4:  {iface.CodePartlyCloudy, \"Halfclear sky\"},\n\t\t5:  {iface.CodeCloudy, \"Cloudy sky\"},\n\t\t6:  {iface.CodeVeryCloudy, \"Overcast\"},\n\t\t7:  {iface.CodeFog, \"Fog\"},\n\t\t8:  {iface.CodeLightShowers, \"Light rain showers\"},\n\t\t9:  {iface.CodeLightShowers, \"Moderate rain showers\"},\n\t\t10: {iface.CodeHeavyShowers, \"Heavy rain showers\"},\n\t\t11: {iface.CodeThunderyShowers, \"Thunderstorm\"},\n\t\t12: {iface.CodeLightSleetShowers, \"Light sleet showers\"},\n\t\t13: {iface.CodeLightSleetShowers, \"Moderate sleet showers\"},\n\t\t14: {iface.CodeHeavySnowShowers, \"Heavy sleet showers\"},\n\t\t15: {iface.CodeLightSnowShowers, \"Light snow showers\"},\n\t\t16: {iface.CodeLightSnowShowers, \"Moderate snow showers\"},\n\t\t17: {iface.CodeHeavySnowShowers, \"Heavy snow showers\"},\n\t\t18: {iface.CodeLightRain, \"Light rain\"},\n\t\t19: {iface.CodeLightRain, \"Moderate rain\"},\n\t\t20: {iface.CodeHeavyRain, \"Heavy rain\"},\n\t\t21: {iface.CodeThunderyHeavyRain, \"Thunder\"},\n\t\t22: {iface.CodeLightSleet, \"Light sleet\"},\n\t\t23: {iface.CodeLightSleet, \"Moderate sleet\"},\n\t\t24: {iface.CodeHeavySnow, \"Heavy sleet\"},\n\t\t25: {iface.CodeLightSnow, \"Light snowfall\"},\n\t\t26: {iface.CodeLightSnow, \"Moderate snowfall\"},\n\t\t27: {iface.CodeHeavySnow, \"Heavy snowfall\"},\n\t}\n)\n\nfunc (c *smhiConfig) Setup() {\n}\n\nfunc (c *smhiConfig) fetch(url string) (*smhiResponse, error) {\n\tresp, err := http.Get(url)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Unable to get (%s): %v\", url, err)\n\t} else if resp.StatusCode != 200 {\n\t\tbody, _ := io.ReadAll(resp.Body)\n\t\tquip := \"\"\n\t\tif string(body) == \"Requested point is out of bounds\" {\n\t\t\tquip = \"\\nPlease note that SMHI only service the nordic countries.\"\n\t\t}\n\t\treturn nil, fmt.Errorf(\"Unable to get (%s): http status %d, %s%s\", url, resp.StatusCode, body, quip)\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Unable to read response body (%s): %v\", url, err)\n\t}\n\n\tvar response smhiResponse\n\terr = json.Unmarshal(body, &response)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"Unable to parse response (%s): %v\", url, err)\n\t}\n\treturn &response, nil\n\n}\n\nfunc (c *smhiConfig) Fetch(location string, numDays int) (ret iface.Data) {\n\tif matched, err := regexp.MatchString(`^-?[0-9]*(\\.[0-9]+)?,-?[0-9]*(\\.[0-9]+)?$`, location); !matched || err != nil {\n\t\tlog.Fatalf(\"Error: The smhi backend only supports latitude,longitude pairs as location.\\nInstead of `%s` try `59.329,18.068` for example to get a forecast for Stockholm.\", location)\n\t}\n\n\ts := strings.Split(location, \",\")\n\trequestUrl := fmt.Sprintf(smhiWuri, s[1], s[0])\n\n\tresp, err := c.fetch(requestUrl)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to fetch weather data: %v\\n\", err)\n\t}\n\n\tret.Current = c.parseCurrent(resp)\n\tret.Forecast = c.parseForecast(resp, numDays)\n\tcoordinates := resp.Geometry.Coordinates\n\tret.GeoLoc = &iface.LatLon{Latitude: coordinates[0][1], Longitude: coordinates[0][0]}\n\tret.Location = location + \" (Forecast provided by SMHI)\"\n\treturn ret\n}\nfunc (c *smhiConfig) parseForecast(response *smhiResponse, numDays int) (days []iface.Day) {\n\tif numDays > 10 {\n\t\tnumDays = 10\n\t}\n\n\tvar currentTime time.Time = time.Now()\n\tvar dayCount = 0\n\n\tvar day iface.Day\n\tday.Date = time.Now()\n\tfor _, prediction := range response.TimeSeries {\n\t\tif dayCount == numDays {\n\t\t\tbreak\n\t\t}\n\n\t\tts, err := time.Parse(time.RFC3339, prediction.ValidTime)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Failed to parse timestamp: %v\\n\", err)\n\t\t}\n\n\t\tif ts.Day() != currentTime.Day() {\n\t\t\tdayCount += 1\n\t\t\tcurrentTime = ts\n\t\t\tdays = append(days, day)\n\t\t\tday = iface.Day{Date: ts}\n\t\t}\n\t\tday.Slots = append(day.Slots, c.parsePrediction(prediction))\n\t}\n\n\treturn days\n}\n\nfunc (c *smhiConfig) parseCurrent(forecast *smhiResponse) (cnd iface.Cond) {\n\tif len(forecast.TimeSeries) < 0 {\n\t\tlog.Fatalln(\"Failed to fetch weather data: No Forecast in response\")\n\t}\n\tvar currentPrediction *smhiTimeSeries = forecast.TimeSeries[0]\n\tvar currentTime time.Time = time.Now().UTC()\n\n\tfor _, prediction := range forecast.TimeSeries {\n\t\tts, err := time.Parse(time.RFC3339, prediction.ValidTime)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Failed to parse timestamp: %v\\n\", err)\n\t\t}\n\n\t\tif ts.After(currentTime) {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn c.parsePrediction(currentPrediction)\n}\n\nfunc (c *smhiConfig) parsePrediction(prediction *smhiTimeSeries) (cnd iface.Cond) {\n\tts, err := time.Parse(time.RFC3339, prediction.ValidTime)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to parse timestamp: %v\\n\", err)\n\t}\n\tcnd.Time = ts\n\n\tfor _, param := range prediction.Parameters {\n\t\tswitch param.Name {\n\t\tcase \"pmean\":\n\t\t\tprecip := float32(param.Values[0].(float64) / 1000) // Convert mm/h to m/h\n\t\t\tcnd.PrecipM = &precip\n\t\tcase \"vis\":\n\t\t\tvis := float32(param.Values[0].(float64) * 1000) // Convert km to m\n\t\t\tcnd.VisibleDistM = &vis\n\t\tcase \"t\":\n\t\t\ttemp := float32(param.Values[0].(float64))\n\t\t\tcnd.TempC = &temp\n\t\tcase \"Wsymb2\":\n\t\t\tcondition := weatherConditions[int(param.Values[0].(float64))]\n\t\t\tcnd.Code = condition.WeatherCode\n\t\t\tcnd.Desc = condition.Description\n\t\tcase \"ws\":\n\t\t\twindSpeed := float32(param.Values[0].(float64) * 3.6) // convert m/s to km/h\n\t\t\tcnd.WindspeedKmph = &windSpeed\n\t\tcase \"gust\":\n\t\t\tgustSpeed := float32(param.Values[0].(float64) * 3.6) // convert m/s to km/h\n\t\t\tcnd.WindGustKmph = &gustSpeed\n\t\tcase \"wd\":\n\t\t\tval := int(param.Values[0].(float64))\n\t\t\tcnd.WinddirDegree = &val\n\t\tcase \"r\":\n\t\t\tval := int(param.Values[0].(float64))\n\t\t\tcnd.Humidity = &val\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\t}\n\n\treturn cnd\n}\n\nfunc init() {\n\tiface.AllBackends[\"smhi\"] = &smhiConfig{}\n}\n"
  },
  {
    "path": "backends/worldweatheronline.com.go",
    "content": "package backends\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t// needed for some go versions <1.4.2. TODO: Remove this import when golang\n\t// v1.4.2 or later is in debian stable and the latest Ubuntu LTS release.\n\t_ \"crypto/sha512\"\n\n\t\"github.com/schachmat/wego/iface\"\n)\n\ntype wwoCond struct {\n\tTmpCor        *int                     `json:\"chanceofrain,string\"`\n\tTmpCode       int                      `json:\"weatherCode,string\"`\n\tTmpDesc       []struct{ Value string } `json:\"weatherDesc\"`\n\tFeelsLikeC    *float32                 `json:\",string\"`\n\tPrecipMM      *float32                 `json:\"precipMM,string\"`\n\tTmpTempC      *float32                 `json:\"tempC,string\"`\n\tTmpTempC2     *float32                 `json:\"temp_C,string\"`\n\tTmpTime       *int                     `json:\"time,string\"`\n\tVisibleDistKM *float32                 `json:\"visibility,string\"`\n\tWindGustKmph  *float32                 `json:\",string\"`\n\tWinddirDegree *int                     `json:\"winddirDegree,string\"`\n\tWindspeedKmph *float32                 `json:\"windspeedKmph,string\"`\n}\n\ntype wwoDay struct {\n\tAstronomy []struct {\n\t\tMoonrise string\n\t\tMoonset  string\n\t\tSunrise  string\n\t\tSunset   string\n\t}\n\tDate   string\n\tHourly []wwoCond\n}\n\ntype wwoResponse struct {\n\tData struct {\n\t\tCurCond []wwoCond              `json:\"current_condition\"`\n\t\tErr     []struct{ Msg string } `json:\"error\"`\n\t\tReq     []struct {\n\t\t\tQuery string `json:\"query\"`\n\t\t\tType  string `json:\"type\"`\n\t\t} `json:\"request\"`\n\t\tDays []wwoDay `json:\"weather\"`\n\t} `json:\"data\"`\n}\n\ntype wwoCoordinateResp struct {\n\tSearch struct {\n\t\tResult []struct {\n\t\t\tLongitude *float32 `json:\"longitude,string\"`\n\t\t\tLatitude  *float32 `json:\"latitude,string\"`\n\t\t} `json:\"result\"`\n\t} `json:\"search_api\"`\n}\n\ntype wwoConfig struct {\n\tapiKey   string\n\tlanguage string\n\tdebug    bool\n}\n\nconst (\n\twwoSuri = \"https://api.worldweatheronline.com/free/v2/search.ashx?\"\n\twwoWuri = \"https://api.worldweatheronline.com/free/v2/weather.ashx?\"\n)\n\nfunc wwoParseCond(cond wwoCond, date time.Time) (ret iface.Cond) {\n\tret.ChanceOfRainPercent = cond.TmpCor\n\n\tcodemap := map[int]iface.WeatherCode{\n\t\t113: iface.CodeSunny,\n\t\t116: iface.CodePartlyCloudy,\n\t\t119: iface.CodeCloudy,\n\t\t122: iface.CodeVeryCloudy,\n\t\t143: iface.CodeFog,\n\t\t176: iface.CodeLightShowers,\n\t\t179: iface.CodeLightSleetShowers,\n\t\t182: iface.CodeLightSleet,\n\t\t185: iface.CodeLightSleet,\n\t\t200: iface.CodeThunderyShowers,\n\t\t227: iface.CodeLightSnow,\n\t\t230: iface.CodeHeavySnow,\n\t\t248: iface.CodeFog,\n\t\t260: iface.CodeFog,\n\t\t263: iface.CodeLightShowers,\n\t\t266: iface.CodeLightRain,\n\t\t281: iface.CodeLightSleet,\n\t\t284: iface.CodeLightSleet,\n\t\t293: iface.CodeLightRain,\n\t\t296: iface.CodeLightRain,\n\t\t299: iface.CodeHeavyShowers,\n\t\t302: iface.CodeHeavyRain,\n\t\t305: iface.CodeHeavyShowers,\n\t\t308: iface.CodeHeavyRain,\n\t\t311: iface.CodeLightSleet,\n\t\t314: iface.CodeLightSleet,\n\t\t317: iface.CodeLightSleet,\n\t\t320: iface.CodeLightSnow,\n\t\t323: iface.CodeLightSnowShowers,\n\t\t326: iface.CodeLightSnowShowers,\n\t\t329: iface.CodeHeavySnow,\n\t\t332: iface.CodeHeavySnow,\n\t\t335: iface.CodeHeavySnowShowers,\n\t\t338: iface.CodeHeavySnow,\n\t\t350: iface.CodeLightSleet,\n\t\t353: iface.CodeLightShowers,\n\t\t356: iface.CodeHeavyShowers,\n\t\t359: iface.CodeHeavyRain,\n\t\t362: iface.CodeLightSleetShowers,\n\t\t365: iface.CodeLightSleetShowers,\n\t\t368: iface.CodeLightSnowShowers,\n\t\t371: iface.CodeHeavySnowShowers,\n\t\t374: iface.CodeLightSleetShowers,\n\t\t377: iface.CodeLightSleet,\n\t\t386: iface.CodeThunderyShowers,\n\t\t389: iface.CodeThunderyHeavyRain,\n\t\t392: iface.CodeThunderySnowShowers,\n\t\t395: iface.CodeHeavySnowShowers,\n\t}\n\tret.Code = iface.CodeUnknown\n\tif val, ok := codemap[cond.TmpCode]; ok {\n\t\tret.Code = val\n\t}\n\n\tif cond.TmpDesc != nil && len(cond.TmpDesc) > 0 {\n\t\tret.Desc = cond.TmpDesc[0].Value\n\t}\n\n\tret.TempC = cond.TmpTempC2\n\tif cond.TmpTempC != nil {\n\t\tret.TempC = cond.TmpTempC\n\t}\n\tret.FeelsLikeC = cond.FeelsLikeC\n\n\tif cond.PrecipMM != nil {\n\t\tp := *cond.PrecipMM / 1000\n\t\tret.PrecipM = &p\n\t}\n\n\tret.Time = date\n\tif cond.TmpTime != nil {\n\t\tyear, month, day := date.Date()\n\t\thour, min := *cond.TmpTime/100, *cond.TmpTime%100\n\t\tret.Time = time.Date(year, month, day, hour, min, 0, 0, time.UTC)\n\t}\n\n\tif cond.VisibleDistKM != nil {\n\t\tp := *cond.VisibleDistKM * 1000\n\t\tret.VisibleDistM = &p\n\t}\n\n\tif cond.WinddirDegree != nil && *cond.WinddirDegree >= 0 {\n\t\tp := *cond.WinddirDegree % 360\n\t\tret.WinddirDegree = &p\n\t}\n\n\tret.WindspeedKmph = cond.WindspeedKmph\n\tret.WindGustKmph = cond.WindGustKmph\n\n\treturn\n}\n\nfunc wwoParseDay(day wwoDay, index int) (ret iface.Day) {\n\t//TODO: Astronomy\n\n\tret.Date = time.Now().Add(time.Hour * 24 * time.Duration(index))\n\tdate, err := time.Parse(\"2006-01-02\", day.Date)\n\tif err == nil {\n\t\tret.Date = date\n\t}\n\n\tif day.Hourly != nil && len(day.Hourly) > 0 {\n\t\tfor _, slot := range day.Hourly {\n\t\t\tret.Slots = append(ret.Slots, wwoParseCond(slot, date))\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc wwoUnmarshalLang(body []byte, r *wwoResponse, lang string) error {\n\tvar rv map[string]interface{}\n\tif err := json.Unmarshal(body, &rv); err != nil {\n\t\treturn err\n\t}\n\tif data, ok := rv[\"data\"].(map[string]interface{}); ok {\n\t\tif ccs, ok := data[\"current_condition\"].([]interface{}); ok {\n\t\t\tfor _, cci := range ccs {\n\t\t\t\tcc, ok := cci.(map[string]interface{})\n\t\t\t\tif !ok {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tlangs, ok := cc[\"lang_\"+lang].([]interface{})\n\t\t\t\tif !ok || len(langs) == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tweatherDesc, ok := cc[\"weatherDesc\"].([]interface{})\n\t\t\t\tif !ok || len(weatherDesc) == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tweatherDesc[0] = langs[0]\n\t\t\t}\n\t\t}\n\t\tif ws, ok := data[\"weather\"].([]interface{}); ok {\n\t\t\tfor _, wi := range ws {\n\t\t\t\tw, ok := wi.(map[string]interface{})\n\t\t\t\tif !ok {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif hs, ok := w[\"hourly\"].([]interface{}); ok {\n\t\t\t\t\tfor _, hi := range hs {\n\t\t\t\t\t\th, ok := hi.(map[string]interface{})\n\t\t\t\t\t\tif !ok {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlangs, ok := h[\"lang_\"+lang].([]interface{})\n\t\t\t\t\t\tif !ok || len(langs) == 0 {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tweatherDesc, ok := h[\"weatherDesc\"].([]interface{})\n\t\t\t\t\t\tif !ok || len(weatherDesc) == 0 {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tweatherDesc[0] = langs[0]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tvar buf bytes.Buffer\n\tif err := json.NewEncoder(&buf).Encode(rv); err != nil {\n\t\treturn err\n\t}\n\treturn json.NewDecoder(&buf).Decode(r)\n}\n\nfunc (c *wwoConfig) Setup() {\n\tflag.StringVar(&c.apiKey, \"wwo-api-key\", \"\", \"worldweatheronline backend: the api `KEY` to use\")\n\tflag.StringVar(&c.language, \"wwo-lang\", \"en\", \"worldweatheronline backend: the `LANGUAGE` to request from worldweatheronline\")\n\tflag.BoolVar(&c.debug, \"wwo-debug\", false, \"worldweatheronline backend: print raw requests and responses\")\n}\n\nfunc (c *wwoConfig) getCoordinatesFromAPI(queryParams []string, res chan *iface.LatLon) {\n\tvar coordResp wwoCoordinateResp\n\trequri := wwoSuri + strings.Join(queryParams, \"&\")\n\thres, err := http.Get(requri)\n\tif err != nil {\n\t\tlog.Println(\"Unable to fetch geo location:\", err)\n\t\tres <- nil\n\t\treturn\n\t} else if hres.StatusCode != 200 {\n\t\tlog.Println(\"Unable to fetch geo location: http status\", hres.StatusCode)\n\t\tres <- nil\n\t\treturn\n\t}\n\tdefer hres.Body.Close()\n\n\tbody, err := io.ReadAll(hres.Body)\n\tif err != nil {\n\t\tlog.Println(\"Unable to read geo location data:\", err)\n\t\tres <- nil\n\t\treturn\n\t}\n\n\tif c.debug {\n\t\tlog.Println(\"Geo location request:\", requri)\n\t\tlog.Println(\"Geo location response:\", string(body))\n\t}\n\n\tif err = json.Unmarshal(body, &coordResp); err != nil {\n\t\tlog.Println(\"Unable to unmarshal geo location data:\", err)\n\t\tres <- nil\n\t\treturn\n\t}\n\n\tr := coordResp.Search.Result\n\tif len(r) < 1 || r[0].Latitude == nil || r[0].Longitude == nil {\n\t\tlog.Println(\"Malformed geo location response\")\n\t\tres <- nil\n\t\treturn\n\t}\n\n\tres <- &iface.LatLon{Latitude: *r[0].Latitude, Longitude: *r[0].Longitude}\n}\n\nfunc (c *wwoConfig) Fetch(loc string, numdays int) iface.Data {\n\tvar params []string\n\tvar resp wwoResponse\n\tvar ret iface.Data\n\tcoordChan := make(chan *iface.LatLon)\n\n\tif len(c.apiKey) == 0 {\n\t\tlog.Fatal(\"No API key specified. Setup instructions are in the README.\")\n\t}\n\tparams = append(params, \"key=\"+c.apiKey)\n\n\tif len(loc) > 0 {\n\t\tparams = append(params, \"q=\"+url.QueryEscape(loc))\n\t}\n\tparams = append(params, \"format=json\")\n\tparams = append(params, \"num_of_days=\"+strconv.Itoa(numdays))\n\tparams = append(params, \"tp=3\")\n\n\tgo c.getCoordinatesFromAPI(params, coordChan)\n\n\tif c.language != \"\" {\n\t\tparams = append(params, \"lang=\"+c.language)\n\t}\n\trequri := wwoWuri + strings.Join(params, \"&\")\n\n\tres, err := http.Get(requri)\n\tif err != nil {\n\t\tlog.Fatal(\"Unable to get weather data: \", err)\n\t} else if res.StatusCode != 200 {\n\t\tlog.Fatal(\"Unable to get weather data: http status \", res.StatusCode)\n\t}\n\tdefer res.Body.Close()\n\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tif c.debug {\n\t\tlog.Println(\"Weather request:\", requri)\n\t\tlog.Println(\"Weather response:\", string(body))\n\t}\n\n\tif c.language == \"\" {\n\t\tif err = json.Unmarshal(body, &resp); err != nil {\n\t\t\tlog.Println(err)\n\t\t}\n\t} else {\n\t\tif err = wwoUnmarshalLang(body, &resp, c.language); err != nil {\n\t\t\tlog.Println(err)\n\t\t}\n\t}\n\n\tif resp.Data.Req == nil || len(resp.Data.Req) < 1 {\n\t\tif resp.Data.Err != nil && len(resp.Data.Err) >= 1 {\n\t\t\tlog.Fatal(resp.Data.Err[0].Msg)\n\t\t}\n\t\tlog.Fatal(\"Malformed response.\")\n\t}\n\n\tret.Location = resp.Data.Req[0].Type + \": \" + resp.Data.Req[0].Query\n\tret.GeoLoc = <-coordChan\n\n\tif resp.Data.CurCond != nil && len(resp.Data.CurCond) > 0 {\n\t\tret.Current = wwoParseCond(resp.Data.CurCond[0], time.Now())\n\t}\n\n\tif resp.Data.Days != nil && numdays > 0 {\n\t\tfor i, day := range resp.Data.Days {\n\t\t\tret.Forecast = append(ret.Forecast, wwoParseDay(day, i))\n\t\t}\n\t}\n\n\treturn ret\n}\n\nfunc init() {\n\tiface.AllBackends[\"worldweatheronline\"] = &wwoConfig{}\n}\n"
  },
  {
    "path": "backends/wwoConditionCodes.txt",
    "content": "DayIcon\t\t\t\t\t\t\t\tNightIcon\t\t\t\t\t\t\t\t\tWeatherCode\tCondition\r\nwsymbol_0001_sunny\t\t\t\t\twsymbol_0008_clear_sky_night\t\t\t\t113\tClear/Sunny\r\nwsymbol_0002_sunny_intervals\t\twsymbol_0008_clear_sky_night\t\t\t\t116\tPartly Cloudy\r\nwsymbol_0003_white_cloud\t\t\twsymbol_0004_black_low_cloud\t\t\t\t119\tCloudy\r\nwsymbol_0004_black_low_cloud\t\twsymbol_0004_black_low_cloud\t\t\t\t122\tOvercast\r\nwsymbol_0006_mist\t\t\t\t\twsymbol_0006_mist\t\t\t\t\t\t\t143\tMist\r\nwsymbol_0007_fog\t\t\t\t\twsymbol_0007_fog\t\t\t\t\t\t\t248\tFog\r\nwsymbol_0007_fog\t\t\t\t\twsymbol_0007_fog\t\t\t\t\t\t\t260\tFreezing fog\r\nwsymbol_0009_light_rain_showers\t\twsymbol_0025_light_rain_showers_night\t\t176\tPatchy rain nearby\r\nwsymbol_0009_light_rain_showers\t\twsymbol_0025_light_rain_showers_night\t\t263\tPatchy light drizzle\r\nwsymbol_0009_light_rain_showers\t\twsymbol_0025_light_rain_showers_night\t\t353\tLight rain shower\r\nwsymbol_0010_heavy_rain_showers\t\twsymbol_0026_heavy_rain_showers_night\t\t299\tModerate rain at times\r\nwsymbol_0010_heavy_rain_showers\t\twsymbol_0026_heavy_rain_showers_night\t\t305\tHeavy rain at times\r\nwsymbol_0010_heavy_rain_showers\t\twsymbol_0026_heavy_rain_showers_night\t\t356\tModerate or heavy rain shower\r\nwsymbol_0011_light_snow_showers\t\twsymbol_0027_light_snow_showers_night\t\t323\tPatchy light snow\r\nwsymbol_0011_light_snow_showers\t\twsymbol_0027_light_snow_showers_night\t\t326\tLight snow\r\nwsymbol_0011_light_snow_showers\t\twsymbol_0027_light_snow_showers_night\t\t368\tLight snow showers\r\nwsymbol_0012_heavy_snow_showers\t\twsymbol_0028_heavy_snow_showers_night\t\t335\tPatchy heavy snow\r\nwsymbol_0012_heavy_snow_showers\t\twsymbol_0028_heavy_snow_showers_night\t\t371\tModerate or heavy snow showers\r\nwsymbol_0012_heavy_snow_showers\t\twsymbol_0028_heavy_snow_showers_night\t\t395\tModerate or heavy snow in area with thunder\r\nwsymbol_0013_sleet_showers\t\t\twsymbol_0029_sleet_showers_night\t\t\t179\tPatchy snow nearby\r\nwsymbol_0013_sleet_showers\t\t\twsymbol_0029_sleet_showers_night\t\t\t362\tLight sleet showers\r\nwsymbol_0013_sleet_showers\t\t\twsymbol_0029_sleet_showers_night\t\t\t365\tModerate or heavy sleet showers\r\nwsymbol_0013_sleet_showers\t\t\twsymbol_0029_sleet_showers_night\t\t\t374\tLight showers of ice pellets\r\nwsymbol_0016_thundery_showers\t\twsymbol_0032_thundery_showers_night\t\t\t200\tThundery outbreaks in nearby\r\nwsymbol_0016_thundery_showers\t\twsymbol_0032_thundery_showers_night\t\t\t386\tPatchy light rain in area with thunder\r\nwsymbol_0016_thundery_showers\t\twsymbol_0032_thundery_showers_night\t\t\t392\tPatchy light snow in area with thunder\r\nwsymbol_0017_cloudy_with_light_rain\twsymbol_0025_light_rain_showers_night\t\t296\tLight rain\r\nwsymbol_0017_cloudy_with_light_rain\twsymbol_0033_cloudy_with_light_rain_night\t266\tLight drizzle\r\nwsymbol_0017_cloudy_with_light_rain\twsymbol_0033_cloudy_with_light_rain_night\t293\tPatchy light rain\r\nwsymbol_0018_cloudy_with_heavy_rain\twsymbol_0034_cloudy_with_heavy_rain_night\t302\tModerate rain\r\nwsymbol_0018_cloudy_with_heavy_rain\twsymbol_0034_cloudy_with_heavy_rain_night\t308\tHeavy rain\r\nwsymbol_0018_cloudy_with_heavy_rain\twsymbol_0034_cloudy_with_heavy_rain_night\t359\tTorrential rain shower\r\nwsymbol_0019_cloudy_with_light_snow\twsymbol_0035_cloudy_with_light_snow_night\t227\tBlowing snow\r\nwsymbol_0019_cloudy_with_light_snow\twsymbol_0035_cloudy_with_light_snow_night\t320\tModerate or heavy sleet\r\nwsymbol_0020_cloudy_with_heavy_snow\twsymbol_0036_cloudy_with_heavy_snow_night\t230\tBlizzard\r\nwsymbol_0020_cloudy_with_heavy_snow\twsymbol_0036_cloudy_with_heavy_snow_night\t329\tPatchy moderate snow\r\nwsymbol_0020_cloudy_with_heavy_snow\twsymbol_0036_cloudy_with_heavy_snow_night\t332\tModerate snow\r\nwsymbol_0020_cloudy_with_heavy_snow\twsymbol_0036_cloudy_with_heavy_snow_night\t338\tHeavy snow\r\nwsymbol_0021_cloudy_with_sleet\t\twsymbol_0037_cloudy_with_sleet_night\t\t182\tPatchy sleet nearby\r\nwsymbol_0021_cloudy_with_sleet\t\twsymbol_0037_cloudy_with_sleet_night\t\t185\tPatchy freezing drizzle nearby\r\nwsymbol_0021_cloudy_with_sleet\t\twsymbol_0037_cloudy_with_sleet_night\t\t281\tFreezing drizzle\r\nwsymbol_0021_cloudy_with_sleet\t\twsymbol_0037_cloudy_with_sleet_night\t\t284\tHeavy freezing drizzle\r\nwsymbol_0021_cloudy_with_sleet\t\twsymbol_0037_cloudy_with_sleet_night\t\t311\tLight freezing rain\r\nwsymbol_0021_cloudy_with_sleet\t\twsymbol_0037_cloudy_with_sleet_night\t\t314\tModerate or Heavy freezing rain\r\nwsymbol_0021_cloudy_with_sleet\t\twsymbol_0037_cloudy_with_sleet_night\t\t317\tLight sleet\r\nwsymbol_0021_cloudy_with_sleet\t\twsymbol_0037_cloudy_with_sleet_night\t\t350\tIce pellets\r\nwsymbol_0021_cloudy_with_sleet\t\twsymbol_0037_cloudy_with_sleet_night\t\t377\tModerate or heavy showers of ice pellets\r\nwsymbol_0024_thunderstorms\t\t\twsymbol_0040_thunderstorms_night\t\t\t389\tModerate or heavy rain in area with thunder\r\n"
  },
  {
    "path": "frontends/ascii-art-table.go",
    "content": "package frontends\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"math\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/mattn/go-colorable\"\n\t\"github.com/mattn/go-runewidth\"\n\t\"github.com/schachmat/wego/iface\"\n)\n\ntype aatConfig struct {\n\tcoords     bool\n\tmonochrome bool\n\tcompact    bool\n\n\tunit iface.UnitSystem\n}\n\n// TODO: replace s parameter with printf interface?\nfunc aatPad(s string, mustLen int) (ret string) {\n\tansiEsc := regexp.MustCompile(\"\\033.*?m\")\n\tret = s\n\trealLen := runewidth.StringWidth(ansiEsc.ReplaceAllLiteralString(s, \"\"))\n\tdelta := mustLen - realLen\n\tif delta > 0 {\n\t\tret += \"\\033[0m\" + strings.Repeat(\" \", delta)\n\t} else if delta < 0 {\n\t\ttoks := ansiEsc.Split(s, 2)\n\t\ttokLen := runewidth.StringWidth(toks[0])\n\t\tif tokLen > mustLen {\n\t\t\tret = fmt.Sprintf(\"%.*s\\033[0m\", mustLen, toks[0])\n\t\t} else {\n\t\t\tesc := ansiEsc.FindString(s)\n\t\t\tret = fmt.Sprintf(\"%s%s%s\", toks[0], esc, aatPad(toks[1], mustLen-tokLen))\n\t\t}\n\t}\n\treturn\n}\n\nfunc (c *aatConfig) formatTemp(cond iface.Cond) string {\n\tcolor := func(temp float32) string {\n\t\tcolmap := []struct {\n\t\t\tmaxtemp float32\n\t\t\tcolor   int\n\t\t}{\n\t\t\t{-15, 21}, {-12, 27}, {-9, 33}, {-6, 39}, {-3, 45},\n\t\t\t{0, 51}, {2, 50}, {4, 49}, {6, 48}, {8, 47},\n\t\t\t{10, 46}, {13, 82}, {16, 118}, {19, 154}, {22, 190},\n\t\t\t{25, 226}, {28, 220}, {31, 214}, {34, 208}, {37, 202},\n\t\t}\n\n\t\tcol := 196\n\t\tfor _, candidate := range colmap {\n\t\t\tif temp < candidate.maxtemp {\n\t\t\t\tcol = candidate.color\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tt, _ := c.unit.Temp(temp)\n\t\treturn fmt.Sprintf(\"\\033[38;5;%03dm%d\\033[0m\", col, int(t))\n\t}\n\n\t_, u := c.unit.Temp(0.0)\n\n\tif cond.TempC == nil {\n\t\treturn aatPad(fmt.Sprintf(\"? %s\", u), 15)\n\t}\n\n\tt := *cond.TempC\n\tif cond.FeelsLikeC != nil {\n\t\tfl := *cond.FeelsLikeC\n\t\treturn aatPad(fmt.Sprintf(\"%s (%s) %s\", color(t), color(fl), u), 15)\n\t}\n\treturn aatPad(fmt.Sprintf(\"%s %s\", color(t), u), 15)\n}\n\nfunc (c *aatConfig) formatWind(cond iface.Cond) string {\n\twindDir := func(deg *int) string {\n\t\tif deg == nil {\n\t\t\treturn \"?\"\n\t\t}\n\t\tarrows := []string{\"↓\", \"↙\", \"←\", \"↖\", \"↑\", \"↗\", \"→\", \"↘\"}\n\t\treturn \"\\033[1m\" + arrows[((*deg+22)%360)/45] + \"\\033[0m\"\n\t}\n\tcolor := func(spdKmph float32) string {\n\t\tcolmap := []struct {\n\t\t\tmaxtemp float32\n\t\t\tcolor   int\n\t\t}{\n\t\t\t{0, 46}, {4, 82}, {7, 118}, {10, 154}, {13, 190},\n\t\t\t{16, 226}, {20, 220}, {24, 214}, {28, 208}, {32, 202},\n\t\t}\n\n\t\tcol := 196\n\t\tfor _, candidate := range colmap {\n\t\t\tif spdKmph < candidate.maxtemp {\n\t\t\t\tcol = candidate.color\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\ts, _ := c.unit.Speed(spdKmph)\n\t\treturn fmt.Sprintf(\"\\033[38;5;%03dm%d\\033[0m\", col, int(s))\n\t}\n\n\t_, u := c.unit.Speed(0.0)\n\n\tif cond.WindspeedKmph == nil {\n\t\treturn aatPad(windDir(cond.WinddirDegree), 15)\n\t}\n\ts := *cond.WindspeedKmph\n\n\tif cond.WindGustKmph != nil {\n\t\tif g := *cond.WindGustKmph; g > s {\n\t\t\treturn aatPad(fmt.Sprintf(\"%s %s – %s %s\", windDir(cond.WinddirDegree), color(s), color(g), u), 15)\n\t\t}\n\t}\n\n\treturn aatPad(fmt.Sprintf(\"%s %s %s\", windDir(cond.WinddirDegree), color(s), u), 15)\n}\n\nfunc (c *aatConfig) formatVisibility(cond iface.Cond) string {\n\tif cond.VisibleDistM == nil {\n\t\treturn aatPad(\"\", 15)\n\t}\n\tv, u := c.unit.Distance(*cond.VisibleDistM)\n\treturn aatPad(fmt.Sprintf(\"%d %s\", int(v), u), 15)\n}\n\nfunc (c *aatConfig) formatRain(cond iface.Cond) string {\n\tif cond.PrecipM != nil {\n\t\tv, u := c.unit.Distance(*cond.PrecipM)\n\t\tu += \"/h\" // it's the same in all unit systems\n\t\tif cond.ChanceOfRainPercent != nil {\n\t\t\treturn aatPad(fmt.Sprintf(\"%.1f %s | %d%%\", v, u, *cond.ChanceOfRainPercent), 15)\n\t\t}\n\t\treturn aatPad(fmt.Sprintf(\"%.1f %s\", v, u), 15)\n\t} else if cond.ChanceOfRainPercent != nil {\n\t\treturn aatPad(fmt.Sprintf(\"%d%%\", *cond.ChanceOfRainPercent), 15)\n\t}\n\treturn aatPad(\"\", 15)\n}\n\nfunc (c *aatConfig) formatCond(cur []string, cond iface.Cond, current bool) (ret []string) {\n\tcodes := map[iface.WeatherCode][]string{\n\t\tiface.CodeUnknown: {\n\t\t\t\"    .-.      \",\n\t\t\t\"     __)     \",\n\t\t\t\"    (        \",\n\t\t\t\"     `-᾿     \",\n\t\t\t\"      •      \",\n\t\t},\n\t\tiface.CodeCloudy: {\n\t\t\t\"             \",\n\t\t\t\"\\033[38;5;250m     .--.    \\033[0m\",\n\t\t\t\"\\033[38;5;250m  .-(    ).  \\033[0m\",\n\t\t\t\"\\033[38;5;250m (___.__)__) \\033[0m\",\n\t\t\t\"             \",\n\t\t},\n\t\tiface.CodeFog: {\n\t\t\t\"             \",\n\t\t\t\"\\033[38;5;251m _ - _ - _ - \\033[0m\",\n\t\t\t\"\\033[38;5;251m  _ - _ - _  \\033[0m\",\n\t\t\t\"\\033[38;5;251m _ - _ - _ - \\033[0m\",\n\t\t\t\"             \",\n\t\t},\n\t\tiface.CodeHeavyRain: {\n\t\t\t\"\\033[38;5;244;1m     .-.     \\033[0m\",\n\t\t\t\"\\033[38;5;244;1m    (   ).   \\033[0m\",\n\t\t\t\"\\033[38;5;244;1m   (___(__)  \\033[0m\",\n\t\t\t\"\\033[38;5;33;1m  ‚ʻ‚ʻ‚ʻ‚ʻ   \\033[0m\",\n\t\t\t\"\\033[38;5;33;1m  ‚ʻ‚ʻ‚ʻ‚ʻ   \\033[0m\",\n\t\t},\n\t\tiface.CodeHeavyShowers: {\n\t\t\t\"\\033[38;5;226m _`/\\\"\\\"\\033[38;5;244;1m.-.    \\033[0m\",\n\t\t\t\"\\033[38;5;226m  ,\\\\_\\033[38;5;244;1m(   ).  \\033[0m\",\n\t\t\t\"\\033[38;5;226m   /\\033[38;5;244;1m(___(__) \\033[0m\",\n\t\t\t\"\\033[38;5;33;1m   ‚ʻ‚ʻ‚ʻ‚ʻ  \\033[0m\",\n\t\t\t\"\\033[38;5;33;1m   ‚ʻ‚ʻ‚ʻ‚ʻ  \\033[0m\",\n\t\t},\n\t\tiface.CodeHeavySnow: {\n\t\t\t\"\\033[38;5;244;1m     .-.     \\033[0m\",\n\t\t\t\"\\033[38;5;244;1m    (   ).   \\033[0m\",\n\t\t\t\"\\033[38;5;244;1m   (___(__)  \\033[0m\",\n\t\t\t\"\\033[38;5;255;1m   * * * *   \\033[0m\",\n\t\t\t\"\\033[38;5;255;1m  * * * *    \\033[0m\",\n\t\t},\n\t\tiface.CodeHeavySnowShowers: {\n\t\t\t\"\\033[38;5;226m _`/\\\"\\\"\\033[38;5;244;1m.-.    \\033[0m\",\n\t\t\t\"\\033[38;5;226m  ,\\\\_\\033[38;5;244;1m(   ).  \\033[0m\",\n\t\t\t\"\\033[38;5;226m   /\\033[38;5;244;1m(___(__) \\033[0m\",\n\t\t\t\"\\033[38;5;255;1m    * * * *  \\033[0m\",\n\t\t\t\"\\033[38;5;255;1m   * * * *   \\033[0m\",\n\t\t},\n\t\tiface.CodeLightRain: {\n\t\t\t\"\\033[38;5;250m     .-.     \\033[0m\",\n\t\t\t\"\\033[38;5;250m    (   ).   \\033[0m\",\n\t\t\t\"\\033[38;5;250m   (___(__)  \\033[0m\",\n\t\t\t\"\\033[38;5;111m    ʻ ʻ ʻ ʻ  \\033[0m\",\n\t\t\t\"\\033[38;5;111m   ʻ ʻ ʻ ʻ   \\033[0m\",\n\t\t},\n\t\tiface.CodeLightShowers: {\n\t\t\t\"\\033[38;5;226m _`/\\\"\\\"\\033[38;5;250m.-.    \\033[0m\",\n\t\t\t\"\\033[38;5;226m  ,\\\\_\\033[38;5;250m(   ).  \\033[0m\",\n\t\t\t\"\\033[38;5;226m   /\\033[38;5;250m(___(__) \\033[0m\",\n\t\t\t\"\\033[38;5;111m     ʻ ʻ ʻ ʻ \\033[0m\",\n\t\t\t\"\\033[38;5;111m    ʻ ʻ ʻ ʻ  \\033[0m\",\n\t\t},\n\t\tiface.CodeLightSleet: {\n\t\t\t\"\\033[38;5;250m     .-.     \\033[0m\",\n\t\t\t\"\\033[38;5;250m    (   ).   \\033[0m\",\n\t\t\t\"\\033[38;5;250m   (___(__)  \\033[0m\",\n\t\t\t\"\\033[38;5;111m    ʻ \\033[38;5;255m*\\033[38;5;111m ʻ \\033[38;5;255m*  \\033[0m\",\n\t\t\t\"\\033[38;5;255m   *\\033[38;5;111m ʻ \\033[38;5;255m*\\033[38;5;111m ʻ   \\033[0m\",\n\t\t},\n\t\tiface.CodeLightSleetShowers: {\n\t\t\t\"\\033[38;5;226m _`/\\\"\\\"\\033[38;5;250m.-.    \\033[0m\",\n\t\t\t\"\\033[38;5;226m  ,\\\\_\\033[38;5;250m(   ).  \\033[0m\",\n\t\t\t\"\\033[38;5;226m   /\\033[38;5;250m(___(__) \\033[0m\",\n\t\t\t\"\\033[38;5;111m     ʻ \\033[38;5;255m*\\033[38;5;111m ʻ \\033[38;5;255m* \\033[0m\",\n\t\t\t\"\\033[38;5;255m    *\\033[38;5;111m ʻ \\033[38;5;255m*\\033[38;5;111m ʻ  \\033[0m\",\n\t\t},\n\t\tiface.CodeLightSnow: {\n\t\t\t\"\\033[38;5;250m     .-.     \\033[0m\",\n\t\t\t\"\\033[38;5;250m    (   ).   \\033[0m\",\n\t\t\t\"\\033[38;5;250m   (___(__)  \\033[0m\",\n\t\t\t\"\\033[38;5;255m    *  *  *  \\033[0m\",\n\t\t\t\"\\033[38;5;255m   *  *  *   \\033[0m\",\n\t\t},\n\t\tiface.CodeLightSnowShowers: {\n\t\t\t\"\\033[38;5;226m _`/\\\"\\\"\\033[38;5;250m.-.    \\033[0m\",\n\t\t\t\"\\033[38;5;226m  ,\\\\_\\033[38;5;250m(   ).  \\033[0m\",\n\t\t\t\"\\033[38;5;226m   /\\033[38;5;250m(___(__) \\033[0m\",\n\t\t\t\"\\033[38;5;255m     *  *  * \\033[0m\",\n\t\t\t\"\\033[38;5;255m    *  *  *  \\033[0m\",\n\t\t},\n\t\tiface.CodePartlyCloudy: {\n\t\t\t\"\\033[38;5;226m   \\\\__/\\033[0m      \",\n\t\t\t\"\\033[38;5;226m __/  \\033[38;5;250m.-.    \\033[0m\",\n\t\t\t\"\\033[38;5;226m   \\\\_\\033[38;5;250m(   ).  \\033[0m\",\n\t\t\t\"\\033[38;5;226m   /\\033[38;5;250m(___(__) \\033[0m\",\n\t\t\t\"             \",\n\t\t},\n\t\tiface.CodeSunny: {\n\t\t\t\"\\033[38;5;226m    \\\\ . /    \\033[0m\",\n\t\t\t\"\\033[38;5;226m   - .-. -   \\033[0m\",\n\t\t\t\"\\033[38;5;226m  ‒ (   ) ‒  \\033[0m\",\n\t\t\t\"\\033[38;5;226m   . `-᾿ .   \\033[0m\",\n\t\t\t\"\\033[38;5;226m    / ' \\\\    \\033[0m\",\n\t\t},\n\t\tiface.CodeThunderyHeavyRain: {\n\t\t\t\"\\033[38;5;244;1m     .-.     \\033[0m\",\n\t\t\t\"\\033[38;5;244;1m    (   ).   \\033[0m\",\n\t\t\t\"\\033[38;5;244;1m   (___(__)  \\033[0m\",\n\t\t\t\"\\033[38;5;33;1m  ‚ʻ\\033[38;5;228;5m⚡\\033[38;5;33;25mʻ‚\\033[38;5;228;5m⚡\\033[38;5;33;25m‚ʻ   \\033[0m\",\n\t\t\t\"\\033[38;5;33;1m  ‚ʻ‚ʻ\\033[38;5;228;5m⚡\\033[38;5;33;25mʻ‚ʻ   \\033[0m\",\n\t\t},\n\t\tiface.CodeThunderyShowers: {\n\t\t\t\"\\033[38;5;226m _`/\\\"\\\"\\033[38;5;250m.-.    \\033[0m\",\n\t\t\t\"\\033[38;5;226m  ,\\\\_\\033[38;5;250m(   ).  \\033[0m\",\n\t\t\t\"\\033[38;5;226m   /\\033[38;5;250m(___(__) \\033[0m\",\n\t\t\t\"\\033[38;5;228;5m    ⚡\\033[38;5;111;25mʻ ʻ\\033[38;5;228;5m⚡\\033[38;5;111;25mʻ ʻ \\033[0m\",\n\t\t\t\"\\033[38;5;111m    ʻ ʻ ʻ ʻ  \\033[0m\",\n\t\t},\n\t\tiface.CodeThunderySnowShowers: {\n\t\t\t\"\\033[38;5;226m _`/\\\"\\\"\\033[38;5;250m.-.    \\033[0m\",\n\t\t\t\"\\033[38;5;226m  ,\\\\_\\033[38;5;250m(   ).  \\033[0m\",\n\t\t\t\"\\033[38;5;226m   /\\033[38;5;250m(___(__) \\033[0m\",\n\t\t\t\"\\033[38;5;255m     *\\033[38;5;228;5m⚡\\033[38;5;255;25m *\\033[38;5;228;5m⚡\\033[38;5;255;25m * \\033[0m\",\n\t\t\t\"\\033[38;5;255m    *  *  *  \\033[0m\",\n\t\t},\n\t\tiface.CodeVeryCloudy: {\n\t\t\t\"             \",\n\t\t\t\"\\033[38;5;244;1m     .--.    \\033[0m\",\n\t\t\t\"\\033[38;5;244;1m  .-(    ).  \\033[0m\",\n\t\t\t\"\\033[38;5;244;1m (___.__)__) \\033[0m\",\n\t\t\t\"             \",\n\t\t},\n\t}\n\n\ticon := make([]string, 5)\n\tif !c.compact {\n\t\tvar ok bool\n\t\ticon, ok = codes[cond.Code]\n\t\tif !ok {\n\t\t\tlog.Fatalln(\"aat-frontend: The following weather code has no icon:\", cond.Code)\n\t\t}\n\t}\n\n\tdesc := cond.Desc\n\tif !current {\n\t\tdesc = runewidth.Truncate(runewidth.FillRight(desc, 15), 15, \"…\")\n\t}\n\n\tret = append(ret, fmt.Sprintf(\"%v %v %v\", cur[0], icon[0], desc))\n\tret = append(ret, fmt.Sprintf(\"%v %v %v\", cur[1], icon[1], c.formatTemp(cond)))\n\tret = append(ret, fmt.Sprintf(\"%v %v %v\", cur[2], icon[2], c.formatWind(cond)))\n\tret = append(ret, fmt.Sprintf(\"%v %v %v\", cur[3], icon[3], c.formatVisibility(cond)))\n\tret = append(ret, fmt.Sprintf(\"%v %v %v\", cur[4], icon[4], c.formatRain(cond)))\n\treturn\n}\n\nfunc (c *aatConfig) formatGeo(coords *iface.LatLon) (ret string) {\n\tif !c.coords || coords == nil {\n\t\treturn \"\"\n\t}\n\n\tlat, lon := \"N\", \"E\"\n\tif coords.Latitude < 0 {\n\t\tlat = \"S\"\n\t}\n\tif coords.Longitude < 0 {\n\t\tlon = \"W\"\n\t}\n\tret = \" \"\n\tret += fmt.Sprintf(\"(%.1f°%s\", math.Abs(float64(coords.Latitude)), lat)\n\tret += fmt.Sprintf(\" %.1f°%s)\", math.Abs(float64(coords.Longitude)), lon)\n\treturn\n}\n\nfunc (c *aatConfig) printDay(day iface.Day) (ret []string) {\n\tdesiredTimesOfDay := []time.Duration{\n\t\t8 * time.Hour,\n\t\t12 * time.Hour,\n\t\t19 * time.Hour,\n\t\t23 * time.Hour,\n\t}\n\tret = make([]string, 5)\n\tfor i := range ret {\n\t\tret[i] = \"│\"\n\t}\n\n\t// save our selected elements from day.Slots in this array\n\tcols := make([]iface.Cond, len(desiredTimesOfDay))\n\t// find hourly data which fits the desired times of day best\n\tfor _, candidate := range day.Slots {\n\t\tcand := candidate.Time.UTC().Sub(candidate.Time.Truncate(24 * time.Hour))\n\t\tfor i, col := range cols {\n\t\t\tcur := col.Time.Sub(col.Time.Truncate(24 * time.Hour))\n\t\t\tif col.Time.IsZero() || math.Abs(float64(cand-desiredTimesOfDay[i])) < math.Abs(float64(cur-desiredTimesOfDay[i])) {\n\t\t\t\tcols[i] = candidate\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, s := range cols {\n\t\tret = c.formatCond(ret, s, false)\n\t\tfor i := range ret {\n\t\t\tret[i] = ret[i] + \"│\"\n\t\t}\n\t}\n\n\tdateFmt := \"┤ \" + day.Date.Format(\"Mon 02. Jan\") + \" ├\"\n\tif !c.compact {\n\t\tret = append([]string{\n\t\t\t\"                                                       ┌─────────────┐                                                       \",\n\t\t\t\"┌──────────────────────────────┬───────────────────────\" + dateFmt + \"───────────────────────┬──────────────────────────────┐\",\n\t\t\t\"│           Morning            │             Noon      └──────┬──────┘    Evening            │            Night             │\",\n\t\t\t\"├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤\"},\n\t\t\tret...)\n\t\tret = append(ret,\n\t\t\t\"└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘\")\n\t} else {\n\t\tmerge := func(src string, into string) string {\n\t\t\tret := []rune(into)\n\t\t\tfor k, v := range src {\n\t\t\t\tret[k] = v\n\t\t\t}\n\t\t\treturn string(ret)\n\t\t}\n\n\t\tspaces := (len(ret[0]) / 4) - 3\n\t\tbar := strings.Repeat(\"─\", spaces)\n\n\t\tret = append([]string{\n\t\t\tday.Date.Format(\"Mon 02. Jan\"),\n\t\t\t\"┌\" + merge(\"Morning\", bar) + \"┬\" + merge(\"Noon\", bar) + \"┬\" + merge(\"Evening\", bar) + \"┬\" + merge(\"Night\", bar) + \"┐\",\n\t\t}, ret...)\n\n\t\tret = append(ret,\n\t\t\t\"└\"+bar+\"┴\"+bar+\"┴\"+bar+\"┴\"+bar+\"┘\",\n\t\t)\n\t}\n\n\treturn ret\n}\n\nfunc (c *aatConfig) Setup() {\n\tflag.BoolVar(&c.coords, \"aat-coords\", false, \"aat-frontend: Show geo coordinates\")\n\tflag.BoolVar(&c.monochrome, \"aat-monochrome\", false, \"aat-frontend: Monochrome output\")\n\n\tflag.BoolVar(&c.compact, \"aat-compact\", false, \"aat-frontend: Compact output\")\n}\n\nfunc (c *aatConfig) Render(r iface.Data, unitSystem iface.UnitSystem) {\n\tc.unit = unitSystem\n\n\tfmt.Printf(\"Weather for %s%s\\n\\n\", r.Location, c.formatGeo(r.GeoLoc))\n\tstdout := colorable.NewColorableStdout()\n\tif c.monochrome {\n\t\tstdout = colorable.NewNonColorable(os.Stdout)\n\t}\n\n\tout := c.formatCond(make([]string, 5), r.Current, true)\n\tfor _, val := range out {\n\t\tfmt.Fprintln(stdout, val)\n\t}\n\n\tif len(r.Forecast) == 0 {\n\t\treturn\n\t}\n\tif r.Forecast == nil {\n\t\tlog.Fatal(\"No detailed weather forecast available.\")\n\t}\n\tfor _, d := range r.Forecast {\n\t\tfor _, val := range c.printDay(d) {\n\t\t\tfmt.Fprintln(stdout, val)\n\t\t}\n\t}\n}\n\nfunc init() {\n\tiface.AllFrontends[\"ascii-art-table\"] = &aatConfig{}\n}\n"
  },
  {
    "path": "frontends/emoji.go",
    "content": "package frontends\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"math\"\n\t\"time\"\n\n\tcolorable \"github.com/mattn/go-colorable\"\n\trunewidth \"github.com/mattn/go-runewidth\"\n\t\"github.com/schachmat/wego/iface\"\n)\n\ntype emojiConfig struct {\n\tunit iface.UnitSystem\n}\n\nfunc (c *emojiConfig) formatTemp(cond iface.Cond) string {\n\tcolor := func(temp float32) string {\n\t\tcolmap := []struct {\n\t\t\tmaxtemp float32\n\t\t\tcolor   int\n\t\t}{\n\t\t\t{-15, 21}, {-12, 27}, {-9, 33}, {-6, 39}, {-3, 45},\n\t\t\t{0, 51}, {2, 50}, {4, 49}, {6, 48}, {8, 47},\n\t\t\t{10, 46}, {13, 82}, {16, 118}, {19, 154}, {22, 190},\n\t\t\t{25, 226}, {28, 220}, {31, 214}, {34, 208}, {37, 202},\n\t\t}\n\n\t\tcol := 196\n\t\tfor _, candidate := range colmap {\n\t\t\tif temp < candidate.maxtemp {\n\t\t\t\tcol = candidate.color\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tt, _ := c.unit.Temp(temp)\n\t\treturn fmt.Sprintf(\"\\033[38;5;%03dm%d\\033[0m\", col, int(t))\n\t}\n\n\t_, u := c.unit.Temp(0.0)\n\n\tif cond.TempC == nil {\n\t\treturn aatPad(fmt.Sprintf(\"? %s\", u), 12)\n\t}\n\n\tt := *cond.TempC\n\tif cond.FeelsLikeC != nil {\n\t\tfl := *cond.FeelsLikeC\n\t\treturn aatPad(fmt.Sprintf(\"%s (%s) %s\", color(t), color(fl), u), 12)\n\t}\n\treturn aatPad(fmt.Sprintf(\"%s %s\", color(t), u), 12)\n}\n\nfunc (c *emojiConfig) formatCond(cur []string, cond iface.Cond, current bool) (ret []string) {\n\tcodes := map[iface.WeatherCode]string{\n\t\tiface.CodeUnknown:             \"✨\",\n\t\tiface.CodeCloudy:              \"☁️\",\n\t\tiface.CodeFog:                 \"🌫\",\n\t\tiface.CodeHeavyRain:           \"🌧\",\n\t\tiface.CodeHeavyShowers:        \"🌧\",\n\t\tiface.CodeHeavySnow:           \"❄️\",\n\t\tiface.CodeHeavySnowShowers:    \"❄️\",\n\t\tiface.CodeLightRain:           \"🌦\",\n\t\tiface.CodeLightShowers:        \"🌦\",\n\t\tiface.CodeLightSleet:          \"🌧\",\n\t\tiface.CodeLightSleetShowers:   \"🌧\",\n\t\tiface.CodeLightSnow:           \"🌨\",\n\t\tiface.CodeLightSnowShowers:    \"🌨\",\n\t\tiface.CodePartlyCloudy:        \"⛅️\",\n\t\tiface.CodeSunny:               \"☀️\",\n\t\tiface.CodeThunderyHeavyRain:   \"🌩\",\n\t\tiface.CodeThunderyShowers:     \"⛈\",\n\t\tiface.CodeThunderySnowShowers: \"⛈\",\n\t\tiface.CodeVeryCloudy:          \"☁️\",\n\t}\n\n\ticon, ok := codes[cond.Code]\n\tif !ok {\n\t\tlog.Fatalln(\"emoji-frontend: The following weather code has no icon:\", cond.Code)\n\t}\n\tif runewidth.StringWidth(icon) == 1 {\n\t\ticon += \" \"\n\t}\n\n\tdesc := cond.Desc\n\tif !current {\n\t\tdesc = runewidth.Truncate(runewidth.FillRight(desc, 13), 13, \"…\")\n\t}\n\n\tret = append(ret, fmt.Sprintf(\"%v %v %v\", cur[0], \"\", desc))\n\tret = append(ret, fmt.Sprintf(\"%v%v %v\", cur[1], icon, c.formatTemp(cond)))\n\treturn\n}\n\nfunc (c *emojiConfig) printAstro(astro iface.Astro) {\n        // print sun astronomy data if present\n\tif astro.Sunrise != astro.Sunset {\n\t    // half the distance between sunrise and sunset\n\t    noon_distance := time.Duration(int64(float32(astro.Sunset.UnixNano() - astro.Sunrise.UnixNano()) * 0.5))\n\t    // time for solar noon\n\t    noon := astro.Sunrise.Add(noon_distance)\n\n\t    // the actual print statement\n\t    fmt.Printf(\"🌞 rise↗ %s noon↑ %s set↘ %s\\n\", astro.Sunrise.Format(time.Kitchen), noon.Format(time.Kitchen), astro.Sunset.Format(time.Kitchen))\n\t}\n        // print moon astronomy data if present\n\tif astro.Moonrise != astro.Moonset {\n\t    fmt.Printf(\"🌚 rise↗ %s set↘ %s\\n\", astro.Moonrise.Format(time.Kitchen), astro.Moonset)\n\t}\n}\n\nfunc (c *emojiConfig) printDay(day iface.Day) (ret []string) {\n\tdesiredTimesOfDay := []time.Duration{\n\t\t8 * time.Hour,\n\t\t12 * time.Hour,\n\t\t19 * time.Hour,\n\t\t23 * time.Hour,\n\t}\n\tret = make([]string, 5)\n\tfor i := range ret {\n\t\tret[i] = \"│\"\n\t}\n\n\tc.printAstro(day.Astronomy)\n\n\t// save our selected elements from day.Slots in this array\n\tcols := make([]iface.Cond, len(desiredTimesOfDay))\n\t// find hourly data which fits the desired times of day best\n\tfor _, candidate := range day.Slots {\n\t\tcand := candidate.Time.UTC().Sub(candidate.Time.Truncate(24 * time.Hour))\n\t\tfor i, col := range cols {\n\t\t\tcur := col.Time.Sub(col.Time.Truncate(24 * time.Hour))\n\t\t\tif math.Abs(float64(cand-desiredTimesOfDay[i])) < math.Abs(float64(cur-desiredTimesOfDay[i])) {\n\t\t\t\tcols[i] = candidate\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, s := range cols {\n\t\tret = c.formatCond(ret, s, false)\n\t\tfor i := range ret {\n\t\t\tret[i] = ret[i] + \"│\"\n\t\t}\n\t}\n\n\tdateFmt := \"┤  \" + day.Date.Format(\"Mon\") + \"  ├\"\n\tret = append([]string{\n\t\t\"                            ┌───────┐ \",\n\t\t\"┌───────────────┬───────────\" + dateFmt + \"───────────┬───────────────┐\",\n\t\t\"│    Morning    │    Noon   └───┬───┘ Evening   │     Night     │\",\n\t\t\"├───────────────┼───────────────┼───────────────┼───────────────┤\"},\n\t\tret...)\n\treturn append(ret,\n\t\t\"└───────────────┴───────────────┴───────────────┴───────────────┘\",\n\t\t\" \")\n}\n\nfunc (c *emojiConfig) Setup() {\n}\n\nfunc (c *emojiConfig) Render(r iface.Data, unitSystem iface.UnitSystem) {\n\tc.unit = unitSystem\n\n\tfmt.Printf(\"Weather for %s\\n\\n\", r.Location)\n\tstdout := colorable.NewColorableStdout()\n\n\tout := c.formatCond(make([]string, 5), r.Current, true)\n\tfor _, val := range out {\n\t\tfmt.Fprintln(stdout, val)\n\t}\n\n\tif len(r.Forecast) == 0 {\n\t\treturn\n\t}\n\tif r.Forecast == nil {\n\t\tlog.Fatal(\"No detailed weather forecast available.\")\n\t}\n\tfmt.Printf(\"\\n\")\n\tfor _, d := range r.Forecast {\n\t\tfor _, val := range c.printDay(d) {\n\t\t\tfmt.Fprintln(stdout, val)\n\t\t}\n\t}\n}\n\nfunc init() {\n\tiface.AllFrontends[\"emoji\"] = &emojiConfig{}\n}\n"
  },
  {
    "path": "frontends/json.go",
    "content": "package frontends\n\nimport (\n\t\"encoding/json\"\n\t\"flag\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/schachmat/wego/iface\"\n)\n\ntype jsnConfig struct {\n\tnoIndent bool\n}\n\nfunc (c *jsnConfig) Setup() {\n\tflag.BoolVar(&c.noIndent, \"jsn-no-indent\", false, \"json frontend: do not indent the output\")\n}\n\nfunc (c *jsnConfig) Render(r iface.Data, unitSystem iface.UnitSystem) {\n\tvar b []byte\n\tvar err error\n\tif c.noIndent {\n\t\tb, err = json.Marshal(r)\n\t} else {\n\t\tb, err = json.MarshalIndent(r, \"\", \"\\t\")\n\t}\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tos.Stdout.Write(b)\n}\n\nfunc init() {\n\tiface.AllFrontends[\"json\"] = &jsnConfig{}\n}\n"
  },
  {
    "path": "frontends/markdown.go",
    "content": "package frontends\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"math\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/mattn/go-colorable\"\n\t\"github.com/mattn/go-runewidth\"\n\t\"github.com/schachmat/wego/iface\"\n)\n\ntype mdConfig struct {\n\tcoords     bool\n\tunit       iface.UnitSystem\n}\n\nfunc mdPad(s string, mustLen int) (ret string) {\n\tret = s\n\trealLen := runewidth.StringWidth(\"|\")\n\tdelta := mustLen - realLen\n\tif delta > 0 {\n\t\tret += strings.Repeat(\" \", delta)\n\t} else if delta < 0 {\n\t\ttoks := \"|\"\n\t\ttokLen := runewidth.StringWidth(toks)\n\t\tif tokLen > mustLen {\n\t\t\tret = fmt.Sprintf(\"%.*s\", mustLen, toks)\n\t\t} else {\n\t\t\tret = fmt.Sprintf(\"%s%s\", toks, mdPad(toks, mustLen-tokLen))\n\t\t}\n\t}\n\treturn\n}\n\nfunc (c *mdConfig) formatTemp(cond iface.Cond) string {\n\n\tcvtUnits := func (temp float32) string {\n\t\tt, _ := c.unit.Temp(temp)\n\t\treturn fmt.Sprintf(\"%d\", int(t))\n\t}\n\t_, u := c.unit.Temp(0.0)\n\n\tif cond.TempC == nil {\n\t\treturn mdPad(fmt.Sprintf(\"? %s\", u), 15)\n\t}\n\n\tt := *cond.TempC\n\tif cond.FeelsLikeC != nil {\n\t\tfl := *cond.FeelsLikeC\n\t\treturn mdPad(fmt.Sprintf(\"%s (%s) %s\", cvtUnits(t), cvtUnits(fl), u), 15)\n\t}\n\treturn mdPad(fmt.Sprintf(\"%s %s\", cvtUnits(t), u), 15)\n}\n\nfunc (c *mdConfig) formatWind(cond iface.Cond) string {\n\twindDir := func(deg *int) string {\n\t\tif deg == nil {\n\t\t\treturn \"?\"\n\t\t}\n\t\tarrows := []string{\"↓\", \"↙\", \"←\", \"↖\", \"↑\", \"↗\", \"→\", \"↘\"}\n\t\treturn arrows[((*deg+22)%360)/45]\n\t}\n\tcolor := func(spdKmph float32) string {\n\t\ts, _ := c.unit.Speed(spdKmph)\n\t\treturn fmt.Sprintf(\"| %d \", int(s))\n\t}\n\n\t_, u := c.unit.Speed(0.0)\n\n\tif cond.WindspeedKmph == nil {\n\t\treturn mdPad(windDir(cond.WinddirDegree), 15)\n\t}\n\ts := *cond.WindspeedKmph\n\n\tif cond.WindGustKmph != nil {\n\t\tif g := *cond.WindGustKmph; g > s {\n\t\t\treturn mdPad(fmt.Sprintf(\"%s %s – %s %s\", windDir(cond.WinddirDegree), color(s), color(g), u), 15)\n\t\t}\n\t}\n\n\treturn mdPad(fmt.Sprintf(\"%s %s %s\", windDir(cond.WinddirDegree), color(s), u), 15)\n}\n\nfunc (c *mdConfig) formatVisibility(cond iface.Cond) string {\n\tif cond.VisibleDistM == nil {\n\t\treturn mdPad(\"\", 15)\n\t}\n\tv, u := c.unit.Distance(*cond.VisibleDistM)\n\treturn mdPad(fmt.Sprintf(\"%d %s\", int(v), u), 15)\n}\n\nfunc (c *mdConfig) formatRain(cond iface.Cond) string {\n\tif cond.PrecipM != nil {\n\t\tv, u := c.unit.Distance(*cond.PrecipM)\n\t\tu += \"/h\" // it's the same in all unit systems\n\t\tif cond.ChanceOfRainPercent != nil {\n\t\t\treturn mdPad(fmt.Sprintf(\"%.1f %s | %d%%\", v, u, *cond.ChanceOfRainPercent), 15)\n\t\t}\n\t\treturn mdPad(fmt.Sprintf(\"%.1f %s\", v, u), 15)\n\t} else if cond.ChanceOfRainPercent != nil {\n\t\treturn mdPad(fmt.Sprintf(\"%d%%\", *cond.ChanceOfRainPercent), 15)\n\t}\n\treturn mdPad(\"\", 15)\n}\n\nfunc (c *mdConfig) formatCond(cur []string, cond iface.Cond, current bool) (ret []string) {\n\tcodes := map[iface.WeatherCode]string{\n\t\tiface.CodeUnknown:             \"✨\",\n\t\tiface.CodeCloudy:              \"☁️\",\n\t\tiface.CodeFog:                 \"🌫\",\n\t\tiface.CodeHeavyRain:           \"🌧\",\n\t\tiface.CodeHeavyShowers:        \"🌧\",\n\t\tiface.CodeHeavySnow:           \"❄️\",\n\t\tiface.CodeHeavySnowShowers:    \"❄️\",\n\t\tiface.CodeLightRain:           \"🌦\",\n\t\tiface.CodeLightShowers:        \"🌦\",\n\t\tiface.CodeLightSleet:          \"🌧\",\n\t\tiface.CodeLightSleetShowers:   \"🌧\",\n\t\tiface.CodeLightSnow:           \"🌨\",\n\t\tiface.CodeLightSnowShowers:    \"🌨\",\n\t\tiface.CodePartlyCloudy:        \"⛅️\",\n\t\tiface.CodeSunny:               \"☀️\",\n\t\tiface.CodeThunderyHeavyRain:   \"🌩\",\n\t\tiface.CodeThunderyShowers:     \"⛈\",\n\t\tiface.CodeThunderySnowShowers: \"⛈\",\n\t\tiface.CodeVeryCloudy:          \"☁️\",\n\t}\n\n\ticon, ok := codes[cond.Code]\n\tif !ok {\n\t\tlog.Fatalln(\"markdown-frontend: The following weather code has no icon:\", cond.Code)\n\t}\n\n\tdesc := cond.Desc\n\tif !current {\n\t\tdesc = runewidth.Truncate(runewidth.FillRight(desc, 25), 25, \"…\")\n\t}\n\n\tret = append(ret, fmt.Sprintf(\"%v %v %v\", cur[0], \"\", desc))\n\tret = append(ret, fmt.Sprintf(\"%v %v %v\", cur[1], icon, c.formatTemp(cond)))\n\treturn\n}\n\nfunc (c *mdConfig) formatGeo(coords *iface.LatLon) (ret string) {\n\tif !c.coords || coords == nil {\n\t\treturn \"\"\n\t}\n\n\tlat, lon := \"N\", \"E\"\n\tif coords.Latitude < 0 {\n\t\tlat = \"S\"\n\t}\n\tif coords.Longitude < 0 {\n\t\tlon = \"W\"\n\t}\n\tret = \" \"\n\tret += fmt.Sprintf(\"(%.1f°%s\", math.Abs(float64(coords.Latitude)), lat)\n\tret += fmt.Sprintf(\"%.1f°%s)\", math.Abs(float64(coords.Longitude)), lon)\n\treturn\n}\n\nfunc (c *mdConfig) printDay(day iface.Day) (ret []string) {\n\tdesiredTimesOfDay := []time.Duration{\n\t\t8 * time.Hour,\n\t\t12 * time.Hour,\n\t\t19 * time.Hour,\n\t\t23 * time.Hour,\n\t}\n\tret = make([]string, 5)\n\tfor i := range ret {\n\t\tret[i] = \"|\"\n\t}\n\n\t// save our selected elements from day.Slots in this array\n\tcols := make([]iface.Cond, len(desiredTimesOfDay))\n\t// find hourly data which fits the desired times of day best\n\tfor _, candidate := range day.Slots {\n\t\tcand := candidate.Time.UTC().Sub(candidate.Time.Truncate(24 * time.Hour))\n\t\tfor i, col := range cols {\n\t\t\tcur := col.Time.Sub(col.Time.Truncate(24 * time.Hour))\n\t\t\tif col.Time.IsZero() || math.Abs(float64(cand-desiredTimesOfDay[i])) < math.Abs(float64(cur-desiredTimesOfDay[i])) {\n\t\t\t\tcols[i] = candidate\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, s := range cols {\n\t\tret = c.formatCond(ret, s, false)\n\t\tfor i := range ret {\n\t\t\tret[i] = ret[i] + \"|\"\n\t\t}\n\t}\n\tdateFmt := day.Date.Format(\"Mon Jan 02\")\n\tret = append([]string{\n\t\t\"\\n### Forecast for \"+dateFmt+ \"\\n\",\n\t\t\"| Morning                   | Noon                      | Evening                   | Night                     |\",\n\t\t\"| ------------------------- | ------------------------- | ------------------------- | ------------------------- |\"},\n\t\tret...)\n\treturn ret\n}\n\nfunc (c *mdConfig) Setup() {\n\tflag.BoolVar(&c.coords, \"md-coords\", false, \"md-frontend: Show geo coordinates\")\n}\n\nfunc (c *mdConfig) Render(r iface.Data, unitSystem iface.UnitSystem) {\n\tc.unit = unitSystem\n\tfmt.Printf(\"## Weather for %s%s\\n\\n\", r.Location, c.formatGeo(r.GeoLoc))\n\tstdout := colorable.NewNonColorable(os.Stdout)\n\tout := c.formatCond(make([]string, 5), r.Current, true)\n\tfor _, val := range out {\n\t\tfmt.Fprintln(stdout, val)\n\t}\n\n\tif len(r.Forecast) == 0 {\n\t\treturn\n\t}\n\tif r.Forecast == nil {\n\t\tlog.Fatal(\"No detailed weather forecast available.\")\n\t}\n\tfor _, d := range r.Forecast {\n\t\tfor _, val := range c.printDay(d) {\n\t\t\tfmt.Fprintln(stdout, val)\n\t\t}\n\t}\n}\n\nfunc init() {\n\tiface.AllFrontends[\"markdown\"] = &mdConfig{}\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/schachmat/wego\n\ngo 1.20\n\nrequire (\n\tgithub.com/mattn/go-colorable v0.1.14\n\tgithub.com/mattn/go-runewidth v0.0.16\n\tgithub.com/schachmat/ingo v0.0.0-20170403011506-a4bdc0729a3f\n)\n\nrequire (\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/rivo/uniseg v0.4.4 // indirect\n\tgolang.org/x/sys v0.29.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=\ngithub.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=\ngithub.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=\ngithub.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/schachmat/ingo v0.0.0-20170403011506-a4bdc0729a3f h1:LVVgdfybimT/BiUdv92Jl2GKh8I6ixWcQkMUxZOcM+A=\ngithub.com/schachmat/ingo v0.0.0-20170403011506-a4bdc0729a3f/go.mod h1:WCPgQqzEa4YPOI8WKplmQu5WyU+BdI1cioHNkzWScP8=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=\ngolang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\n"
  },
  {
    "path": "iface/iface.go",
    "content": "package iface\n\nimport (\n\t\"log\"\n\t\"time\"\n)\n\ntype WeatherCode int\n\nconst (\n\tCodeUnknown WeatherCode = iota\n\tCodeCloudy\n\tCodeFog\n\tCodeHeavyRain\n\tCodeHeavyShowers\n\tCodeHeavySnow\n\tCodeHeavySnowShowers\n\tCodeLightRain\n\tCodeLightShowers\n\tCodeLightSleet\n\tCodeLightSleetShowers\n\tCodeLightSnow\n\tCodeLightSnowShowers\n\tCodePartlyCloudy\n\tCodeSunny\n\tCodeThunderyHeavyRain\n\tCodeThunderyShowers\n\tCodeThunderySnowShowers\n\tCodeVeryCloudy\n)\n\ntype Cond struct {\n\t// Time is the time, where this weather condition applies.\n\tTime time.Time\n\n\t// Code is the general weather condition and must be one the WeatherCode\n\t// constants.\n\tCode WeatherCode\n\n\t// Desc is a short string describing the condition. It should be just one\n\t// sentence.\n\tDesc string\n\n\t// TempC is the temperature in degrees celsius.\n\tTempC *float32\n\n\t// FeelsLikeC is the felt temperature (with windchill effect e.g.) in\n\t// degrees celsius.\n\tFeelsLikeC *float32\n\n\t// ChanceOfRainPercent is the probability of rain or snow. It must be in the\n\t// range [0, 100].\n\tChanceOfRainPercent *int\n\n\t// PrecipM is the precipitation amount in meters(!) per hour. Must be >= 0.\n\tPrecipM *float32\n\n\t// VisibleDistM is the visibility range in meters(!). It must be >= 0.\n\tVisibleDistM *float32\n\n\t// WindspeedKmph is the average wind speed in kilometers per hour. The value\n\t// must be >= 0.\n\tWindspeedKmph *float32\n\n\t// WindGustKmph is the maximum temporary wind speed in kilometers per\n\t// second. It should be > WindspeedKmph.\n\tWindGustKmph *float32\n\n\t// WinddirDegree is the direction the wind is blowing from on a clock\n\t// oriented circle with 360 degrees. 0 means the wind is blowing from north,\n\t// 90 means the wind is blowing from east, 180 means the wind is blowing\n\t// from south and 270 means the wind is blowing from west. The value must be\n\t// in the range [0, 359].\n\tWinddirDegree *int\n\n\t// Humidity is the *relative* humidity and must be in [0, 100].\n\tHumidity *int\n}\n\ntype Astro struct {\n\tMoonrise time.Time\n\tMoonset  time.Time\n\tSunrise  time.Time\n\tSunset   time.Time\n}\n\ntype Day struct {\n\t// Date is the date of this Day.\n\tDate time.Time\n\n\t// Slots is a slice of conditions for different times of day. They should be\n\t// ordered by the contained Time field.\n\tSlots []Cond\n\n\t// Astronomy contains planetary data.\n\tAstronomy Astro\n}\n\ntype LatLon struct {\n\tLatitude  float32\n\tLongitude float32\n}\n\ntype Data struct {\n\tCurrent  Cond\n\tForecast []Day\n\tLocation string\n\tGeoLoc   *LatLon\n}\n\ntype UnitSystem int\n\nconst (\n\tUnitsMetric UnitSystem = iota\n\tUnitsImperial\n\tUnitsSi\n\tUnitsMetricMs\n)\n\nfunc (u UnitSystem) Temp(tempC float32) (res float32, unit string) {\n\tif u == UnitsMetric || u == UnitsMetricMs {\n\t\treturn tempC, \"°C\"\n\t} else if u == UnitsImperial {\n\t\treturn tempC*1.8 + 32, \"°F\"\n\t} else if u == UnitsSi {\n\t\treturn tempC + 273.16, \"K\"\n\t}\n\tlog.Fatalln(\"Unknown unit system:\", u)\n\treturn\n}\n\nfunc (u UnitSystem) Speed(spdKmph float32) (res float32, unit string) {\n\tif u == UnitsMetric {\n\t\treturn spdKmph, \"km/h\"\n\t} else if u == UnitsImperial {\n\t\treturn spdKmph / 1.609, \"mph\"\n\t} else if u == UnitsSi || u == UnitsMetricMs {\n\t\treturn spdKmph / 3.6, \"m/s\"\n\t}\n\tlog.Fatalln(\"Unknown unit system:\", u)\n\treturn\n}\n\nfunc (u UnitSystem) Distance(distM float32) (res float32, unit string) {\n\tif u == UnitsMetric || u == UnitsSi || u == UnitsMetricMs {\n\t\tif distM < 1 {\n\t\t\treturn distM * 1000, \"mm\"\n\t\t} else if distM < 1000 {\n\t\t\treturn distM, \"m\"\n\t\t} else {\n\t\t\treturn distM / 1000, \"km\"\n\t\t}\n\t} else if u == UnitsImperial {\n\t\tres, unit = distM/0.0254, \"in\"\n\t\tif res < 3*12 { // 1yd = 3ft, 1ft = 12in\n\t\t\treturn\n\t\t} else if res < 8*10*22*36 { //1mi = 8fur, 1fur = 10ch, 1ch = 22yd\n\t\t\treturn res / 36, \"yd\"\n\t\t} else {\n\t\t\treturn res / 8 / 10 / 22 / 36, \"mi\"\n\t\t}\n\t}\n\tlog.Fatalln(\"Unknown unit system:\", u)\n\treturn\n}\n\ntype Backend interface {\n\tSetup()\n\tFetch(location string, numdays int) Data\n}\n\ntype Frontend interface {\n\tSetup()\n\tRender(weather Data, unitSystem UnitSystem)\n}\n\nvar (\n\tAllBackends  = make(map[string]Backend)\n\tAllFrontends = make(map[string]Frontend)\n)\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/schachmat/ingo\"\n\t_ \"github.com/schachmat/wego/backends\"\n\t_ \"github.com/schachmat/wego/frontends\"\n\t\"github.com/schachmat/wego/iface\"\n)\n\nfunc pluginLists() {\n\tbEnds := make([]string, 0, len(iface.AllBackends))\n\tfor name := range iface.AllBackends {\n\t\tbEnds = append(bEnds, name)\n\t}\n\tsort.Strings(bEnds)\n\n\tfEnds := make([]string, 0, len(iface.AllFrontends))\n\tfor name := range iface.AllFrontends {\n\t\tfEnds = append(fEnds, name)\n\t}\n\tsort.Strings(fEnds)\n\n\tfmt.Fprintln(os.Stderr, \"Available backends:\", strings.Join(bEnds, \", \"))\n\tfmt.Fprintln(os.Stderr, \"Available frontends:\", strings.Join(fEnds, \", \"))\n}\n\nfunc main() {\n\t// initialize backends and frontends (flags and default config)\n\tfor _, be := range iface.AllBackends {\n\t\tbe.Setup()\n\t}\n\tfor _, fe := range iface.AllFrontends {\n\t\tfe.Setup()\n\t}\n\n\t// initialize global flags and default config\n\tlocation := flag.String(\"location\", \"40.748,-73.985\", \"`LOCATION` to be queried\")\n\tflag.StringVar(location, \"l\", \"40.748,-73.985\", \"`LOCATION` to be queried (shorthand)\")\n\tnumdays := flag.Int(\"days\", 3, \"`NUMBER` of days of weather forecast to be displayed\")\n\tflag.IntVar(numdays, \"d\", 3, \"`NUMBER` of days of weather forecast to be displayed (shorthand)\")\n\tunitSystem := flag.String(\"units\", \"metric\", \"`UNITSYSTEM` to use for output.\\n    \\tChoices are: metric, imperial, si, metric-ms\")\n\tflag.StringVar(unitSystem, \"u\", \"metric\", \"`UNITSYSTEM` to use for output. (shorthand)\\n    \\tChoices are: metric, imperial, si, metric-ms\")\n\tselectedBackend := flag.String(\"backend\", \"openweathermap\", \"`BACKEND` to be used\")\n\tflag.StringVar(selectedBackend, \"b\", \"openweathermap\", \"`BACKEND` to be used (shorthand)\")\n\tselectedFrontend := flag.String(\"frontend\", \"ascii-art-table\", \"`FRONTEND` to be used\")\n\tflag.StringVar(selectedFrontend, \"f\", \"ascii-art-table\", \"`FRONTEND` to be used (shorthand)\")\n\n\t// print out a list of all backends and frontends in the usage\n\ttmpUsage := flag.Usage\n\tflag.Usage = func() {\n\t\ttmpUsage()\n\t\tpluginLists()\n\t}\n\n\t// read/write config and parse flags\n\tif err := ingo.Parse(\"wego\"); err != nil {\n\t\tlog.Fatalf(\"Error parsing config: %v\", err)\n\t}\n\n\t// non-flag shortcut arguments overwrite possible flag arguments\n\tfor _, arg := range flag.Args() {\n\t\tif v, err := strconv.Atoi(arg); err == nil && len(arg) == 1 {\n\t\t\t*numdays = v\n\t\t} else {\n\t\t\t*location = arg\n\t\t}\n\t}\n\n\t// get selected backend and fetch the weather data from it\n\tbe, ok := iface.AllBackends[*selectedBackend]\n\tif !ok {\n\t\tlog.Fatalf(\"Could not find selected backend \\\"%s\\\"\", *selectedBackend)\n\t}\n\tr := be.Fetch(*location, *numdays)\n\n\t// set unit system\n\tunit := iface.UnitsMetric\n\tif *unitSystem == \"imperial\" {\n\t\tunit = iface.UnitsImperial\n\t} else if *unitSystem == \"si\" {\n\t\tunit = iface.UnitsSi\n\t} else if *unitSystem == \"metric-ms\" {\n\t\tunit = iface.UnitsMetricMs\n\t}\n\n\t// get selected frontend and render the weather data with it\n\tfe, ok := iface.AllFrontends[*selectedFrontend]\n\tif !ok {\n\t\tlog.Fatalf(\"Could not find selected frontend \\\"%s\\\"\", *selectedFrontend)\n\t}\n\tfe.Render(r, unit)\n}\n"
  }
]