diff --git a/frontends/ascii-art-table.go b/frontends/ascii-art-table.go index 01a85aa..5601f80 100644 --- a/frontends/ascii-art-table.go +++ b/frontends/ascii-art-table.go @@ -18,10 +18,10 @@ import ( type aatConfig struct { coords bool monochrome bool - unit iface.UnitSystem + units iface.Units } -//TODO: replace s parameter with printf interface? +// TODO: replace s parameter with printf interface? func aatPad(s string, mustLen int) (ret string) { ansiEsc := regexp.MustCompile("\033.*?m") ret = s @@ -61,11 +61,11 @@ func (c *aatConfig) formatTemp(cond iface.Cond) string { break } } - t, _ := c.unit.Temp(temp) + t, _ := c.units.ConvertTemp(temp) return fmt.Sprintf("\033[38;5;%03dm%d\033[0m", col, int(t)) } - _, u := c.unit.Temp(0.0) + _, u := c.units.ConvertTemp(0.0) if cond.TempC == nil { return aatPad(fmt.Sprintf("? %s", u), 15) @@ -104,11 +104,11 @@ func (c *aatConfig) formatWind(cond iface.Cond) string { } } - s, _ := c.unit.Speed(spdKmph) + s, _ := c.units.ConvertSpeed(spdKmph) return fmt.Sprintf("\033[38;5;%03dm%d\033[0m", col, int(s)) } - _, u := c.unit.Speed(0.0) + _, u := c.units.ConvertSpeed(0.0) if cond.WindspeedKmph == nil { return aatPad(windDir(cond.WinddirDegree), 15) @@ -128,13 +128,13 @@ func (c *aatConfig) formatVisibility(cond iface.Cond) string { if cond.VisibleDistM == nil { return aatPad("", 15) } - v, u := c.unit.Distance(*cond.VisibleDistM) + v, u := c.units.ConvertDistance(*cond.VisibleDistM) return aatPad(fmt.Sprintf("%d %s", int(v), u), 15) } func (c *aatConfig) formatRain(cond iface.Cond) string { if cond.PrecipM != nil { - v, u := c.unit.Distance(*cond.PrecipM) + v, u := c.units.ConvertDistance(*cond.PrecipM) u += "/h" // it's the same in all unit systems if cond.ChanceOfRainPercent != nil { return aatPad(fmt.Sprintf("%.1f %s | %d%%", v, u, *cond.ChanceOfRainPercent), 15) @@ -367,8 +367,8 @@ func (c *aatConfig) Setup() { flag.BoolVar(&c.monochrome, "aat-monochrome", false, "aat-frontend: Monochrome output") } -func (c *aatConfig) Render(r iface.Data, unitSystem iface.UnitSystem) { - c.unit = unitSystem +func (c *aatConfig) Render(r iface.Data, units iface.Units) { + c.units = units fmt.Printf("Weather for %s%s\n\n", r.Location, c.formatGeo(r.GeoLoc)) stdout := colorable.NewColorableStdout() diff --git a/frontends/emoji.go b/frontends/emoji.go index 6d93b5f..de5dff9 100644 --- a/frontends/emoji.go +++ b/frontends/emoji.go @@ -12,7 +12,7 @@ import ( ) type emojiConfig struct { - unit iface.UnitSystem + units iface.Units } func (c *emojiConfig) formatTemp(cond iface.Cond) string { @@ -34,11 +34,11 @@ func (c *emojiConfig) formatTemp(cond iface.Cond) string { break } } - t, _ := c.unit.Temp(temp) + t, _ := c.units.ConvertTemp(temp) return fmt.Sprintf("\033[38;5;%03dm%d\033[0m", col, int(t)) } - _, u := c.unit.Temp(0.0) + _, u := c.units.ConvertTemp(0.0) if cond.TempC == nil { return aatPad(fmt.Sprintf("? %s", u), 12) @@ -94,19 +94,19 @@ func (c *emojiConfig) formatCond(cur []string, cond iface.Cond, current bool) (r } func (c *emojiConfig) printAstro(astro iface.Astro) { - // print sun astronomy data if present + // print sun astronomy data if present if astro.Sunrise != astro.Sunset { - // half the distance between sunrise and sunset - noon_distance := time.Duration(int64(float32(astro.Sunset.UnixNano() - astro.Sunrise.UnixNano()) * 0.5)) - // time for solar noon - noon := astro.Sunrise.Add(noon_distance) + // half the distance between sunrise and sunset + noon_distance := time.Duration(int64(float32(astro.Sunset.UnixNano()-astro.Sunrise.UnixNano()) * 0.5)) + // time for solar noon + noon := astro.Sunrise.Add(noon_distance) - // the actual print statement - fmt.Printf("🌞 rise↗ %s noon↑ %s set↘ %s\n", astro.Sunrise.Format(time.Kitchen), noon.Format(time.Kitchen), astro.Sunset.Format(time.Kitchen)) + // the actual print statement + fmt.Printf("🌞 rise↗ %s noon↑ %s set↘ %s\n", astro.Sunrise.Format(time.Kitchen), noon.Format(time.Kitchen), astro.Sunset.Format(time.Kitchen)) } - // print moon astronomy data if present + // print moon astronomy data if present if astro.Moonrise != astro.Moonset { - fmt.Printf("🌚 rise↗ %s set↘ %s\n", astro.Moonrise.Format(time.Kitchen), astro.Moonset) + fmt.Printf("🌚 rise↗ %s set↘ %s\n", astro.Moonrise.Format(time.Kitchen), astro.Moonset) } } @@ -159,8 +159,8 @@ func (c *emojiConfig) printDay(day iface.Day) (ret []string) { func (c *emojiConfig) Setup() { } -func (c *emojiConfig) Render(r iface.Data, unitSystem iface.UnitSystem) { - c.unit = unitSystem +func (c *emojiConfig) Render(r iface.Data, units iface.Units) { + c.units = units fmt.Printf("Weather for %s\n\n", r.Location) stdout := colorable.NewColorableStdout() diff --git a/frontends/json.go b/frontends/json.go index 13cf8fd..5adf884 100644 --- a/frontends/json.go +++ b/frontends/json.go @@ -17,7 +17,7 @@ func (c *jsnConfig) Setup() { flag.BoolVar(&c.noIndent, "jsn-no-indent", false, "json frontend: do not indent the output") } -func (c *jsnConfig) Render(r iface.Data, unitSystem iface.UnitSystem) { +func (c *jsnConfig) Render(r iface.Data, units iface.Units) { var b []byte var err error if c.noIndent { diff --git a/frontends/markdown.go b/frontends/markdown.go index 6e9c496..279c002 100644 --- a/frontends/markdown.go +++ b/frontends/markdown.go @@ -15,8 +15,8 @@ import ( ) type mdConfig struct { - coords bool - unit iface.UnitSystem + coords bool + units iface.Units } func mdPad(s string, mustLen int) (ret string) { @@ -39,11 +39,11 @@ func mdPad(s string, mustLen int) (ret string) { func (c *mdConfig) formatTemp(cond iface.Cond) string { - cvtUnits := func (temp float32) string { - t, _ := c.unit.Temp(temp) + cvtUnits := func(temp float32) string { + t, _ := c.units.ConvertTemp(temp) return fmt.Sprintf("%d", int(t)) } - _, u := c.unit.Temp(0.0) + _, u := c.units.ConvertTemp(0.0) if cond.TempC == nil { return mdPad(fmt.Sprintf("? %s", u), 15) @@ -66,11 +66,11 @@ func (c *mdConfig) formatWind(cond iface.Cond) string { return arrows[((*deg+22)%360)/45] } color := func(spdKmph float32) string { - s, _ := c.unit.Speed(spdKmph) + s, _ := c.units.ConvertSpeed(spdKmph) return fmt.Sprintf("| %d ", int(s)) } - _, u := c.unit.Speed(0.0) + _, u := c.units.ConvertSpeed(0.0) if cond.WindspeedKmph == nil { return mdPad(windDir(cond.WinddirDegree), 15) @@ -90,13 +90,13 @@ func (c *mdConfig) formatVisibility(cond iface.Cond) string { if cond.VisibleDistM == nil { return mdPad("", 15) } - v, u := c.unit.Distance(*cond.VisibleDistM) + v, u := c.units.ConvertDistance(*cond.VisibleDistM) return mdPad(fmt.Sprintf("%d %s", int(v), u), 15) } func (c *mdConfig) formatRain(cond iface.Cond) string { if cond.PrecipM != nil { - v, u := c.unit.Distance(*cond.PrecipM) + v, u := c.units.ConvertDistance(*cond.PrecipM) u += "/h" // it's the same in all unit systems if cond.ChanceOfRainPercent != nil { return mdPad(fmt.Sprintf("%.1f %s | %d%%", v, u, *cond.ChanceOfRainPercent), 15) @@ -197,7 +197,7 @@ func (c *mdConfig) printDay(day iface.Day) (ret []string) { } dateFmt := day.Date.Format("Mon Jan 02") ret = append([]string{ - "\n### Forecast for "+dateFmt+ "\n", + "\n### Forecast for " + dateFmt + "\n", "| Morning | Noon | Evening | Night |", "| ------------------------- | ------------------------- | ------------------------- | ------------------------- |"}, ret...) @@ -208,8 +208,8 @@ func (c *mdConfig) Setup() { flag.BoolVar(&c.coords, "md-coords", false, "md-frontend: Show geo coordinates") } -func (c *mdConfig) Render(r iface.Data, unitSystem iface.UnitSystem) { - c.unit = unitSystem +func (c *mdConfig) Render(r iface.Data, units iface.Units) { + c.units = units fmt.Printf("## Weather for %s%s\n\n", r.Location, c.formatGeo(r.GeoLoc)) stdout := colorable.NewNonColorable(os.Stdout) out := c.formatCond(make([]string, 5), r.Current, true) diff --git a/iface/iface.go b/iface/iface.go index 0d4ea91..46542a7 100644 --- a/iface/iface.go +++ b/iface/iface.go @@ -108,41 +108,117 @@ type Data struct { GeoLoc *LatLon } -type UnitSystem int +type Unit int const ( - UnitsMetric UnitSystem = iota - UnitsImperial - UnitsSi - UnitsMetricMs + Celsius Unit = iota + Fahrenheit + Kelvin + Kmh + Mph + Ms + Beaufort + Metric + Imperial ) -func (u UnitSystem) Temp(tempC float32) (res float32, unit string) { - if u == UnitsMetric || u == UnitsMetricMs { +type Units struct { + Temp Unit + Speed Unit + Distance Unit +} + +func NewUnits(tempUnit, speedUnit, distanceUnit string) Units { + units := Units{ + Temp: Celsius, + Speed: Kmh, + Distance: Metric, + } + + unitMap := map[string]Unit{ + "celsius": Celsius, + "fahrenheit": Fahrenheit, + "kelvin": Kelvin, + "kmh": Kmh, + "mph": Mph, + "ms": Ms, + "beaufort": Beaufort, + "metric": Metric, + "imperial": Imperial, + } + + if unit, ok := unitMap[tempUnit]; ok { + units.Temp = unit + } + if unit, ok := unitMap[speedUnit]; ok { + units.Speed = unit + } + if unit, ok := unitMap[distanceUnit]; ok { + units.Distance = unit + } + + return units +} + +func (u Units) ConvertTemp(tempC float32) (res float32, unit string) { + switch u.Temp { + case Celsius: return tempC, "°C" - } else if u == UnitsImperial { + case Fahrenheit: return tempC*1.8 + 32, "°F" - } else if u == UnitsSi { - return tempC + 273.16, "°K" + case Kelvin: + return tempC + 273.16, "K" } - log.Fatalln("Unknown unit system:", u) + log.Fatalln("Unknown temperature unit:", u) return } -func (u UnitSystem) Speed(spdKmph float32) (res float32, unit string) { - if u == UnitsMetric { +func (u Units) ConvertSpeed(spdKmph float32) (res float32, unit string) { + switch u.Speed { + case Kmh: return spdKmph, "km/h" - } else if u == UnitsImperial { + case Mph: return spdKmph / 1.609, "mph" - } else if u == UnitsSi || u == UnitsMetricMs { + case Ms: return spdKmph / 3.6, "m/s" + case Beaufort: + switch { + case spdKmph < 1: + return 0, "Bft" + case spdKmph < 6: + return 1, "Bft" + case spdKmph < 12: + return 2, "Bft" + case spdKmph < 20: + return 3, "Bft" + case spdKmph < 29: + return 4, "Bft" + case spdKmph < 39: + return 5, "Bft" + case spdKmph < 50: + return 6, "Bft" + case spdKmph < 62: + return 7, "Bft" + case spdKmph < 75: + return 8, "Bft" + case spdKmph < 89: + return 9, "Bft" + case spdKmph < 103: + return 10, "Bft" + case spdKmph < 118: + return 11, "Bft" + default: + return 12, "Bft" + } } - log.Fatalln("Unknown unit system:", u) + + log.Fatalln("Unknown speed unit:", u) return } -func (u UnitSystem) Distance(distM float32) (res float32, unit string) { - if u == UnitsMetric || u == UnitsSi || u == UnitsMetricMs { +func (u Units) ConvertDistance(distM float32) (res float32, unit string) { + switch u.Distance { + case Metric: if distM < 1 { return distM * 1000, "mm" } else if distM < 1000 { @@ -150,17 +226,17 @@ func (u UnitSystem) Distance(distM float32) (res float32, unit string) { } else { return distM / 1000, "km" } - } else if u == UnitsImperial { + case Imperial: res, unit = distM/0.0254, "in" - if res < 3*12 { // 1yd = 3ft, 1ft = 12in + if res < 3*12 { return - } else if res < 8*10*22*36 { //1mi = 8fur, 1fur = 10ch, 1ch = 22yd + } else if res < 8*10*22*36 { return res / 36, "yd" } else { return res / 8 / 10 / 22 / 36, "mi" } } - log.Fatalln("Unknown unit system:", u) + log.Fatalln("Unknown distance unit:", u) return } @@ -171,7 +247,7 @@ type Backend interface { type Frontend interface { Setup() - Render(weather Data, unitSystem UnitSystem) + Render(weather Data, unitSystem Units) } var ( diff --git a/main.go b/main.go index 50ec775..3dbeb5c 100644 --- a/main.go +++ b/main.go @@ -46,12 +46,13 @@ func main() { flag.StringVar(location, "l", "40.748,-73.985", "`LOCATION` to be queried (shorthand)") numdays := flag.Int("days", 3, "`NUMBER` of days of weather forecast to be displayed") flag.IntVar(numdays, "d", 3, "`NUMBER` of days of weather forecast to be displayed (shorthand)") - unitSystem := flag.String("units", "metric", "`UNITSYSTEM` to use for output.\n \tChoices are: metric, imperial, si, metric-ms") - flag.StringVar(unitSystem, "u", "metric", "`UNITSYSTEM` to use for output. (shorthand)\n \tChoices are: metric, imperial, si, metric-ms") selectedBackend := flag.String("backend", "openweathermap", "`BACKEND` to be used") flag.StringVar(selectedBackend, "b", "openweathermap", "`BACKEND` to be used (shorthand)") selectedFrontend := flag.String("frontend", "ascii-art-table", "`FRONTEND` to be used") flag.StringVar(selectedFrontend, "f", "ascii-art-table", "`FRONTEND` to be used (shorthand)") + tempUnit := flag.String("unit-temperature", "celsius", "`UNIT` for temperature.\n \tChoices are: celsius, fahrenheit, kelvin") + speedUnit := flag.String("unit-speed", "kmh", "`UNIT` for wind speed.\n \tChoices are: kmh, mph, ms, beaufort") + distanceUnit := flag.String("unit-distance", "metric", "`UNIT` for distance.\n \tChoices are: metric, imperial") // print out a list of all backends and frontends in the usage tmpUsage := flag.Usage @@ -81,20 +82,11 @@ func main() { } r := be.Fetch(*location, *numdays) - // set unit system - unit := iface.UnitsMetric - if *unitSystem == "imperial" { - unit = iface.UnitsImperial - } else if *unitSystem == "si" { - unit = iface.UnitsSi - } else if *unitSystem == "metric-ms" { - unit = iface.UnitsMetricMs - } - // get selected frontend and render the weather data with it fe, ok := iface.AllFrontends[*selectedFrontend] if !ok { log.Fatalf("Could not find selected frontend \"%s\"", *selectedFrontend) } - fe.Render(r, unit) + units := iface.NewUnits(*tempUnit, *speedUnit, *distanceUnit) + fe.Render(r, units) }