wttr/src/render/line.zig
2025-12-18 16:39:01 -08:00

204 lines
7.7 KiB
Zig
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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