From 59ec75dbf62bc9fb252876c2b6de24f9d4d34bfd Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Thu, 8 Jan 2026 15:05:15 -0800 Subject: [PATCH] get one line output matching wttr.in --- src/http/handler.zig | 6 ++-- src/render/Formatted.zig | 16 +++------ src/render/Line.zig | 70 ++++++++++++++++------------------------ src/render/utils.zig | 7 ++++ 4 files changed, 42 insertions(+), 57 deletions(-) diff --git a/src/http/handler.zig b/src/http/handler.zig index 1e3df08..41f6528 100644 --- a/src/http/handler.zig +++ b/src/http/handler.zig @@ -425,7 +425,7 @@ test "handler: format line 1" { try handleWeather(&harness.opts, ht.req, ht.res, client_ip); try ht.expectStatus(200); - try ht.expectBody("Test: ☀️ 20°C"); + try ht.expectBody("☀️ +20°C"); } test "handler: format line 2" { @@ -446,7 +446,7 @@ test "handler: format line 2" { try handleWeather(&harness.opts, ht.req, ht.res, client_ip); try ht.expectStatus(200); - try ht.expectBody("Test: ☀️ 20°C 🌬️N5km/h"); + try ht.expectBody("☀️ 🌡️+20°C 🌬️↓5km/h"); } test "handler: format line 3" { @@ -467,5 +467,5 @@ test "handler: format line 3" { try handleWeather(&harness.opts, ht.req, ht.res, client_ip); try ht.expectStatus(200); - try ht.expectBody("Test: ☀️ 20°C 🌬️N5km/h 💧50%%"); + try ht.expectBody("Test: ☀️ +20°C"); } diff --git a/src/render/Formatted.zig b/src/render/Formatted.zig index 892aa39..6fc9132 100644 --- a/src/render/Formatted.zig +++ b/src/render/Formatted.zig @@ -1,6 +1,7 @@ const std = @import("std"); const types = @import("../weather/types.zig"); const zeit = @import("zeit"); +const utils = @import("utils.zig"); /// Select 4 hours representing morning (6am), noon (12pm), evening (6pm), night (12am) in LOCAL time /// Hours in the hourly forecast are assumed to be all on the same day, in local time @@ -46,13 +47,6 @@ fn selectHourlyForecasts(all_hours: []const types.HourlyForecast, buf: []?types. return selected.items; } -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, ansi, @@ -146,7 +140,7 @@ fn renderCurrent(w: *std.Io.Writer, current: types.CurrentCondition, options: Re .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], degreeToArrow(current.wind_deg), wind_speed, wind_unit }); + try w.print("{s} {s} {d:.0} {s}\n", .{ art[2], utils.degreeToArrow(current.wind_deg), wind_speed, wind_unit }); if (current.visibility_km) |_| { const visibility = if (options.use_imperial) current.visiblityMph().? else current.visibility_km.?; const vis_unit = if (options.use_imperial) "mi" else "km"; @@ -163,7 +157,7 @@ fn renderCurrent(w: *std.Io.Writer, current: types.CurrentCondition, options: Re try w.print("{s} {s}\n", .{ art[0], current.condition }); try w.print("{s} \x1b[38;5;{d}m{c}{d:.0}({c}{d:.0}){s} {s}\n", .{ art[1], temp_color_code, sign, abs_temp, fl_sign, abs_fl, reset, temp_unit }); - try w.print("{s} {s} \x1b[38;5;{d}m{d:.0}{s} {s}\n", .{ art[2], degreeToArrow(current.wind_deg), wind_color_code, wind_speed, reset, wind_unit }); + try w.print("{s} {s} \x1b[38;5;{d}m{d:.0}{s} {s}\n", .{ art[2], utils.degreeToArrow(current.wind_deg), wind_color_code, wind_speed, reset, wind_unit }); if (current.visibility_km) |_| { const visibility = if (options.use_imperial) current.visiblityMph().? else current.visibility_km.?; const vis_unit = if (options.use_imperial) "mi" else "km"; @@ -179,7 +173,7 @@ fn renderCurrent(w: *std.Io.Writer, current: types.CurrentCondition, options: Re try w.print("{s} {s}\n", .{ art[0], current.condition }); try w.print("{s} {c}{d:.0}({c}{d:.0}) {s}\n", .{ art[1], temp_color, sign, abs_temp, fl_sign, abs_fl, temp_unit }); - try w.print("{s} {s} {d:.0} {s}\n", .{ art[2], degreeToArrow(current.wind_deg), wind_color, wind_speed, wind_unit }); + try w.print("{s} {s} {d:.0} {s}\n", .{ art[2], utils.degreeToArrow(current.wind_deg), wind_color, wind_speed, wind_unit }); if (current.visibility_km) |_| { const visibility = if (options.use_imperial) current.visiblityMph().? else current.visibility_km.?; const vis_unit = if (options.use_imperial) "mi" else "km"; @@ -329,7 +323,7 @@ fn renderHourlyCell(w: *std.Io.Writer, hour: types.HourlyForecast, line: usize, .wind => { const wind_speed = if (options.use_imperial) hour.windMph() else hour.wind_kph; const wind_unit = if (options.use_imperial) "mph" else "km/h"; - const arrow = degreeToArrow(hour.wind_deg); + const arrow = utils.degreeToArrow(hour.wind_deg); switch (options.format) { .ansi => { const color = windColor(hour.wind_kph); diff --git a/src/render/Line.zig b/src/render/Line.zig index 13dda6a..dbb3786 100644 --- a/src/render/Line.zig +++ b/src/render/Line.zig @@ -11,68 +11,52 @@ const Format = enum(u3) { }; pub fn render(writer: *std.Io.Writer, data: types.WeatherData, format: Format, use_imperial: bool) !void { + const temp = if (use_imperial) data.current.tempFahrenheit() else data.current.temp_c; + const unit = if (use_imperial) "°F" else "°C"; + const sign: []const u8 = if (temp >= 0) "+" else if (temp < 0) "-" else ""; + const abs_temp = @abs(temp); + const wind = if (use_imperial) data.current.windMph() else data.current.wind_kph; + const wind_unit = if (use_imperial) "mph" else "km/h"; + switch (format) { .@"1" => { - const temp = if (use_imperial) data.current.tempFahrenheit() else data.current.temp_c; - const unit = if (use_imperial) "°F" else "°C"; - try writer.print("{s}: {s} {d:.0}{s}", .{ - data.location, + try writer.print("{s} {s}{d:.0}{s}", .{ emoji.getWeatherEmoji(data.current.weather_code), - temp, + sign, + abs_temp, unit, }); }, .@"2" => { - const temp = if (use_imperial) data.current.tempFahrenheit() else data.current.temp_c; - const unit = if (use_imperial) "°F" else "°C"; - const wind = if (use_imperial) data.current.windMph() else data.current.wind_kph; - const wind_unit = if (use_imperial) "mph" else "km/h"; - try writer.print("{s}: {s} {d:.0}{s} {s}{s}{d:.0}{s}", .{ - data.location, + try writer.print("{s} 🌡️{s}{d:.0}{s} 🌬️{s}{d:.0}{s}", .{ emoji.getWeatherEmoji(data.current.weather_code), - temp, + sign, + abs_temp, unit, - "🌬️", - utils.degreeToDirection(data.current.wind_deg), + utils.degreeToArrow(data.current.wind_deg), wind, wind_unit, }); }, .@"3" => { - const temp = if (use_imperial) data.current.tempFahrenheit() else data.current.temp_c; - const unit = if (use_imperial) "°F" else "°C"; - const wind = if (use_imperial) data.current.windMph() else data.current.wind_kph; - const wind_unit = if (use_imperial) "mph" else "km/h"; - try writer.print("{s}: {s} {d:.0}{s} {s}{s}{d:.0}{s} {s}{d}%%", .{ + try writer.print("{s}: {s} {s}{d:.0}{s}", .{ data.location, emoji.getWeatherEmoji(data.current.weather_code), - temp, + sign, + abs_temp, unit, - "🌬️", - utils.degreeToDirection(data.current.wind_deg), - wind, - wind_unit, - "💧", - data.current.humidity, }); }, .@"4" => { - const temp = if (use_imperial) data.current.tempFahrenheit() else data.current.temp_c; - const unit = if (use_imperial) "°F" else "°C"; - const wind = if (use_imperial) data.current.windMph() else data.current.wind_kph; - const wind_unit = if (use_imperial) "mph" else "km/h"; - try writer.print("{s}: {s} {d:.0}{s} {s}{s}{d:.0}{s} {s}{d}%% {s}", .{ + try writer.print("{s}: {s} 🌡️{s}{d:.0}{s} 🌬️{s}{d:.0}{s}", .{ data.location, emoji.getWeatherEmoji(data.current.weather_code), - temp, + sign, + abs_temp, unit, - "🌬️", - utils.degreeToDirection(data.current.wind_deg), + utils.degreeToArrow(data.current.wind_deg), wind, wind_unit, - "💧", - data.current.humidity, - "☀️", }); }, } @@ -105,7 +89,7 @@ test "format 1" { const output = output_buf[0..writer.end]; - try std.testing.expectEqualStrings("London: ☀️ 15°C", output); + try std.testing.expectEqualStrings("☀️ +15°C", output); } test "format 2 with imperial units" { @@ -116,7 +100,7 @@ test "format 2 with imperial units" { const output = output_buf[0..writer.end]; - try std.testing.expectEqualStrings("London: ☀️ 59°F 🌬️N6mph", output); + try std.testing.expectEqualStrings("☀️ 🌡️+59°F 🌬️↓6mph", output); } test "format 3 with metric units" { @@ -127,7 +111,7 @@ test "format 3 with metric units" { const output = output_buf[0..writer.end]; - try std.testing.expectEqualStrings("London: ☀️ 15°C 🌬️N10km/h 💧65%%", output); + try std.testing.expectEqualStrings("London: ☀️ +15°C", output); } test "format 3 with imperial units" { @@ -138,7 +122,7 @@ test "format 3 with imperial units" { const output = output_buf[0..writer.end]; - try std.testing.expectEqualStrings("London: ☀️ 59°F 🌬️N6mph 💧65%%", output); + try std.testing.expectEqualStrings("London: ☀️ +59°F", output); } test "format 4 with metric units" { @@ -149,7 +133,7 @@ test "format 4 with metric units" { const output = output_buf[0..writer.end]; - try std.testing.expectEqualStrings("London: ☀️ 15°C 🌬️N10km/h 💧65%% ☀️", output); + try std.testing.expectEqualStrings("London: ☀️ 🌡️+15°C 🌬️↓10km/h", output); } test "format 4 with imperial units" { @@ -160,5 +144,5 @@ test "format 4 with imperial units" { const output = output_buf[0..writer.end]; - try std.testing.expectEqualStrings("London: ☀️ 59°F 🌬️N6mph 💧65%% ☀️", output); + try std.testing.expectEqualStrings("London: ☀️ 🌡️+59°F 🌬️↓6mph", output); } diff --git a/src/render/utils.zig b/src/render/utils.zig index 4df44ad..3398791 100644 --- a/src/render/utils.zig +++ b/src/render/utils.zig @@ -7,6 +7,13 @@ pub fn degreeToDirection(deg: f32) []const u8 { return directions[@min(idx, 7)]; } +pub 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)]; +} + test "degreeToDirection" { try std.testing.expectEqualStrings("N", degreeToDirection(0)); try std.testing.expectEqualStrings("NE", degreeToDirection(45));