diff options
| author | twells46 <tom@wellsth.com> | 2026-03-07 08:50:19 -0600 |
|---|---|---|
| committer | twells46 <tom@wellsth.com> | 2026-03-07 09:07:19 -0600 |
| commit | d2b6b3060cfbeb17047f1fd1f989677f28f677eb (patch) | |
| tree | 1f17a8d76ef752bee60f20d338d56fdd4a597fb3 /main.go | |
Initial commit
Diffstat (limited to 'main.go')
| -rw-r--r-- | main.go | 223 |
1 files changed, 223 insertions, 0 deletions
@@ -0,0 +1,223 @@ +package main + +import ( + "fmt" + "strconv" + "strings" + "time" + + "charm.land/lipgloss/v2" + "github.com/NimbleMarkets/ntcharts/linechart/timeserieslinechart" +) + +var block = lipgloss.NewStyle(). + Padding(1). + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Magenta) +var bold = lipgloss.NewStyle(). + Bold(true) +var tStyle = bold + +// TODO Faint should render based on light/dark terminal theme +var faint = lipgloss.NewStyle(). + Foreground(lipgloss.BrightWhite) + +var temperature_colors = lipgloss.Blend1D(20, + lipgloss.Color("#94006f"), + lipgloss.Blue, + lipgloss.Blue, + lipgloss.Green, + lipgloss.Color("#ff5500"), + lipgloss.Red, +) + +// TODO Handle light theme +var rain_colors = lipgloss.Blend1D(10, + lipgloss.White, + lipgloss.Blue, +) + +func main() { + weather, coords := GetWeather() + header := fmt.Sprintf("%s, %s (%s, %s)\n", + bold.Render(coords.Name), + bold.Render(coords.State), + faint.Render(ff(weather.Lat)), + faint.Render(ff(weather.Lon)), + ) + current := fmtCurrent(weather) + minutely := fmtMinutely(weather) + hourly := fmtHourly(weather) + + lipgloss.Println(lipgloss.JoinVertical(lipgloss.Center, + header, + current, + minutely, + hourly, + )) +} + +func fmtHourly(w WeatherResponse) string { + hours := make([]string, len(w.Hourly)) + // TODO Make this not ugly and include units + for i, v := range w.Hourly { + // For now only show next 8 + // 48 available later + if i > 7 { + break + } + temperature_color := int(v.Temp / 5) + tStyle = tStyle.Foreground(temperature_colors[temperature_color]) + rain_color := int(v.Pop * 9) + rStyle := tStyle.Foreground(rain_colors[rain_color]) + now := time.Unix(v.Dt, 0).Local() + hours[i] = block.Render(lipgloss.JoinVertical(lipgloss.Center, + now.Format(time.DateOnly), + now.Format(time.Kitchen), + tStyle.Render(fmt.Sprintf("%g°F", v.Temp)), + rStyle.Render(fmt.Sprintf("雨%d%%", int(v.Pop*100))), + )) + } + + return lipgloss.JoinHorizontal(lipgloss.Center, hours...) +} + +/* +func fmtHourlyGraph(w WeatherResponse) string { + dataset := make([]timeserieslinechart.TimePoint, len(w.Hourly)) + + for i, v := range w.Hourly { + fmt.Println(linechart.DefaultLabelFormatter()(i, float64(v.Dt))) + dataset[i] = timeserieslinechart.TimePoint{ + Time: time.Unix(v.Dt, 0).Local(), + Value: v.Temp, + } + } + chart := timeserieslinechart.New( + 80, + 10, + timeserieslinechart.WithTimeSeries(dataset), + timeserieslinechart.WithXLabelFormatter(func(i int, _ float64) string { + res := time.Unix(w.Hourly[0].Dt, 0).Add(time.Hour * time.Duration(i)) + return res.Local().Format(time.Kitchen) + }), + ) + chart.DrawBraille() + + return block.Render(chart.View()) +} +*/ + +func fmtMinutely(w WeatherResponse) string { + dataset := make([]timeserieslinechart.TimePoint, len(w.Minutely)) + + empty := true + for i, v := range w.Minutely { + now := time.Unix(v.Dt, 0).Local() + if v.Precipitation != 0 { + empty = false + } + dataset[i] = timeserieslinechart.TimePoint{ + Time: now, + Value: v.Precipitation, + } + } + + if empty { + return "" + } + + tslc := timeserieslinechart.New( + 80, + 10, + timeserieslinechart.WithXLabelFormatter(func(_ int, d float64) string { + res := (int64(d) - w.Minutely[0].Dt) / 60 + return strconv.Itoa(int(res)) + }), + timeserieslinechart.WithTimeSeries(dataset), + ) + + tslc.DrawBraille() + + return block.BorderForeground(lipgloss.Blue).Render( + lipgloss.JoinVertical(lipgloss.Center, + bold.Render("Precipitation"), + lipgloss.JoinHorizontal(lipgloss.Center, + "mm/h", + tslc.View(), + ), + "minutes", + ), + ) +} + +func fmtCurrent(w WeatherResponse) string { + // TODO This color gradient only handles 0-100 + temperature_color := int(w.Current.Temp / 5) + tStyle = tStyle.Foreground(temperature_colors[temperature_color]) + + var b strings.Builder + + // Temperature + fmt.Fprintf(&b, "%s°F (%s°F)", + tStyle.Render(ff(w.Current.Temp)), + tStyle.Render(ff(w.Current.FeelsLike)), + ) + temp := block.Render(b.String()) + b.Reset() + + fmt.Fprintf(&b, "Humidity: %s%%", + bold.Render(strconv.Itoa(w.Current.Humidity)), + ) + humidity := block.Render(b.String()) + b.Reset() + + fmt.Fprintf(&b, "Clouds: %s%%", + bold.Render(strconv.Itoa(w.Current.Clouds)), + ) + clouds := block.Render(b.String()) + b.Reset() + + fmt.Fprintf(&b, "%s° at %s MPH", + bold.Render(strconv.Itoa(w.Current.WindDeg)), + bold.Render(ff(w.Current.WindSpeed)), + ) + wind := block.Render(b.String()) + b.Reset() + + var rain string + if w.Current.Rain != nil { + rain = block. + BorderForeground(lipgloss.Blue). + Render(fmt.Sprintf("Rain:\n%v mm/h", w.Current.Rain.Hour)) + b.Reset() + } + + var snow string + if w.Current.Snow != nil { + snow = block. + BorderForeground(lipgloss.Blue). + Render(fmt.Sprintf("Snow:\n%v mm/h", w.Current.Snow.Hour)) + b.Reset() + } + + now := time.Unix(w.Current.Dt, 0).Local() + subheader := fmt.Sprintf("%s with %s", now.Format(time.Kitchen), w.Current.Weather[0].Description) + + return lipgloss.JoinVertical(lipgloss.Center, + subheader, + lipgloss.JoinHorizontal(lipgloss.Top, + temp, + humidity, + clouds, + wind, + rain, + snow, + ), + ) +} + +// [f]ormat [f]loat +func ff(f float64) string { + return strconv.FormatFloat(f, 'f', -1, 64) +} |