204 lines
7.7 KiB
Zig
204 lines
7.7 KiB
Zig
const std = @import("std");
|
||
const types = @import("../weather/types.zig");
|
||
|
||
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")) {
|
||
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,
|
||
getConditionEmoji(data.current.weather_code),
|
||
temp,
|
||
unit,
|
||
});
|
||
} else if (std.mem.eql(u8, format, "2")) {
|
||
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,
|
||
getConditionEmoji(data.current.weather_code),
|
||
temp,
|
||
unit,
|
||
"🌬️",
|
||
data.current.wind_dir,
|
||
wind,
|
||
wind_unit,
|
||
});
|
||
} else if (std.mem.eql(u8, format, "3")) {
|
||
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,
|
||
getConditionEmoji(data.current.weather_code),
|
||
temp,
|
||
unit,
|
||
"🌬️",
|
||
data.current.wind_dir,
|
||
wind,
|
||
wind_unit,
|
||
"💧",
|
||
data.current.humidity,
|
||
});
|
||
} else if (std.mem.eql(u8, format, "4")) {
|
||
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,
|
||
getConditionEmoji(data.current.weather_code),
|
||
temp,
|
||
unit,
|
||
"🌬️",
|
||
data.current.wind_dir,
|
||
wind,
|
||
wind_unit,
|
||
"💧",
|
||
data.current.humidity,
|
||
"☀️",
|
||
});
|
||
} else {
|
||
return renderCustom(allocator, data, format, use_imperial);
|
||
}
|
||
}
|
||
|
||
fn renderCustom(allocator: std.mem.Allocator, data: types.WeatherData, format: []const u8, use_imperial: bool) ![]const u8 {
|
||
var output: std.ArrayList(u8) = .empty;
|
||
errdefer output.deinit(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 output.appendSlice(allocator, getConditionEmoji(data.current.weather_code)),
|
||
'C' => try output.appendSlice(allocator, data.current.condition),
|
||
'h' => try output.writer(allocator).print("{d}", .{data.current.humidity}),
|
||
't' => {
|
||
const temp = if (use_imperial) data.current.temp_f else data.current.temp_c;
|
||
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),
|
||
'p' => {
|
||
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, '%'),
|
||
else => {
|
||
try output.append(allocator, '%');
|
||
try output.append(allocator, code);
|
||
},
|
||
}
|
||
i += 2;
|
||
} else {
|
||
try output.append(allocator, format[i]);
|
||
i += 1;
|
||
}
|
||
}
|
||
|
||
return output.toOwnedSlice(allocator);
|
||
}
|
||
|
||
fn getConditionEmoji(code: types.WeatherCode) []const u8 {
|
||
return switch (@intFromEnum(code)) {
|
||
800 => "☀️", // Clear
|
||
801, 802 => "⛅️", // Few/scattered clouds
|
||
803, 804 => "☁️", // Broken/overcast clouds
|
||
701, 741 => "🌁", // Mist/fog (alternatively, maybe 🌫️ or 🌫 is better)
|
||
300...321 => "🌦", // Drizzle
|
||
500...531 => "🌧️", // Rain
|
||
200...232 => "⛈️", // Thunderstorm
|
||
611...616 => "❄️", // Sleet/freezing (check before snow)
|
||
600...610, 617...622 => "🌨️", // Snow
|
||
else => "🌡️",
|
||
};
|
||
}
|
||
|
||
test "format 1" {
|
||
const data = types.WeatherData{
|
||
.location = "London",
|
||
.current = .{
|
||
.temp_c = 15.0,
|
||
.temp_f = 59.0,
|
||
.condition = "Clear",
|
||
.weather_code = .clear,
|
||
.humidity = 65,
|
||
.wind_kph = 10.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, "1", false);
|
||
defer std.testing.allocator.free(output);
|
||
|
||
try std.testing.expectEqualStrings("London: ☀️ 15°C", output);
|
||
}
|
||
|
||
test "custom format" {
|
||
const data = types.WeatherData{
|
||
.location = "London",
|
||
.current = .{
|
||
.temp_c = 15.0,
|
||
.temp_f = 59.0,
|
||
.condition = "Clear",
|
||
.weather_code = .clear,
|
||
.humidity = 65,
|
||
.wind_kph = 10.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, "%l: %c %t°C", false);
|
||
defer std.testing.allocator.free(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 = .clouds_overcast,
|
||
.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);
|
||
}
|