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) }