get one line output matching wttr.in

This commit is contained in:
Emil Lerch 2026-01-08 15:05:15 -08:00
parent 115af96c8a
commit 59ec75dbf6
Signed by: lobo
GPG key ID: A7B62D657EF764F8
4 changed files with 42 additions and 57 deletions

View file

@ -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");
}

View file

@ -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} <span style=\"color:{s}\">{c}{d:.0}({c}{d:.0})</span> {s}\n", .{ art[1], temp_color, sign, abs_temp, fl_sign, abs_fl, temp_unit });
try w.print("{s} {s} <span style=\"color:{s}\">{d:.0}</span> {s}\n", .{ art[2], degreeToArrow(current.wind_deg), wind_color, wind_speed, wind_unit });
try w.print("{s} {s} <span style=\"color:{s}\">{d:.0}</span> {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);

View file

@ -11,68 +11,52 @@ const Format = enum(u3) {
};
pub fn render(writer: *std.Io.Writer, data: types.WeatherData, format: Format, use_imperial: bool) !void {
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,
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" => {
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);
}

View file

@ -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));