AI: Fix issues of imperial units in various renderers
This commit is contained in:
parent
58d09b1f7e
commit
4746ad81b9
6 changed files with 264 additions and 48 deletions
|
|
@ -4,6 +4,7 @@ const Cache = @import("../cache/cache.zig").Cache;
|
||||||
const WeatherProvider = @import("../weather/provider.zig").WeatherProvider;
|
const WeatherProvider = @import("../weather/provider.zig").WeatherProvider;
|
||||||
const Resolver = @import("../location/resolver.zig").Resolver;
|
const Resolver = @import("../location/resolver.zig").Resolver;
|
||||||
const Location = @import("../location/resolver.zig").Location;
|
const Location = @import("../location/resolver.zig").Location;
|
||||||
|
const QueryParams = @import("query.zig").QueryParams;
|
||||||
const ansi = @import("../render/ansi.zig");
|
const ansi = @import("../render/ansi.zig");
|
||||||
const line = @import("../render/line.zig");
|
const line = @import("../render/line.zig");
|
||||||
const json = @import("../render/json.zig");
|
const json = @import("../render/json.zig");
|
||||||
|
|
@ -130,21 +131,27 @@ fn handleWeatherInternal(
|
||||||
};
|
};
|
||||||
defer weather.deinit();
|
defer weather.deinit();
|
||||||
|
|
||||||
const query = try req.query();
|
const query_string = req.url.query;
|
||||||
const format = query.get("format");
|
const params = try QueryParams.parse(allocator, query_string);
|
||||||
|
defer {
|
||||||
|
if (params.format) |f| allocator.free(f);
|
||||||
|
if (params.lang) |l| allocator.free(l);
|
||||||
|
}
|
||||||
|
|
||||||
const output = if (format) |fmt| blk: {
|
const use_imperial = if (params.units) |u| u == .uscs else false;
|
||||||
|
|
||||||
|
const output = if (params.format) |fmt| blk: {
|
||||||
if (std.mem.eql(u8, fmt, "j1")) {
|
if (std.mem.eql(u8, fmt, "j1")) {
|
||||||
res.content_type = .JSON;
|
res.content_type = .JSON;
|
||||||
break :blk try json.render(allocator, weather);
|
break :blk try json.render(allocator, weather);
|
||||||
} else if (std.mem.eql(u8, fmt, "v2")) {
|
} else if (std.mem.eql(u8, fmt, "v2")) {
|
||||||
break :blk try v2.render(allocator, weather);
|
break :blk try v2.render(allocator, weather, use_imperial);
|
||||||
} else if (std.mem.startsWith(u8, fmt, "%")) {
|
} else if (std.mem.startsWith(u8, fmt, "%")) {
|
||||||
break :blk try custom.render(allocator, weather, fmt);
|
break :blk try custom.render(allocator, weather, fmt, use_imperial);
|
||||||
} else {
|
} else {
|
||||||
break :blk try line.render(allocator, weather, fmt);
|
break :blk try line.render(allocator, weather, fmt, use_imperial);
|
||||||
}
|
}
|
||||||
} else try ansi.render(allocator, weather, .{});
|
} else try ansi.render(allocator, weather, .{ .use_imperial = use_imperial });
|
||||||
|
|
||||||
const ttl = 1000 + std.crypto.random.intRangeAtMost(u64, 0, 1000);
|
const ttl = 1000 + std.crypto.random.intRangeAtMost(u64, 0, 1000);
|
||||||
try opts.cache.put(cache_key, output, ttl);
|
try opts.cache.put(cache_key, output, ttl);
|
||||||
|
|
@ -161,12 +168,11 @@ fn generateCacheKey(
|
||||||
location: ?[]const u8,
|
location: ?[]const u8,
|
||||||
) ![]const u8 {
|
) ![]const u8 {
|
||||||
const loc = location orelse "";
|
const loc = location orelse "";
|
||||||
const query = try req.query();
|
const query_string = req.url.query;
|
||||||
const format = query.get("format") orelse "";
|
|
||||||
return std.fmt.allocPrint(allocator, "{s}:{s}:{s}", .{
|
return std.fmt.allocPrint(allocator, "{s}:{s}:{s}", .{
|
||||||
req.url.path,
|
req.url.path,
|
||||||
loc,
|
loc,
|
||||||
format,
|
query_string,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,23 @@ test "parse format parameter" {
|
||||||
try std.testing.expectEqualStrings("j1", params.format.?);
|
try std.testing.expectEqualStrings("j1", params.format.?);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "parse units with question mark" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
// Test with just "u" (no question mark in query string)
|
||||||
|
const params1 = try QueryParams.parse(allocator, "u");
|
||||||
|
try std.testing.expectEqual(QueryParams.Units.uscs, params1.units.?);
|
||||||
|
|
||||||
|
// Test with "u=" (empty value)
|
||||||
|
const params2 = try QueryParams.parse(allocator, "u=");
|
||||||
|
try std.testing.expectEqual(QueryParams.Units.uscs, params2.units.?);
|
||||||
|
|
||||||
|
// Test combined with other params
|
||||||
|
const params3 = try QueryParams.parse(allocator, "format=3&u");
|
||||||
|
defer if (params3.format) |f| allocator.free(f);
|
||||||
|
try std.testing.expectEqual(QueryParams.Units.uscs, params3.units.?);
|
||||||
|
}
|
||||||
|
|
||||||
test "parse units parameters" {
|
test "parse units parameters" {
|
||||||
const allocator = std.testing.allocator;
|
const allocator = std.testing.allocator;
|
||||||
const params_m = try QueryParams.parse(allocator, "m");
|
const params_m = try QueryParams.parse(allocator, "m");
|
||||||
|
|
@ -66,6 +83,9 @@ test "parse units parameters" {
|
||||||
|
|
||||||
const params_u = try QueryParams.parse(allocator, "u");
|
const params_u = try QueryParams.parse(allocator, "u");
|
||||||
try std.testing.expectEqual(QueryParams.Units.uscs, params_u.units.?);
|
try std.testing.expectEqual(QueryParams.Units.uscs, params_u.units.?);
|
||||||
|
|
||||||
|
const params_u_query = try QueryParams.parse(allocator, "u=");
|
||||||
|
try std.testing.expectEqual(QueryParams.Units.uscs, params_u_query.units.?);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parse multiple parameters" {
|
test "parse multiple parameters" {
|
||||||
|
|
|
||||||
|
|
@ -27,3 +27,27 @@ pub fn render(allocator: std.mem.Allocator, data: types.WeatherData, options: Re
|
||||||
|
|
||||||
return output.toOwnedSlice(allocator);
|
return output.toOwnedSlice(allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "render with imperial units" {
|
||||||
|
const data = types.WeatherData{
|
||||||
|
.location = "Chicago",
|
||||||
|
.current = .{
|
||||||
|
.temp_c = 10.0,
|
||||||
|
.temp_f = 50.0,
|
||||||
|
.condition = "Clear",
|
||||||
|
.weather_code = 113,
|
||||||
|
.humidity = 60,
|
||||||
|
.wind_kph = 16.0,
|
||||||
|
.wind_dir = "N",
|
||||||
|
.pressure_mb = 1013.0,
|
||||||
|
.precip_mm = 0.0,
|
||||||
|
},
|
||||||
|
.forecast = &.{},
|
||||||
|
.allocator = std.testing.allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = try render(std.testing.allocator, data, .{ .use_imperial = true });
|
||||||
|
defer std.testing.allocator.free(output);
|
||||||
|
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, output, "50.0°F") != null);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ fn getWeatherIcon(code: u16) []const u8 {
|
||||||
return weather_icons[idx];
|
return weather_icons[idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, format: []const u8) ![]const u8 {
|
pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, format: []const u8, use_imperial: bool) ![]const u8 {
|
||||||
var output: std.ArrayList(u8) = .empty;
|
var output: std.ArrayList(u8) = .empty;
|
||||||
errdefer output.deinit(allocator);
|
errdefer output.deinit(allocator);
|
||||||
const writer = output.writer(allocator);
|
const writer = output.writer(allocator);
|
||||||
|
|
@ -26,12 +26,32 @@ pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, format:
|
||||||
'c' => try writer.print("{s}", .{getWeatherIcon(weather.current.weather_code)}),
|
'c' => try writer.print("{s}", .{getWeatherIcon(weather.current.weather_code)}),
|
||||||
'C' => try writer.print("{s}", .{weather.current.condition}),
|
'C' => try writer.print("{s}", .{weather.current.condition}),
|
||||||
'h' => try writer.print("{d}%", .{weather.current.humidity}),
|
'h' => try writer.print("{d}%", .{weather.current.humidity}),
|
||||||
't' => try writer.print("{d:.0}°C", .{weather.current.temp_c}),
|
't' => {
|
||||||
'f' => try writer.print("{d:.0}°C", .{weather.current.temp_c}), // Feels like (same as temp for now)
|
const temp = if (use_imperial) weather.current.temp_f else weather.current.temp_c;
|
||||||
'w' => try writer.print("{d:.0} km/h {s}", .{ weather.current.wind_kph, weather.current.wind_dir }),
|
const unit = if (use_imperial) "°F" else "°C";
|
||||||
|
try writer.print("{d:.0}{s}", .{ temp, unit });
|
||||||
|
},
|
||||||
|
'f' => {
|
||||||
|
const temp = if (use_imperial) weather.current.temp_f else weather.current.temp_c;
|
||||||
|
const unit = if (use_imperial) "°F" else "°C";
|
||||||
|
try writer.print("{d:.0}{s}", .{ temp, unit });
|
||||||
|
},
|
||||||
|
'w' => {
|
||||||
|
const wind = if (use_imperial) weather.current.wind_kph * 0.621371 else weather.current.wind_kph;
|
||||||
|
const unit = if (use_imperial) "mph" else "km/h";
|
||||||
|
try writer.print("{d:.0} {s} {s}", .{ wind, unit, weather.current.wind_dir });
|
||||||
|
},
|
||||||
'l' => try writer.print("{s}", .{weather.location}),
|
'l' => try writer.print("{s}", .{weather.location}),
|
||||||
'p' => try writer.print("{d:.1} mm", .{weather.current.precip_mm}),
|
'p' => {
|
||||||
'P' => try writer.print("{d:.0} hPa", .{weather.current.pressure_mb}),
|
const precip = if (use_imperial) weather.current.precip_mm * 0.0393701 else weather.current.precip_mm;
|
||||||
|
const unit = if (use_imperial) "in" else "mm";
|
||||||
|
try writer.print("{d:.1} {s}", .{ precip, unit });
|
||||||
|
},
|
||||||
|
'P' => {
|
||||||
|
const pressure = if (use_imperial) weather.current.pressure_mb * 0.02953 else weather.current.pressure_mb;
|
||||||
|
const unit = if (use_imperial) "inHg" else "hPa";
|
||||||
|
try writer.print("{d:.2} {s}", .{ pressure, unit });
|
||||||
|
},
|
||||||
'm' => try writer.print("🌕", .{}), // Moon phase placeholder
|
'm' => try writer.print("🌕", .{}), // Moon phase placeholder
|
||||||
'M' => try writer.print("15", .{}), // Moon day placeholder
|
'M' => try writer.print("15", .{}), // Moon day placeholder
|
||||||
'o' => try writer.print("0%", .{}), // Probability of precipitation placeholder
|
'o' => try writer.print("0%", .{}), // Probability of precipitation placeholder
|
||||||
|
|
@ -76,7 +96,7 @@ test "render custom format with location and temp" {
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(allocator, weather, "%l: %c %t");
|
const output = try render(allocator, weather, "%l: %c %t", false);
|
||||||
defer allocator.free(output);
|
defer allocator.free(output);
|
||||||
|
|
||||||
try std.testing.expect(std.mem.indexOf(u8, output, "London") != null);
|
try std.testing.expect(std.mem.indexOf(u8, output, "London") != null);
|
||||||
|
|
@ -103,7 +123,7 @@ test "render custom format with newline" {
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(allocator, weather, "%l%n%C");
|
const output = try render(allocator, weather, "%l%n%C", false);
|
||||||
defer allocator.free(output);
|
defer allocator.free(output);
|
||||||
|
|
||||||
try std.testing.expect(std.mem.indexOf(u8, output, "Paris\nClear") != null);
|
try std.testing.expect(std.mem.indexOf(u8, output, "Paris\nClear") != null);
|
||||||
|
|
@ -129,9 +149,37 @@ test "render custom format with humidity and pressure" {
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(allocator, weather, "Humidity: %h, Pressure: %P");
|
const output = try render(allocator, weather, "Humidity: %h, Pressure: %P", false);
|
||||||
defer allocator.free(output);
|
defer allocator.free(output);
|
||||||
|
|
||||||
try std.testing.expect(std.mem.indexOf(u8, output, "85%") != null);
|
try std.testing.expect(std.mem.indexOf(u8, output, "85%") != null);
|
||||||
try std.testing.expect(std.mem.indexOf(u8, output, "1012") != null);
|
try std.testing.expect(std.mem.indexOf(u8, output, "1012") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "render custom format with imperial units" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
const weather = types.WeatherData{
|
||||||
|
.location = "NYC",
|
||||||
|
.current = .{
|
||||||
|
.temp_c = 10.0,
|
||||||
|
.temp_f = 50.0,
|
||||||
|
.condition = "Clear",
|
||||||
|
.weather_code = 113,
|
||||||
|
.humidity = 60,
|
||||||
|
.wind_kph = 16.0,
|
||||||
|
.wind_dir = "N",
|
||||||
|
.pressure_mb = 1013.0,
|
||||||
|
.precip_mm = 2.5,
|
||||||
|
},
|
||||||
|
.forecast = &[_]types.ForecastDay{},
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = try render(allocator, weather, "%t %w %p", true);
|
||||||
|
defer allocator.free(output);
|
||||||
|
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, output, "50°F") != null);
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, output, "mph") != null);
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, output, "in") != null);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,72 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const types = @import("../weather/types.zig");
|
const types = @import("../weather/types.zig");
|
||||||
|
|
||||||
pub fn render(allocator: std.mem.Allocator, data: types.WeatherData, format: []const u8) ![]const u8 {
|
pub fn render(allocator: std.mem.Allocator, data: types.WeatherData, format: []const u8, use_imperial: bool) ![]const u8 {
|
||||||
if (std.mem.eql(u8, format, "1")) {
|
if (std.mem.eql(u8, format, "1")) {
|
||||||
return std.fmt.allocPrint(allocator, "{s}: {s} {d:.0}°C", .{
|
const temp = if (use_imperial) data.current.temp_f else data.current.temp_c;
|
||||||
|
const unit = if (use_imperial) "°F" else "°C";
|
||||||
|
return std.fmt.allocPrint(allocator, "{s}: {s} {d:.0}{s}", .{
|
||||||
data.location,
|
data.location,
|
||||||
getConditionEmoji(data.current.weather_code),
|
getConditionEmoji(data.current.weather_code),
|
||||||
data.current.temp_c,
|
temp,
|
||||||
|
unit,
|
||||||
});
|
});
|
||||||
} else if (std.mem.eql(u8, format, "2")) {
|
} else if (std.mem.eql(u8, format, "2")) {
|
||||||
return std.fmt.allocPrint(allocator, "{s}: {s} {d:.0}°C {s}{s}{d:.0}km/h", .{
|
const temp = if (use_imperial) data.current.temp_f else data.current.temp_c;
|
||||||
|
const unit = if (use_imperial) "°F" else "°C";
|
||||||
|
const wind = if (use_imperial) data.current.wind_kph * 0.621371 else data.current.wind_kph;
|
||||||
|
const wind_unit = if (use_imperial) "mph" else "km/h";
|
||||||
|
return std.fmt.allocPrint(allocator, "{s}: {s} {d:.0}{s} {s}{s}{d:.0}{s}", .{
|
||||||
data.location,
|
data.location,
|
||||||
getConditionEmoji(data.current.weather_code),
|
getConditionEmoji(data.current.weather_code),
|
||||||
data.current.temp_c,
|
temp,
|
||||||
|
unit,
|
||||||
"🌬️",
|
"🌬️",
|
||||||
data.current.wind_dir,
|
data.current.wind_dir,
|
||||||
data.current.wind_kph,
|
wind,
|
||||||
|
wind_unit,
|
||||||
});
|
});
|
||||||
} else if (std.mem.eql(u8, format, "3")) {
|
} else if (std.mem.eql(u8, format, "3")) {
|
||||||
return std.fmt.allocPrint(allocator, "{s}: {s} {d:.0}°C {s}{s}{d:.0}km/h {s}{d}%%", .{
|
const temp = if (use_imperial) data.current.temp_f else data.current.temp_c;
|
||||||
|
const unit = if (use_imperial) "°F" else "°C";
|
||||||
|
const wind = if (use_imperial) data.current.wind_kph * 0.621371 else data.current.wind_kph;
|
||||||
|
const wind_unit = if (use_imperial) "mph" else "km/h";
|
||||||
|
return std.fmt.allocPrint(allocator, "{s}: {s} {d:.0}{s} {s}{s}{d:.0}{s} {s}{d}%%", .{
|
||||||
data.location,
|
data.location,
|
||||||
getConditionEmoji(data.current.weather_code),
|
getConditionEmoji(data.current.weather_code),
|
||||||
data.current.temp_c,
|
temp,
|
||||||
|
unit,
|
||||||
"🌬️",
|
"🌬️",
|
||||||
data.current.wind_dir,
|
data.current.wind_dir,
|
||||||
data.current.wind_kph,
|
wind,
|
||||||
|
wind_unit,
|
||||||
"💧",
|
"💧",
|
||||||
data.current.humidity,
|
data.current.humidity,
|
||||||
});
|
});
|
||||||
} else if (std.mem.eql(u8, format, "4")) {
|
} else if (std.mem.eql(u8, format, "4")) {
|
||||||
return std.fmt.allocPrint(allocator, "{s}: {s} {d:.0}°C {s}{s}{d:.0}km/h {s}{d}%% {s}", .{
|
const temp = if (use_imperial) data.current.temp_f else data.current.temp_c;
|
||||||
|
const unit = if (use_imperial) "°F" else "°C";
|
||||||
|
const wind = if (use_imperial) data.current.wind_kph * 0.621371 else data.current.wind_kph;
|
||||||
|
const wind_unit = if (use_imperial) "mph" else "km/h";
|
||||||
|
return std.fmt.allocPrint(allocator, "{s}: {s} {d:.0}{s} {s}{s}{d:.0}{s} {s}{d}%% {s}", .{
|
||||||
data.location,
|
data.location,
|
||||||
getConditionEmoji(data.current.weather_code),
|
getConditionEmoji(data.current.weather_code),
|
||||||
data.current.temp_c,
|
temp,
|
||||||
|
unit,
|
||||||
"🌬️",
|
"🌬️",
|
||||||
data.current.wind_dir,
|
data.current.wind_dir,
|
||||||
data.current.wind_kph,
|
wind,
|
||||||
|
wind_unit,
|
||||||
"💧",
|
"💧",
|
||||||
data.current.humidity,
|
data.current.humidity,
|
||||||
"☀️",
|
"☀️",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return renderCustom(allocator, data, format);
|
return renderCustom(allocator, data, format, use_imperial);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn renderCustom(allocator: std.mem.Allocator, data: types.WeatherData, format: []const u8) ![]const u8 {
|
fn renderCustom(allocator: std.mem.Allocator, data: types.WeatherData, format: []const u8, use_imperial: bool) ![]const u8 {
|
||||||
var output: std.ArrayList(u8) = .empty;
|
var output: std.ArrayList(u8) = .empty;
|
||||||
errdefer output.deinit(allocator);
|
errdefer output.deinit(allocator);
|
||||||
|
|
||||||
|
|
@ -57,12 +78,28 @@ fn renderCustom(allocator: std.mem.Allocator, data: types.WeatherData, format: [
|
||||||
'c' => try output.appendSlice(allocator, getConditionEmoji(data.current.weather_code)),
|
'c' => try output.appendSlice(allocator, getConditionEmoji(data.current.weather_code)),
|
||||||
'C' => try output.appendSlice(allocator, data.current.condition),
|
'C' => try output.appendSlice(allocator, data.current.condition),
|
||||||
'h' => try output.writer(allocator).print("{d}", .{data.current.humidity}),
|
'h' => try output.writer(allocator).print("{d}", .{data.current.humidity}),
|
||||||
't' => try output.writer(allocator).print("{d:.0}", .{data.current.temp_c}),
|
't' => {
|
||||||
'f' => try output.writer(allocator).print("{d:.0}", .{data.current.temp_c}),
|
const temp = if (use_imperial) data.current.temp_f else data.current.temp_c;
|
||||||
'w' => try output.writer(allocator).print("{s}{d:.0}km/h", .{ data.current.wind_dir, data.current.wind_kph }),
|
try output.writer(allocator).print("{d:.0}", .{temp});
|
||||||
|
},
|
||||||
|
'f' => {
|
||||||
|
const temp = if (use_imperial) data.current.temp_f else data.current.temp_c;
|
||||||
|
try output.writer(allocator).print("{d:.0}", .{temp});
|
||||||
|
},
|
||||||
|
'w' => {
|
||||||
|
const wind = if (use_imperial) data.current.wind_kph * 0.621371 else data.current.wind_kph;
|
||||||
|
const wind_unit = if (use_imperial) "mph" else "km/h";
|
||||||
|
try output.writer(allocator).print("{s}{d:.0}{s}", .{ data.current.wind_dir, wind, wind_unit });
|
||||||
|
},
|
||||||
'l' => try output.appendSlice(allocator, data.location),
|
'l' => try output.appendSlice(allocator, data.location),
|
||||||
'p' => try output.writer(allocator).print("{d:.1}", .{data.current.precip_mm}),
|
'p' => {
|
||||||
'P' => try output.writer(allocator).print("{d:.0}", .{data.current.pressure_mb}),
|
const precip = if (use_imperial) data.current.precip_mm * 0.0393701 else data.current.precip_mm;
|
||||||
|
try output.writer(allocator).print("{d:.1}", .{precip});
|
||||||
|
},
|
||||||
|
'P' => {
|
||||||
|
const pressure = if (use_imperial) data.current.pressure_mb * 0.02953 else data.current.pressure_mb;
|
||||||
|
try output.writer(allocator).print("{d:.0}", .{pressure});
|
||||||
|
},
|
||||||
'%' => try output.append(allocator, '%'),
|
'%' => try output.append(allocator, '%'),
|
||||||
else => {
|
else => {
|
||||||
try output.append(allocator, '%');
|
try output.append(allocator, '%');
|
||||||
|
|
@ -112,7 +149,7 @@ test "format 1" {
|
||||||
.allocator = std.testing.allocator,
|
.allocator = std.testing.allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(std.testing.allocator, data, "1");
|
const output = try render(std.testing.allocator, data, "1", false);
|
||||||
defer std.testing.allocator.free(output);
|
defer std.testing.allocator.free(output);
|
||||||
|
|
||||||
try std.testing.expectEqualStrings("London: ☀️ 15°C", output);
|
try std.testing.expectEqualStrings("London: ☀️ 15°C", output);
|
||||||
|
|
@ -136,8 +173,32 @@ test "custom format" {
|
||||||
.allocator = std.testing.allocator,
|
.allocator = std.testing.allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(std.testing.allocator, data, "%l: %c %t°C");
|
const output = try render(std.testing.allocator, data, "%l: %c %t°C", false);
|
||||||
defer std.testing.allocator.free(output);
|
defer std.testing.allocator.free(output);
|
||||||
|
|
||||||
try std.testing.expectEqualStrings("London: ☀️ 15°C", output);
|
try std.testing.expectEqualStrings("London: ☀️ 15°C", output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "format 2 with imperial units" {
|
||||||
|
const data = types.WeatherData{
|
||||||
|
.location = "Portland",
|
||||||
|
.current = .{
|
||||||
|
.temp_c = 10.0,
|
||||||
|
.temp_f = 50.0,
|
||||||
|
.condition = "Cloudy",
|
||||||
|
.weather_code = 119,
|
||||||
|
.humidity = 70,
|
||||||
|
.wind_kph = 20.0,
|
||||||
|
.wind_dir = "SE",
|
||||||
|
.pressure_mb = 1013.0,
|
||||||
|
.precip_mm = 0.0,
|
||||||
|
},
|
||||||
|
.forecast = &.{},
|
||||||
|
.allocator = std.testing.allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = try render(std.testing.allocator, data, "2", true);
|
||||||
|
defer std.testing.allocator.free(output);
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings("Portland: ☁️ 50°F 🌬️SE12mph", output);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const types = @import("../weather/types.zig");
|
const types = @import("../weather/types.zig");
|
||||||
|
|
||||||
pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData) ![]const u8 {
|
pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, use_imperial: bool) ![]const u8 {
|
||||||
var output: std.ArrayList(u8) = .empty;
|
var output: std.ArrayList(u8) = .empty;
|
||||||
errdefer output.deinit(allocator);
|
errdefer output.deinit(allocator);
|
||||||
const writer = output.writer(allocator);
|
const writer = output.writer(allocator);
|
||||||
|
|
@ -13,15 +13,34 @@ pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData) ![]const
|
||||||
try writer.print(" Current conditions\n", .{});
|
try writer.print(" Current conditions\n", .{});
|
||||||
try writer.print(" {s}\n", .{weather.current.condition});
|
try writer.print(" {s}\n", .{weather.current.condition});
|
||||||
try writer.writeAll(" 🌡️ ");
|
try writer.writeAll(" 🌡️ ");
|
||||||
try writer.print("{d:.1}°C ({d:.1}°F)\n", .{ weather.current.temp_c, weather.current.temp_f });
|
if (use_imperial) {
|
||||||
|
try writer.print("{d:.1}°F\n", .{weather.current.temp_f});
|
||||||
|
} else {
|
||||||
|
try writer.print("{d:.1}°C ({d:.1}°F)\n", .{ weather.current.temp_c, weather.current.temp_f });
|
||||||
|
}
|
||||||
try writer.writeAll(" 💧 ");
|
try writer.writeAll(" 💧 ");
|
||||||
try writer.print("{d}%\n", .{weather.current.humidity});
|
try writer.print("{d}%\n", .{weather.current.humidity});
|
||||||
try writer.writeAll(" 🌬️ ");
|
try writer.writeAll(" 🌬️ ");
|
||||||
try writer.print("{d:.1} km/h {s}\n", .{ weather.current.wind_kph, weather.current.wind_dir });
|
if (use_imperial) {
|
||||||
|
const wind_mph = weather.current.wind_kph * 0.621371;
|
||||||
|
try writer.print("{d:.1} mph {s}\n", .{ wind_mph, weather.current.wind_dir });
|
||||||
|
} else {
|
||||||
|
try writer.print("{d:.1} km/h {s}\n", .{ weather.current.wind_kph, weather.current.wind_dir });
|
||||||
|
}
|
||||||
try writer.writeAll(" 🔽 ");
|
try writer.writeAll(" 🔽 ");
|
||||||
try writer.print("{d:.1} hPa\n", .{weather.current.pressure_mb});
|
if (use_imperial) {
|
||||||
|
const pressure_inhg = weather.current.pressure_mb * 0.02953;
|
||||||
|
try writer.print("{d:.2} inHg\n", .{pressure_inhg});
|
||||||
|
} else {
|
||||||
|
try writer.print("{d:.1} hPa\n", .{weather.current.pressure_mb});
|
||||||
|
}
|
||||||
try writer.writeAll(" 💦 ");
|
try writer.writeAll(" 💦 ");
|
||||||
try writer.print("{d:.1} mm\n\n", .{weather.current.precip_mm});
|
if (use_imperial) {
|
||||||
|
const precip_in = weather.current.precip_mm * 0.0393701;
|
||||||
|
try writer.print("{d:.2} in\n\n", .{precip_in});
|
||||||
|
} else {
|
||||||
|
try writer.print("{d:.1} mm\n\n", .{weather.current.precip_mm});
|
||||||
|
}
|
||||||
|
|
||||||
// Forecast
|
// Forecast
|
||||||
if (weather.forecast.len > 0) {
|
if (weather.forecast.len > 0) {
|
||||||
|
|
@ -29,9 +48,19 @@ pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData) ![]const
|
||||||
for (weather.forecast) |day| {
|
for (weather.forecast) |day| {
|
||||||
try writer.print(" {s}: {s}\n", .{ day.date, day.condition });
|
try writer.print(" {s}: {s}\n", .{ day.date, day.condition });
|
||||||
try writer.writeAll(" ↑ ");
|
try writer.writeAll(" ↑ ");
|
||||||
try writer.print("{d:.1}°C ", .{day.max_temp_c});
|
if (use_imperial) {
|
||||||
|
const max_f = day.max_temp_c * 9.0 / 5.0 + 32.0;
|
||||||
|
try writer.print("{d:.1}°F ", .{max_f});
|
||||||
|
} else {
|
||||||
|
try writer.print("{d:.1}°C ", .{day.max_temp_c});
|
||||||
|
}
|
||||||
try writer.writeAll("↓ ");
|
try writer.writeAll("↓ ");
|
||||||
try writer.print("{d:.1}°C\n", .{day.min_temp_c});
|
if (use_imperial) {
|
||||||
|
const min_f = day.min_temp_c * 9.0 / 5.0 + 32.0;
|
||||||
|
try writer.print("{d:.1}°F\n", .{min_f});
|
||||||
|
} else {
|
||||||
|
try writer.print("{d:.1}°C\n", .{day.min_temp_c});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,7 +87,7 @@ test "render v2 format" {
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(allocator, weather);
|
const output = try render(allocator, weather, false);
|
||||||
defer allocator.free(output);
|
defer allocator.free(output);
|
||||||
|
|
||||||
try std.testing.expect(output.len > 0);
|
try std.testing.expect(output.len > 0);
|
||||||
|
|
@ -66,3 +95,31 @@ test "render v2 format" {
|
||||||
try std.testing.expect(std.mem.indexOf(u8, output, "Current conditions") != null);
|
try std.testing.expect(std.mem.indexOf(u8, output, "Current conditions") != null);
|
||||||
try std.testing.expect(std.mem.indexOf(u8, output, "12.0°C") != null);
|
try std.testing.expect(std.mem.indexOf(u8, output, "12.0°C") != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "render v2 format with imperial units" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
const weather = types.WeatherData{
|
||||||
|
.location = "Boston",
|
||||||
|
.current = .{
|
||||||
|
.temp_c = 10.0,
|
||||||
|
.temp_f = 50.0,
|
||||||
|
.condition = "Clear",
|
||||||
|
.weather_code = 113,
|
||||||
|
.humidity = 65,
|
||||||
|
.wind_kph = 16.0,
|
||||||
|
.wind_dir = "N",
|
||||||
|
.pressure_mb = 1013.0,
|
||||||
|
.precip_mm = 0.0,
|
||||||
|
},
|
||||||
|
.forecast = &[_]types.ForecastDay{},
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = try render(allocator, weather, true);
|
||||||
|
defer allocator.free(output);
|
||||||
|
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, output, "50.0°F") != null);
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, output, "mph") != null);
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, output, "inHg") != null);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue