diff --git a/src/render/custom.zig b/src/render/custom.zig index a820f4a..4a3a227 100644 --- a/src/render/custom.zig +++ b/src/render/custom.zig @@ -91,6 +91,7 @@ test "render custom format with location and temp" { .wind_deg = 22.5, .pressure_mb = 1019.0, .precip_mm = 0.0, + .visibility_km = null, }, .forecast = &[_]types.ForecastDay{}, .allocator = allocator, @@ -118,6 +119,7 @@ test "render custom format with newline" { .wind_deg = 90.0, .pressure_mb = 1020.0, .precip_mm = 0.0, + .visibility_km = null, }, .forecast = &[_]types.ForecastDay{}, .allocator = allocator, @@ -144,6 +146,7 @@ test "render custom format with humidity and pressure" { .wind_deg = 270.0, .pressure_mb = 1012.0, .precip_mm = 0.2, + .visibility_km = null, }, .forecast = &[_]types.ForecastDay{}, .allocator = allocator, @@ -171,6 +174,7 @@ test "render custom format with imperial units" { .wind_deg = 0.0, .pressure_mb = 1013.0, .precip_mm = 2.5, + .visibility_km = null, }, .forecast = &[_]types.ForecastDay{}, .allocator = allocator, diff --git a/src/render/formatted.zig b/src/render/formatted.zig index c031686..081405c 100644 --- a/src/render/formatted.zig +++ b/src/render/formatted.zig @@ -1,6 +1,12 @@ const std = @import("std"); const types = @import("../weather/types.zig"); -const utils = @import("utils.zig"); + +fn degreeToArrow(deg: f32) []const u8 { + const normalized = @mod(deg + 22.5, 360.0); + const idx: usize = @intFromFloat(normalized / 45.0); + const arrows = [_][]const u8{ "↓", "↙", "←", "↖", "↑", "↗", "→", "↘" }; + return arrows[@min(idx, 7)]; +} pub const Format = enum { plain_text, @@ -45,7 +51,6 @@ fn renderCurrent(w: *std.Io.Writer, current: types.CurrentCondition, options: Re const wind_speed = if (options.use_imperial) current.wind_kph * 0.621371 else current.wind_kph; const precip_unit = if (options.use_imperial) "in" else "mm"; const precip = if (options.use_imperial) current.precip_mm * 0.0393701 else current.precip_mm; - const visibility = if (options.use_imperial) "6 mi" else "10 km"; const art = getWeatherArt(current.weather_code); const sign: u8 = if (temp >= 0) '+' else '-'; @@ -56,8 +61,14 @@ fn renderCurrent(w: *std.Io.Writer, current: types.CurrentCondition, options: Re if (options.format == .plain_text) { try w.print("{s} {s}\n", .{ art[0], current.condition }); try w.print("{s} {c}{d:.0}({c}{d:.0}) {s}\n", .{ art[1], sign, abs_temp, fl_sign, abs_fl, temp_unit }); - try w.print("{s} {s} {d:.0} {s}\n", .{ art[2], utils.degreeToDirection(current.wind_deg), wind_speed, wind_unit }); - try w.print("{s} {s}\n", .{ art[3], visibility }); + try w.print("{s} {s} {d:.0} {s}\n", .{ art[2], degreeToArrow(current.wind_deg), wind_speed, wind_unit }); + if (current.visibility_km) |vis_km| { + const visibility = if (options.use_imperial) vis_km * 0.621371 else vis_km; + const vis_unit = if (options.use_imperial) "mi" else "km"; + try w.print("{s} {d:.0} {s}\n", .{ art[3], visibility, vis_unit }); + } else { + try w.print("{s}\n", .{std.mem.trimRight(u8, art[3], " ")}); + } try w.print("{s} {d:.1} {s}\n", .{ art[4], precip, precip_unit }); } else { const temp_color_code = tempColor(current.temp_c); @@ -67,8 +78,14 @@ fn renderCurrent(w: *std.Io.Writer, current: types.CurrentCondition, options: Re try w.print("{s}{s}{s} {s}\n", .{ cloud_color, art[0], reset, current.condition }); try w.print("{s}{s}{s} \x1b[38;5;{d}m{c}{d:.0}({c}{d:.0}){s} {s}\n", .{ cloud_color, art[1], reset, temp_color_code, sign, abs_temp, fl_sign, abs_fl, reset, temp_unit }); - try w.print("{s}{s}{s} {s} \x1b[38;5;{d}m{d:.0}{s} {s}\n", .{ cloud_color, art[2], reset, utils.degreeToDirection(current.wind_deg), wind_color_code, wind_speed, reset, wind_unit }); - try w.print("{s}{s}{s} {s}\n", .{ cloud_color, art[3], reset, visibility }); + try w.print("{s}{s}{s} {s} \x1b[38;5;{d}m{d:.0}{s} {s}\n", .{ cloud_color, art[2], reset, degreeToArrow(current.wind_deg), wind_color_code, wind_speed, reset, wind_unit }); + if (current.visibility_km) |vis_km| { + const visibility = if (options.use_imperial) vis_km * 0.621371 else vis_km; + const vis_unit = if (options.use_imperial) "mi" else "km"; + try w.print("{s}{s}{s} {d:.0} {s}\n", .{ cloud_color, art[3], reset, visibility, vis_unit }); + } else { + try w.print("{s}{s}{s}\n", .{ cloud_color, std.mem.trimRight(u8, art[3], " "), reset }); + } try w.print("{s}{s}{s} {d:.1} {s}\n", .{ cloud_color, art[4], reset, precip, precip_unit }); } } @@ -155,14 +172,16 @@ fn renderHourlyCell(w: *std.Io.Writer, hour: types.HourlyForecast, line: usize, .wind => { const wind_speed = if (options.use_imperial) hour.wind_kph * 0.621371192237 else hour.wind_kph; const wind_unit = if (options.use_imperial) "mph" else "km/h"; - // Wind direction is two bytes, so we need to subtract one - // Somehow the actual answer here is two, though? - try cell_writer.print("↑ {d:.0} {s}", .{ wind_speed, wind_unit }); - display_width_byte_length_offset = 2; + const arrow = degreeToArrow(hour.wind_deg); + try cell_writer.print("{s} {d:.0} {s}", .{ arrow, wind_speed, wind_unit }); + display_width_byte_length_offset = arrow.len - 1; }, .visibility => { - const visibility = if (options.use_imperial) "6 mi" else "10 km"; - try cell_writer.print("{s}", .{visibility}); + if (hour.visibility_km) |vis_km| { + const visibility = if (options.use_imperial) vis_km * 0.621371 else vis_km; + const vis_unit = if (options.use_imperial) "mi" else "km"; + try cell_writer.print("{d:.0} {s}", .{ visibility, vis_unit }); + } }, .precipitation => { const precip = if (options.use_imperial) hour.precip_mm * 0.0393701 else hour.precip_mm; @@ -331,6 +350,7 @@ test "render with imperial units" { .wind_deg = 0.0, .pressure_mb = 1013.0, .precip_mm = 0.0, + .visibility_km = null, }, .forecast = &.{}, .allocator = std.testing.allocator, @@ -358,6 +378,7 @@ test "clear weather art" { .wind_deg = 0.0, .pressure_mb = 1013.0, .precip_mm = 0.0, + .visibility_km = null, }, .forecast = &.{}, .allocator = std.testing.allocator, @@ -383,6 +404,7 @@ test "partly cloudy weather art" { .wind_deg = 45.0, .pressure_mb = 1013.0, .precip_mm = 0.0, + .visibility_km = null, }, .forecast = &.{}, .allocator = std.testing.allocator, @@ -408,6 +430,7 @@ test "cloudy weather art" { .wind_deg = 90.0, .pressure_mb = 1010.0, .precip_mm = 0.0, + .visibility_km = null, }, .forecast = &.{}, .allocator = std.testing.allocator, @@ -433,6 +456,7 @@ test "rain weather art" { .wind_deg = 135.0, .pressure_mb = 1005.0, .precip_mm = 5.0, + .visibility_km = null, }, .forecast = &.{}, .allocator = std.testing.allocator, @@ -457,6 +481,7 @@ test "thunderstorm weather art" { .wind_deg = 180.0, .pressure_mb = 1000.0, .precip_mm = 10.0, + .visibility_km = null, }, .forecast = &.{}, .allocator = std.testing.allocator, @@ -481,6 +506,7 @@ test "snow weather art" { .wind_deg = 225.0, .pressure_mb = 1008.0, .precip_mm = 3.0, + .visibility_km = null, }, .forecast = &.{}, .allocator = std.testing.allocator, @@ -505,6 +531,7 @@ test "sleet weather art" { .wind_deg = 270.0, .pressure_mb = 1007.0, .precip_mm = 2.0, + .visibility_km = null, }, .forecast = &.{}, .allocator = std.testing.allocator, @@ -529,6 +556,7 @@ test "fog weather art" { .wind_deg = 315.0, .pressure_mb = 1012.0, .precip_mm = 0.0, + .visibility_km = null, }, .forecast = &.{}, .allocator = std.testing.allocator, @@ -553,6 +581,7 @@ test "unknown weather code art" { .wind_deg = 0.0, .pressure_mb = 1013.0, .precip_mm = 0.0, + .visibility_km = null, }, .forecast = &.{}, .allocator = std.testing.allocator, @@ -580,6 +609,7 @@ test "temperature matches between ansi and custom format" { .wind_deg = 0.0, .pressure_mb = 1013.0, .precip_mm = 0.0, + .visibility_km = null, }, .forecast = &.{}, .allocator = std.testing.allocator, @@ -686,8 +716,8 @@ test "plain text format - MetNo real data" { \\ \\ .-. Light rain \\ ( ). +7(+7) °C - \\ (___(__) E 6 km/h - \\ ʻ ʻ ʻ ʻ 10 km + \\ (___(__) ← 6 km/h + \\ ʻ ʻ ʻ ʻ \\ ʻ ʻ ʻ ʻ 0.0 mm \\ \\ @@ -703,8 +733,8 @@ test "plain text format - MetNo real data" { \\├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤ \\│ .-. Rain │ Cloudy │ .-. Heavy rain │ \ / Partly cloudy │ \\│ ( ). +7(+7) °C │ .--. +6(+6) °C │ ( ). +7(+7) °C │ _ /"".-. +8(+8) °C │ - \\│ (___(__) ↑ 5 km/h │ .-( ). ↑ 9 km/h │ (___(__) ↑ 14 km/h │ \_( ). ↑ 12 km/h │ - \\│ ʻ ʻ ʻ ʻ 10 km │ (___.__)__) 10 km │ ʻ ʻ ʻ ʻ 10 km │ /(___(__) 10 km │ + \\│ (___(__) ↖ 5 km/h │ .-( ). ↓ 9 km/h │ (___(__) ↖ 14 km/h │ \_( ). ↑ 12 km/h │ + \\│ ʻ ʻ ʻ ʻ │ (___.__)__) │ ʻ ʻ ʻ ʻ │ /(___(__) │ \\│ ʻ ʻ ʻ ʻ 0.3 mm | 0% │ 0.0 mm | 0% │ ʻ ʻ ʻ ʻ 1.2 mm | 0% │ 0.0 mm | 0% │ \\└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘ \\ ┌─────────────┐ @@ -713,8 +743,8 @@ test "plain text format - MetNo real data" { \\├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤ \\│ Cloudy │ Cloudy │ Cloudy │ .-. Light rain │ \\│ .--. +10(+10) °C │ .--. +8(+8) °C │ .--. +10(+10) °C │ ( ). +9(+9) °C │ - \\│ .-( ). ↑ 7 km/h │ .-( ). ↑ 14 km/h │ .-( ). ↑ 31 km/h │ (___(__) ↑ 24 km/h │ - \\│ (___.__)__) 10 km │ (___.__)__) 10 km │ (___.__)__) 10 km │ ʻ ʻ ʻ ʻ 10 km │ + \\│ .-( ). ↙ 7 km/h │ .-( ). ↑ 14 km/h │ .-( ). ↑ 31 km/h │ (___(__) ↑ 24 km/h │ + \\│ (___.__)__) │ (___.__)__) │ (___.__)__) │ ʻ ʻ ʻ ʻ │ \\│ 0.0 mm | 0% │ 0.0 mm | 0% │ 0.0 mm | 0% │ ʻ ʻ ʻ ʻ 0.2 mm | 0% │ \\└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘ \\ diff --git a/src/render/json.zig b/src/render/json.zig index 8c97055..bd5082d 100644 --- a/src/render/json.zig +++ b/src/render/json.zig @@ -34,6 +34,7 @@ test "render json format" { .wind_deg = 225.0, .pressure_mb = 1013.0, .precip_mm = 0.0, + .visibility_km = null, }, .forecast = &[_]types.ForecastDay{}, .allocator = allocator, diff --git a/src/render/line.zig b/src/render/line.zig index 3705093..ba78b89 100644 --- a/src/render/line.zig +++ b/src/render/line.zig @@ -131,6 +131,7 @@ test "format 1" { .wind_deg = 0.0, .pressure_mb = 1013.0, .precip_mm = 0.0, + .visibility_km = null, }, .forecast = &.{}, .allocator = std.testing.allocator, @@ -155,6 +156,7 @@ test "custom format" { .wind_deg = 0.0, .pressure_mb = 1013.0, .precip_mm = 0.0, + .visibility_km = null, }, .forecast = &.{}, .allocator = std.testing.allocator, @@ -179,6 +181,7 @@ test "format 2 with imperial units" { .wind_deg = 135.0, .pressure_mb = 1013.0, .precip_mm = 0.0, + .visibility_km = null, }, .forecast = &.{}, .allocator = std.testing.allocator, diff --git a/src/render/v2.zig b/src/render/v2.zig index 79f7208..b9eedbc 100644 --- a/src/render/v2.zig +++ b/src/render/v2.zig @@ -81,6 +81,7 @@ test "render v2 format" { .wind_deg = 315.0, .pressure_mb = 1015.0, .precip_mm = 0.5, + .visibility_km = null, }, .forecast = &[_]types.ForecastDay{}, .allocator = allocator, @@ -110,6 +111,7 @@ test "render v2 format with imperial units" { .wind_deg = 0.0, .pressure_mb = 1013.0, .precip_mm = 0.0, + .visibility_km = null, }, .forecast = &[_]types.ForecastDay{}, .allocator = allocator, diff --git a/src/weather/MetNo.zig b/src/weather/MetNo.zig index b24ef8d..d98b68c 100644 --- a/src/weather/MetNo.zig +++ b/src/weather/MetNo.zig @@ -194,6 +194,7 @@ fn parseMetNoResponse(allocator: std.mem.Allocator, coords: Coordinates, json: s .wind_deg = wind_deg, .pressure_mb = pressure_mb, .precip_mm = 0.0, + .visibility_km = null, }, .forecast = forecast, .allocator = allocator, @@ -244,6 +245,10 @@ fn parseForecastDays(allocator: std.mem.Allocator, timeseries: []std.json.Value) const temp: f32 = @floatCast(details_obj.object.get("air_temperature").?.float); const wind_ms = details_obj.object.get("wind_speed") orelse continue; const wind_kph: f32 = @floatCast(wind_ms.float * 3.6); + const wind_deg: f32 = if (details_obj.object.get("wind_from_direction")) |deg| + @floatCast(deg.float) + else + 0.0; if (current_date == null or !std.mem.eql(u8, current_date.?, date)) { // Save previous day if exists @@ -330,7 +335,9 @@ fn parseForecastDays(allocator: std.mem.Allocator, timeseries: []std.json.Value) .condition = try allocator.dupe(u8, symbolCodeToCondition(symbol_code)), .weather_code = symbolCodeToWeatherCode(symbol_code), .wind_kph = wind_kph, + .wind_deg = wind_deg, .precip_mm = precip, + .visibility_km = null, }); } @@ -353,7 +360,9 @@ fn parseForecastDays(allocator: std.mem.Allocator, timeseries: []std.json.Value) .condition = try allocator.dupe(u8, symbolCodeToCondition(symbol_code)), .weather_code = symbolCodeToWeatherCode(symbol_code), .wind_kph = wind_kph, + .wind_deg = wind_deg, .precip_mm = precip, + .visibility_km = null, }); } } diff --git a/src/weather/types.zig b/src/weather/types.zig index 59744ac..1ac79c2 100644 --- a/src/weather/types.zig +++ b/src/weather/types.zig @@ -116,6 +116,7 @@ pub const CurrentCondition = struct { wind_deg: f32, pressure_mb: f32, precip_mm: f32, + visibility_km: ?f32, pub fn tempFahrenheit(self: CurrentCondition) f32 { return celsiusToFahrenheit(self.temp_c); @@ -148,5 +149,7 @@ pub const HourlyForecast = struct { condition: []const u8, weather_code: WeatherCode, wind_kph: f32, + wind_deg: f32, precip_mm: f32, + visibility_km: ?f32, };