const std = @import("std"); const types = @import("../weather/types.zig"); const emoji = @import("emoji.zig"); pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, format: []const u8, use_imperial: bool) ![]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}", .{emoji.getWeatherEmoji(weather.current.weather_code)}), 'C' => try writer.print("{s}", .{weather.current.condition}), 'h' => try writer.print("{d}%", .{weather.current.humidity}), 't' => { const temp = if (use_imperial) weather.current.tempFahrenheit() else weather.current.temp_c; const unit = if (use_imperial) "°F" else "°C"; const sign: u8 = if (temp >= 0) '+' else 0; if (sign != 0) { try writer.print("{c}{d:.1}{s}", .{ sign, temp, unit }); } else { try writer.print("{d:.1}{s}", .{ temp, unit }); } }, 'f' => { const temp = if (use_imperial) weather.current.tempFahrenheit() else weather.current.temp_c; const unit = if (use_imperial) "°F" else "°C"; const sign: u8 = if (temp >= 0) '+' else 0; if (sign != 0) { try writer.print("{c}{d:.1}{s}", .{ sign, temp, unit }); } else { try writer.print("{d:.1}{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}), 'p' => { 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("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, .feels_like_c = 7.0, .condition = "Overcast", .weather_code = .clouds_overcast, .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", false); 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.0°C") != null); } test "render custom format with newline" { const allocator = std.testing.allocator; const weather = types.WeatherData{ .location = "Paris", .current = .{ .temp_c = 10.0, .feels_like_c = 10.0, .condition = "Clear", .weather_code = .clear, .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", false); 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, .feels_like_c = 5.0, .condition = "Cloudy", .weather_code = .clouds_overcast, .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", false); 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); } test "render custom format with imperial units" { const allocator = std.testing.allocator; const weather = types.WeatherData{ .location = "NYC", .current = .{ .temp_c = 10.0, .feels_like_c = 10.0, .condition = "Clear", .weather_code = .clear, .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.0°F") != null); try std.testing.expect(std.mem.indexOf(u8, output, "mph") != null); try std.testing.expect(std.mem.indexOf(u8, output, "in") != null); }