more AI stuff - not reviewed
This commit is contained in:
parent
bb25def875
commit
68dbe7d7dd
8 changed files with 329 additions and 37 deletions
|
|
@ -4,6 +4,9 @@ const Cache = @import("../cache/cache.zig").Cache;
|
||||||
const WeatherProvider = @import("../weather/provider.zig").WeatherProvider;
|
const WeatherProvider = @import("../weather/provider.zig").WeatherProvider;
|
||||||
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 v2 = @import("../render/v2.zig");
|
||||||
|
const custom = @import("../render/custom.zig");
|
||||||
const help = @import("help.zig");
|
const help = @import("help.zig");
|
||||||
|
|
||||||
pub const HandleWeatherOptions = struct {
|
pub const HandleWeatherOptions = struct {
|
||||||
|
|
@ -77,15 +80,26 @@ fn handleWeatherInternal(
|
||||||
|
|
||||||
const query = try req.query();
|
const query = try req.query();
|
||||||
const format = query.get("format");
|
const format = query.get("format");
|
||||||
const output = if (format) |fmt|
|
|
||||||
try line.render(allocator, weather, fmt)
|
const output = if (format) |fmt| blk: {
|
||||||
else
|
if (std.mem.eql(u8, fmt, "j1")) {
|
||||||
try ansi.render(allocator, weather, .{});
|
res.content_type = .JSON;
|
||||||
|
break :blk try json.render(allocator, weather);
|
||||||
|
} else if (std.mem.eql(u8, fmt, "v2")) {
|
||||||
|
break :blk try v2.render(allocator, weather);
|
||||||
|
} else if (std.mem.startsWith(u8, fmt, "%")) {
|
||||||
|
break :blk try custom.render(allocator, weather, fmt);
|
||||||
|
} else {
|
||||||
|
break :blk try line.render(allocator, weather, fmt);
|
||||||
|
}
|
||||||
|
} else try ansi.render(allocator, weather, .{});
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
if (res.content_type != .JSON) {
|
||||||
res.content_type = .TEXT;
|
res.content_type = .TEXT;
|
||||||
|
}
|
||||||
res.body = output;
|
res.body = output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
88
zig/src/http/query.zig
Normal file
88
zig/src/http/query.zig
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const QueryParams = struct {
|
||||||
|
format: ?[]const u8 = null,
|
||||||
|
lang: ?[]const u8 = null,
|
||||||
|
units: ?Units = null,
|
||||||
|
transparency: ?u8 = null,
|
||||||
|
|
||||||
|
pub const Units = enum {
|
||||||
|
metric,
|
||||||
|
uscs,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn parse(allocator: std.mem.Allocator, query_string: []const u8) !QueryParams {
|
||||||
|
var params = QueryParams{};
|
||||||
|
var iter = std.mem.splitScalar(u8, query_string, '&');
|
||||||
|
|
||||||
|
while (iter.next()) |pair| {
|
||||||
|
if (pair.len == 0) continue;
|
||||||
|
|
||||||
|
var kv = std.mem.splitScalar(u8, pair, '=');
|
||||||
|
const key = kv.next() orelse continue;
|
||||||
|
const value = kv.next();
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, key, "format")) {
|
||||||
|
params.format = if (value) |v| try allocator.dupe(u8, v) else null;
|
||||||
|
} else if (std.mem.eql(u8, key, "lang")) {
|
||||||
|
params.lang = if (value) |v| try allocator.dupe(u8, v) else null;
|
||||||
|
} else if (std.mem.eql(u8, key, "u")) {
|
||||||
|
params.units = .uscs;
|
||||||
|
} else if (std.mem.eql(u8, key, "m")) {
|
||||||
|
params.units = .metric;
|
||||||
|
} else if (std.mem.eql(u8, key, "transparency")) {
|
||||||
|
if (value) |v| {
|
||||||
|
params.transparency = try std.fmt.parseInt(u8, v, 10);
|
||||||
|
}
|
||||||
|
} else if (std.mem.eql(u8, key, "t")) {
|
||||||
|
params.transparency = 150;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "parse empty query" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
const params = try QueryParams.parse(allocator, "");
|
||||||
|
try std.testing.expect(params.format == null);
|
||||||
|
try std.testing.expect(params.lang == null);
|
||||||
|
try std.testing.expect(params.units == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse format parameter" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
const params = try QueryParams.parse(allocator, "format=j1");
|
||||||
|
defer if (params.format) |f| allocator.free(f);
|
||||||
|
try std.testing.expect(params.format != null);
|
||||||
|
try std.testing.expectEqualStrings("j1", params.format.?);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse units parameters" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
const params_m = try QueryParams.parse(allocator, "m");
|
||||||
|
try std.testing.expectEqual(QueryParams.Units.metric, params_m.units.?);
|
||||||
|
|
||||||
|
const params_u = try QueryParams.parse(allocator, "u");
|
||||||
|
try std.testing.expectEqual(QueryParams.Units.uscs, params_u.units.?);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse multiple parameters" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
const params = try QueryParams.parse(allocator, "format=3&lang=de&m");
|
||||||
|
defer if (params.format) |f| allocator.free(f);
|
||||||
|
defer if (params.lang) |l| allocator.free(l);
|
||||||
|
try std.testing.expectEqualStrings("3", params.format.?);
|
||||||
|
try std.testing.expectEqualStrings("de", params.lang.?);
|
||||||
|
try std.testing.expectEqual(QueryParams.Units.metric, params.units.?);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse transparency" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
const params_t = try QueryParams.parse(allocator, "t");
|
||||||
|
try std.testing.expectEqual(@as(u8, 150), params_t.transparency.?);
|
||||||
|
|
||||||
|
const params_custom = try QueryParams.parse(allocator, "transparency=200");
|
||||||
|
try std.testing.expectEqual(@as(u8, 200), params_custom.transparency.?);
|
||||||
|
}
|
||||||
|
|
@ -74,7 +74,7 @@ pub const Resolver = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format IP address
|
// Format IP address
|
||||||
const ip_str = try std.fmt.allocPrint(self.allocator, "{}", .{addr_list.addrs[0].any});
|
const ip_str = try std.fmt.allocPrint(self.allocator, "{any}", .{addr_list.addrs[0].any});
|
||||||
defer self.allocator.free(ip_str);
|
defer self.allocator.free(ip_str);
|
||||||
|
|
||||||
return self.resolveIP(ip_str);
|
return self.resolveIP(ip_str);
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,9 @@ test {
|
||||||
_ = @import("http/query.zig");
|
_ = @import("http/query.zig");
|
||||||
_ = @import("http/help.zig");
|
_ = @import("http/help.zig");
|
||||||
_ = @import("render/line.zig");
|
_ = @import("render/line.zig");
|
||||||
|
_ = @import("render/json.zig");
|
||||||
|
_ = @import("render/v2.zig");
|
||||||
|
_ = @import("render/custom.zig");
|
||||||
_ = @import("location/geoip.zig");
|
_ = @import("location/geoip.zig");
|
||||||
_ = @import("location/resolver.zig");
|
_ = @import("location/resolver.zig");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
137
zig/src/render/custom.zig
Normal file
137
zig/src/render/custom.zig
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const types = @import("../weather/types.zig");
|
||||||
|
|
||||||
|
const weather_icons = [_][]const u8{
|
||||||
|
"✨", // 0-99
|
||||||
|
"🌑", // 100-199
|
||||||
|
"⛅", // 200-299
|
||||||
|
"☁️", // 300-399
|
||||||
|
};
|
||||||
|
|
||||||
|
fn getWeatherIcon(code: u16) []const u8 {
|
||||||
|
const idx = @min(code / 100, weather_icons.len - 1);
|
||||||
|
return weather_icons[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, format: []const u8) ![]const u8 {
|
||||||
|
var output: std.ArrayList(u8) = .empty;
|
||||||
|
errdefer output.deinit(allocator);
|
||||||
|
const writer = output.writer(allocator);
|
||||||
|
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < format.len) {
|
||||||
|
if (format[i] == '%' and i + 1 < format.len) {
|
||||||
|
const code = format[i + 1];
|
||||||
|
switch (code) {
|
||||||
|
'c' => try writer.print("{s}", .{getWeatherIcon(weather.current.weather_code)}),
|
||||||
|
'C' => try writer.print("{s}", .{weather.current.condition}),
|
||||||
|
'h' => try writer.print("{d}%", .{weather.current.humidity}),
|
||||||
|
't' => try writer.print("{d:.0}°C", .{weather.current.temp_c}),
|
||||||
|
'f' => try writer.print("{d:.0}°C", .{weather.current.temp_c}), // Feels like (same as temp for now)
|
||||||
|
'w' => try writer.print("{d:.0} km/h {s}", .{ weather.current.wind_kph, weather.current.wind_dir }),
|
||||||
|
'l' => try writer.print("{s}", .{weather.location}),
|
||||||
|
'p' => try writer.print("{d:.1} mm", .{weather.current.precip_mm}),
|
||||||
|
'P' => try writer.print("{d:.0} hPa", .{weather.current.pressure_mb}),
|
||||||
|
'm' => try writer.print("🌕", .{}), // Moon phase placeholder
|
||||||
|
'M' => try writer.print("15", .{}), // Moon day placeholder
|
||||||
|
'o' => try writer.print("0%", .{}), // Probability of precipitation placeholder
|
||||||
|
'D' => try writer.print("06:00", .{}), // Dawn placeholder
|
||||||
|
'S' => try writer.print("07:30", .{}), // Sunrise placeholder
|
||||||
|
'z' => try writer.print("12:00", .{}), // Zenith placeholder
|
||||||
|
's' => try writer.print("18:30", .{}), // Sunset placeholder
|
||||||
|
'd' => try writer.print("20:00", .{}), // Dusk placeholder
|
||||||
|
'%' => try writer.print("%", .{}),
|
||||||
|
'n' => try writer.print("\n", .{}),
|
||||||
|
else => {
|
||||||
|
try writer.print("%{c}", .{code});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
i += 2;
|
||||||
|
} else {
|
||||||
|
try writer.writeByte(format[i]);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.toOwnedSlice(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "render custom format with location and temp" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
const weather = types.WeatherData{
|
||||||
|
.location = "London",
|
||||||
|
.current = .{
|
||||||
|
.temp_c = 7.0,
|
||||||
|
.temp_f = 44.6,
|
||||||
|
.condition = "Overcast",
|
||||||
|
.weather_code = 122,
|
||||||
|
.humidity = 76,
|
||||||
|
.wind_kph = 11.0,
|
||||||
|
.wind_dir = "NNE",
|
||||||
|
.pressure_mb = 1019.0,
|
||||||
|
.precip_mm = 0.0,
|
||||||
|
},
|
||||||
|
.forecast = &[_]types.ForecastDay{},
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = try render(allocator, weather, "%l: %c %t");
|
||||||
|
defer allocator.free(output);
|
||||||
|
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, output, "London") != null);
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, output, "7°C") != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "render custom format with newline" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
const weather = types.WeatherData{
|
||||||
|
.location = "Paris",
|
||||||
|
.current = .{
|
||||||
|
.temp_c = 10.0,
|
||||||
|
.temp_f = 50.0,
|
||||||
|
.condition = "Clear",
|
||||||
|
.weather_code = 113,
|
||||||
|
.humidity = 65,
|
||||||
|
.wind_kph = 8.0,
|
||||||
|
.wind_dir = "E",
|
||||||
|
.pressure_mb = 1020.0,
|
||||||
|
.precip_mm = 0.0,
|
||||||
|
},
|
||||||
|
.forecast = &[_]types.ForecastDay{},
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = try render(allocator, weather, "%l%n%C");
|
||||||
|
defer allocator.free(output);
|
||||||
|
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, output, "Paris\nClear") != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "render custom format with humidity and pressure" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
const weather = types.WeatherData{
|
||||||
|
.location = "Berlin",
|
||||||
|
.current = .{
|
||||||
|
.temp_c = 5.0,
|
||||||
|
.temp_f = 41.0,
|
||||||
|
.condition = "Cloudy",
|
||||||
|
.weather_code = 119,
|
||||||
|
.humidity = 85,
|
||||||
|
.wind_kph = 12.0,
|
||||||
|
.wind_dir = "W",
|
||||||
|
.pressure_mb = 1012.0,
|
||||||
|
.precip_mm = 0.2,
|
||||||
|
},
|
||||||
|
.forecast = &[_]types.ForecastDay{},
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = try render(allocator, weather, "Humidity: %h, Pressure: %P");
|
||||||
|
defer allocator.free(output);
|
||||||
|
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, output, "85%") != null);
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, output, "1012") != null);
|
||||||
|
}
|
||||||
|
|
@ -2,10 +2,7 @@ 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) ![]const u8 {
|
||||||
var output = std.ArrayList(u8).init(allocator);
|
const data = .{
|
||||||
errdefer output.deinit();
|
|
||||||
|
|
||||||
try std.json.stringify(.{
|
|
||||||
.current_condition = .{
|
.current_condition = .{
|
||||||
.temp_C = weather.current.temp_c,
|
.temp_C = weather.current.temp_c,
|
||||||
.temp_F = weather.current.temp_f,
|
.temp_F = weather.current.temp_f,
|
||||||
|
|
@ -17,31 +14,10 @@ pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData) ![]const
|
||||||
.pressure = weather.current.pressure_mb,
|
.pressure = weather.current.pressure_mb,
|
||||||
.precipMM = weather.current.precip_mm,
|
.precipMM = weather.current.precip_mm,
|
||||||
},
|
},
|
||||||
.weather = blk: {
|
.weather = weather.forecast,
|
||||||
var forecast_array = std.ArrayList(struct {
|
};
|
||||||
date: []const u8,
|
|
||||||
maxtempC: f32,
|
|
||||||
mintempC: f32,
|
|
||||||
weatherCode: u16,
|
|
||||||
weatherDesc: []const u8,
|
|
||||||
}).init(allocator);
|
|
||||||
defer forecast_array.deinit();
|
|
||||||
|
|
||||||
for (weather.forecast) |day| {
|
return try std.fmt.allocPrint(allocator, "{any}", .{std.json.fmt(data, .{})});
|
||||||
try forecast_array.append(.{
|
|
||||||
.date = day.date,
|
|
||||||
.maxtempC = day.max_temp_c,
|
|
||||||
.mintempC = day.min_temp_c,
|
|
||||||
.weatherCode = day.weather_code,
|
|
||||||
.weatherDesc = day.condition,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
break :blk try forecast_array.toOwnedSlice();
|
|
||||||
},
|
|
||||||
}, .{}, output.writer());
|
|
||||||
|
|
||||||
return output.toOwnedSlice();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "render json format" {
|
test "render json format" {
|
||||||
|
|
|
||||||
|
|
@ -9,30 +9,36 @@ pub fn render(allocator: std.mem.Allocator, data: types.WeatherData, format: []c
|
||||||
data.current.temp_c,
|
data.current.temp_c,
|
||||||
});
|
});
|
||||||
} 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}{d:.0}km/h", .{
|
return std.fmt.allocPrint(allocator, "{s}: {s} {d:.0}°C {s}{s}{d:.0}km/h", .{
|
||||||
data.location,
|
data.location,
|
||||||
getConditionEmoji(data.current.weather_code),
|
getConditionEmoji(data.current.weather_code),
|
||||||
data.current.temp_c,
|
data.current.temp_c,
|
||||||
|
"🌬️",
|
||||||
data.current.wind_dir,
|
data.current.wind_dir,
|
||||||
data.current.wind_kph,
|
data.current.wind_kph,
|
||||||
});
|
});
|
||||||
} 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}{d:.0}km/h 💧{d}%%", .{
|
return std.fmt.allocPrint(allocator, "{s}: {s} {d:.0}°C {s}{s}{d:.0}km/h {s}{d}%%", .{
|
||||||
data.location,
|
data.location,
|
||||||
getConditionEmoji(data.current.weather_code),
|
getConditionEmoji(data.current.weather_code),
|
||||||
data.current.temp_c,
|
data.current.temp_c,
|
||||||
|
"🌬️",
|
||||||
data.current.wind_dir,
|
data.current.wind_dir,
|
||||||
data.current.wind_kph,
|
data.current.wind_kph,
|
||||||
|
"💧",
|
||||||
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}{d:.0}km/h 💧{d}%% ☀️", .{
|
return std.fmt.allocPrint(allocator, "{s}: {s} {d:.0}°C {s}{s}{d:.0}km/h {s}{d}%% {s}", .{
|
||||||
data.location,
|
data.location,
|
||||||
getConditionEmoji(data.current.weather_code),
|
getConditionEmoji(data.current.weather_code),
|
||||||
data.current.temp_c,
|
data.current.temp_c,
|
||||||
|
"🌬️",
|
||||||
data.current.wind_dir,
|
data.current.wind_dir,
|
||||||
data.current.wind_kph,
|
data.current.wind_kph,
|
||||||
|
"💧",
|
||||||
data.current.humidity,
|
data.current.humidity,
|
||||||
|
"☀️",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return renderCustom(allocator, data, format);
|
return renderCustom(allocator, data, format);
|
||||||
|
|
|
||||||
68
zig/src/render/v2.zig
Normal file
68
zig/src/render/v2.zig
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const types = @import("../weather/types.zig");
|
||||||
|
|
||||||
|
pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData) ![]const u8 {
|
||||||
|
var output: std.ArrayList(u8) = .empty;
|
||||||
|
errdefer output.deinit(allocator);
|
||||||
|
const writer = output.writer(allocator);
|
||||||
|
|
||||||
|
// Header with location
|
||||||
|
try writer.print("Weather report: {s}\n\n", .{weather.location});
|
||||||
|
|
||||||
|
// Current conditions
|
||||||
|
try writer.print(" Current conditions\n", .{});
|
||||||
|
try writer.print(" {s}\n", .{weather.current.condition});
|
||||||
|
try writer.writeAll(" 🌡️ ");
|
||||||
|
try writer.print("{d:.1}°C ({d:.1}°F)\n", .{ weather.current.temp_c, weather.current.temp_f });
|
||||||
|
try writer.writeAll(" 💧 ");
|
||||||
|
try writer.print("{d}%\n", .{weather.current.humidity});
|
||||||
|
try writer.writeAll(" 🌬️ ");
|
||||||
|
try writer.print("{d:.1} km/h {s}\n", .{ weather.current.wind_kph, weather.current.wind_dir });
|
||||||
|
try writer.writeAll(" 🔽 ");
|
||||||
|
try writer.print("{d:.1} hPa\n", .{weather.current.pressure_mb});
|
||||||
|
try writer.writeAll(" 💦 ");
|
||||||
|
try writer.print("{d:.1} mm\n\n", .{weather.current.precip_mm});
|
||||||
|
|
||||||
|
// Forecast
|
||||||
|
if (weather.forecast.len > 0) {
|
||||||
|
try writer.print(" Forecast\n", .{});
|
||||||
|
for (weather.forecast) |day| {
|
||||||
|
try writer.print(" {s}: {s}\n", .{ day.date, day.condition });
|
||||||
|
try writer.writeAll(" ↑ ");
|
||||||
|
try writer.print("{d:.1}°C ", .{day.max_temp_c});
|
||||||
|
try writer.writeAll("↓ ");
|
||||||
|
try writer.print("{d:.1}°C\n", .{day.min_temp_c});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.toOwnedSlice(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "render v2 format" {
|
||||||
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
const weather = types.WeatherData{
|
||||||
|
.location = "Munich",
|
||||||
|
.current = .{
|
||||||
|
.temp_c = 12.0,
|
||||||
|
.temp_f = 53.6,
|
||||||
|
.condition = "Overcast",
|
||||||
|
.weather_code = 122,
|
||||||
|
.humidity = 80,
|
||||||
|
.wind_kph = 15.0,
|
||||||
|
.wind_dir = "NW",
|
||||||
|
.pressure_mb = 1015.0,
|
||||||
|
.precip_mm = 0.5,
|
||||||
|
},
|
||||||
|
.forecast = &[_]types.ForecastDay{},
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
const output = try render(allocator, weather);
|
||||||
|
defer allocator.free(output);
|
||||||
|
|
||||||
|
try std.testing.expect(output.len > 0);
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, output, "Munich") != 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);
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue