From 9c7902aca8d39bff8f32a45cd8779daea40a998c Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Thu, 18 Dec 2025 17:47:29 -0800 Subject: [PATCH] ai stuff to fix colors, etc --- src/render/ansi.zig | 164 ++++++++++++++++++++++++++++++++++++++++-- src/render/custom.zig | 18 +++-- 2 files changed, 173 insertions(+), 9 deletions(-) diff --git a/src/render/ansi.zig b/src/render/ansi.zig index 96defb0..a9eccd8 100644 --- a/src/render/ansi.zig +++ b/src/render/ansi.zig @@ -21,18 +21,65 @@ pub fn render(allocator: std.mem.Allocator, data: types.WeatherData, options: Re const wind_unit = if (options.use_imperial) "mph" else "km/h"; const wind_speed = if (options.use_imperial) data.current.wind_kph * 0.621371 else data.current.wind_kph; + // Dynamic colors based on temperature and wind speed + const temp_color_code = tempColor(data.current.temp_c); + const wind_color_code = windColor(data.current.wind_kph); + const rain_color = "\x1b[38;5;111m"; // light blue + const cloud_color = "\x1b[38;5;250m"; // gray + const reset = "\x1b[0m"; + const art = getWeatherArt(data.current.weather_code); const w = output.writer(allocator); - try w.print("{s} {s}\n", .{ art[0], data.current.condition }); - try w.print("{s} {d:.1}{s}\n", .{ art[1], temp, temp_unit }); - try w.print("{s} {s} {d:.1} {s}\n", .{ art[2], data.current.wind_dir, wind_speed, wind_unit }); - try w.print("{s} Humidity: {d}%\n", .{ art[3], data.current.humidity }); - try w.print("{s} {d:.1} mm\n\n", .{ art[4], data.current.precip_mm }); + try w.print("{s}{s}{s} {s}\n", .{ cloud_color, art[0], reset, data.current.condition }); + try w.print("{s}{s}{s} \x1b[38;5;{d}m{d:.1}{s}{s}\n", .{ cloud_color, art[1], reset, temp_color_code, temp, temp_unit, reset }); + try w.print("{s}{s}{s} {s} \x1b[38;5;{d}m{d:.1}{s} {s}\n", .{ cloud_color, art[2], reset, data.current.wind_dir, wind_color_code, wind_speed, reset, wind_unit }); + try w.print("{s}{s}{s} Humidity: {d}%\n", .{ cloud_color, art[3], reset, data.current.humidity }); + try w.print("{s}{s}{s} {s}{d:.1}{s} mm\n\n", .{ rain_color, art[4], reset, rain_color, data.current.precip_mm, reset }); return output.toOwnedSlice(allocator); } +fn tempColor(temp_c: f32) u8 { + const temp: i32 = @intFromFloat(@round(temp_c)); + return switch (temp) { + -15, -14, -13 => 27, + -12, -11, -10 => 33, + -9, -8, -7 => 39, + -6, -5, -4 => 45, + -3, -2, -1 => 51, + 0, 1 => 50, + 2, 3 => 49, + 4, 5 => 48, + 6, 7 => 47, + 8, 9 => 46, + 10, 11, 12 => 82, + 13, 14, 15 => 118, + 16, 17, 18 => 154, + 19, 20, 21 => 190, + 22, 23, 24 => 226, + 25, 26, 27 => 220, + 28, 29, 30 => 214, + 31, 32, 33 => 208, + 34, 35, 36 => 202, + else => if (temp > 36) 196 else 21, + }; +} + +fn windColor(wind_kph: f32) u8 { + const wind: i32 = @intFromFloat(@round(wind_kph)); + if (wind <= 3) return 241; + if (wind <= 6) return 242; + if (wind <= 9) return 243; + if (wind <= 12) return 246; + if (wind <= 15) return 250; + if (wind <= 19) return 253; + if (wind <= 23) return 214; + if (wind <= 27) return 208; + if (wind <= 31) return 202; + return 196; +} + fn getWeatherArt(code: types.WeatherCode) [5][]const u8 { return switch (@intFromEnum(code)) { 800 => .{ // Clear @@ -123,6 +170,8 @@ test "render with imperial units" { defer std.testing.allocator.free(output); try std.testing.expect(std.mem.indexOf(u8, output, "50.0°F") != null); + // 10°C should be color 82 + try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[38;5;82m") != null); } test "clear weather art" { @@ -344,3 +393,108 @@ test "unknown weather code art" { try std.testing.expect(std.mem.indexOf(u8, output, "?") != null); try std.testing.expect(std.mem.indexOf(u8, output, "¯\\_(ツ)_/¯") != null); } + +test "temperature matches between ansi and custom format" { + const custom = @import("custom.zig"); + + const data = types.WeatherData{ + .location = "PDX", + .current = .{ + .temp_c = 13.1, + .temp_f = 55.6, + .condition = "Clear", + .weather_code = .clear, + .humidity = 60, + .wind_kph = 10.0, + .wind_dir = "N", + .pressure_mb = 1013.0, + .precip_mm = 0.0, + }, + .forecast = &.{}, + .allocator = std.testing.allocator, + }; + + const ansi_output = try render(std.testing.allocator, data, .{ .use_imperial = true }); + defer std.testing.allocator.free(ansi_output); + + const custom_output = try custom.render(std.testing.allocator, data, "%t", true); + defer std.testing.allocator.free(custom_output); + + // Both should show 55.6°F + try std.testing.expect(std.mem.indexOf(u8, ansi_output, "55.6°F") != null); + try std.testing.expect(std.mem.indexOf(u8, custom_output, "55.6°F") != null); +} + +test "tempColor returns correct colors for temperature ranges" { + // Very cold + try std.testing.expectEqual(@as(u8, 27), tempColor(-15)); + try std.testing.expectEqual(@as(u8, 27), tempColor(-13)); + try std.testing.expectEqual(@as(u8, 33), tempColor(-12)); + try std.testing.expectEqual(@as(u8, 33), tempColor(-10)); + + // Cold + try std.testing.expectEqual(@as(u8, 39), tempColor(-9)); + try std.testing.expectEqual(@as(u8, 45), tempColor(-6)); + try std.testing.expectEqual(@as(u8, 51), tempColor(-3)); + + // Cool + try std.testing.expectEqual(@as(u8, 50), tempColor(0)); + try std.testing.expectEqual(@as(u8, 49), tempColor(2)); + try std.testing.expectEqual(@as(u8, 48), tempColor(4)); + try std.testing.expectEqual(@as(u8, 46), tempColor(8)); + + // Mild + try std.testing.expectEqual(@as(u8, 82), tempColor(10)); + try std.testing.expectEqual(@as(u8, 118), tempColor(13)); + try std.testing.expectEqual(@as(u8, 118), tempColor(15)); + + // Warm + try std.testing.expectEqual(@as(u8, 154), tempColor(16)); + try std.testing.expectEqual(@as(u8, 190), tempColor(20)); + try std.testing.expectEqual(@as(u8, 226), tempColor(23)); + try std.testing.expectEqual(@as(u8, 220), tempColor(26)); + + // Hot + try std.testing.expectEqual(@as(u8, 214), tempColor(29)); + try std.testing.expectEqual(@as(u8, 208), tempColor(32)); + try std.testing.expectEqual(@as(u8, 202), tempColor(35)); + + // Very hot + try std.testing.expectEqual(@as(u8, 196), tempColor(37)); + try std.testing.expectEqual(@as(u8, 196), tempColor(50)); + + // Very cold (below range) + try std.testing.expectEqual(@as(u8, 21), tempColor(-20)); +} + +test "windColor returns correct colors for wind speed ranges" { + // Calm + try std.testing.expectEqual(@as(u8, 241), windColor(0)); + try std.testing.expectEqual(@as(u8, 241), windColor(3)); + + // Light breeze + try std.testing.expectEqual(@as(u8, 242), windColor(4)); + try std.testing.expectEqual(@as(u8, 242), windColor(6)); + try std.testing.expectEqual(@as(u8, 243), windColor(7)); + try std.testing.expectEqual(@as(u8, 243), windColor(9)); + + // Moderate + try std.testing.expectEqual(@as(u8, 246), windColor(10)); + try std.testing.expectEqual(@as(u8, 246), windColor(12)); + try std.testing.expectEqual(@as(u8, 250), windColor(13)); + try std.testing.expectEqual(@as(u8, 250), windColor(15)); + try std.testing.expectEqual(@as(u8, 253), windColor(16)); + try std.testing.expectEqual(@as(u8, 253), windColor(19)); + + // Fresh + try std.testing.expectEqual(@as(u8, 214), windColor(20)); + try std.testing.expectEqual(@as(u8, 214), windColor(23)); + try std.testing.expectEqual(@as(u8, 208), windColor(24)); + try std.testing.expectEqual(@as(u8, 208), windColor(27)); + + // Strong + try std.testing.expectEqual(@as(u8, 202), windColor(28)); + try std.testing.expectEqual(@as(u8, 202), windColor(31)); + try std.testing.expectEqual(@as(u8, 196), windColor(32)); + try std.testing.expectEqual(@as(u8, 196), windColor(50)); +} diff --git a/src/render/custom.zig b/src/render/custom.zig index d39879a..65c718b 100644 --- a/src/render/custom.zig +++ b/src/render/custom.zig @@ -29,12 +29,22 @@ pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, format: 't' => { 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 }); + 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.temp_f else weather.current.temp_c; const unit = if (use_imperial) "°F" else "°C"; - try writer.print("{d:.0}{s}", .{ temp, unit }); + 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; @@ -100,7 +110,7 @@ test "render custom format with location and temp" { 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); + try std.testing.expect(std.mem.indexOf(u8, output, "+7.0°C") != null); } test "render custom format with newline" { @@ -179,7 +189,7 @@ test "render custom format with imperial units" { 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, "+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); }