diff --git a/src/http/handler.zig b/src/http/handler.zig index 246882e..f34825a 100644 --- a/src/http/handler.zig +++ b/src/http/handler.zig @@ -140,32 +140,33 @@ fn handleWeatherInternal( const coords_header = try std.fmt.allocPrint(res.arena, "{d:.4},{d:.4}", .{ location.coords.latitude, location.coords.longitude }); res.headers.add("X-Location-Coordinates", coords_header); - res.body = blk: { - if (params.format) |fmt| { - // Anything except the json will be plain text - res.content_type = .TEXT; - if (std.mem.eql(u8, fmt, "j1")) { - res.content_type = .JSON; // reset to json - break :blk try Json.render(req_alloc, weather); - } - if (std.mem.eql(u8, fmt, "p1")) - break :blk try Prometheus.render(req_alloc, weather); - if (std.mem.eql(u8, fmt, "v2")) - break :blk try V2.render(req_alloc, weather, render_options.use_imperial); - if (std.mem.startsWith(u8, fmt, "%")) - break :blk try Custom.render(req_alloc, weather, fmt, render_options.use_imperial); - // fall back to line if we don't understand the format parameter - break :blk try Line.render(req_alloc, weather, fmt, render_options.use_imperial); + if (params.format) |fmt| { + // Anything except the json will be plain text + res.content_type = .TEXT; + if (std.mem.eql(u8, fmt, "j1")) { + res.content_type = .JSON; // reset to json + try Json.render(res.writer(), weather); + } else if (std.mem.eql(u8, fmt, "p1")) { + try Prometheus.render(res.writer(), weather, req_alloc); + } else if (std.mem.eql(u8, fmt, "v2")) { + try V2.render(res.writer(), weather, render_options.use_imperial); + } else if (std.mem.startsWith(u8, fmt, "%")) { + try Custom.render(res.writer(), weather, fmt, render_options.use_imperial); } else { - render_options.format = determineFormat(params, req.headers.get("user-agent")); - log.debug( - "Format: {}. params.ansi {}, params.text {}, user agent: {?s}", - .{ render_options.format, params.ansi, params.text_only, req.headers.get("user-agent") }, - ); - if (render_options.format != .html) res.content_type = .TEXT else res.content_type = .HTML; - break :blk try Formatted.render(req_alloc, weather, render_options); + // fall back to line if we don't understand the format parameter + try Line.render(res.writer(), weather, fmt, render_options.use_imperial); } - }; + } else { + // No specific format selected, we'll provide Formatted output in either + // text (ansi/plain) or html + render_options.format = determineFormat(params, req.headers.get("user-agent")); + log.debug( + "Format: {}. params.ansi {}, params.text {}, user agent: {?s}", + .{ render_options.format, params.ansi, params.text_only, req.headers.get("user-agent") }, + ); + if (render_options.format != .html) res.content_type = .TEXT else res.content_type = .HTML; + try Formatted.render(res.writer(), weather, render_options); + } } fn determineFormat(params: QueryParams, user_agent: ?[]const u8) Formatted.Format { diff --git a/src/render/Custom.zig b/src/render/Custom.zig index b3f0694..e396e60 100644 --- a/src/render/Custom.zig +++ b/src/render/Custom.zig @@ -8,18 +8,14 @@ const Astronomical = @import("../Astronomical.zig"); const TimeZoneOffsets = @import("../location/timezone_offsets.zig"); const Coordinates = @import("../Coordinates.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); - +pub fn render(writer: *std.Io.Writer, weather: types.WeatherData, format: []const u8, use_imperial: bool) !void { 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}), + 'c' => try writer.writeAll(emoji.getWeatherEmoji(weather.current.weather_code)), + 'C' => try writer.writeAll(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; @@ -46,7 +42,7 @@ pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, format: const unit = if (use_imperial) "mph" else "km/h"; try writer.print("{d:.0} {s} {s}", .{ wind, unit, utils.degreeToDirection(weather.current.wind_deg) }); }, - 'l' => try writer.print("{s}", .{weather.locationDisplayName()}), + 'l' => try writer.writeAll(weather.locationDisplayName()), '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"; @@ -60,7 +56,7 @@ pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, format: 'm' => { const now = try nowAt(weather.coords); const moon = Moon.getPhase(now); - try writer.print("{s}", .{moon.emoji()}); + try writer.writeAll(moon.emoji()); }, 'M' => { const now = try nowAt(weather.coords); @@ -101,8 +97,6 @@ pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, format: i += 1; } } - - return output.toOwnedSlice(allocator); } fn nowAt(coords: Coordinates) !i64 { @@ -137,8 +131,12 @@ test "render custom format with location and temp" { .allocator = allocator, }; - const output = try render(allocator, weather, "%l: %c %t", false); - defer allocator.free(output); + var output_buf: [1024]u8 = undefined; + var writer = std.Io.Writer.fixed(&output_buf); + + try render(&writer, weather, "%l: %c %t", false); + + const output = output_buf[0..writer.end]; try std.testing.expect(std.mem.indexOf(u8, output, "London") != null); try std.testing.expect(std.mem.indexOf(u8, output, "+7.0°C") != null); @@ -166,8 +164,12 @@ test "render custom format with newline" { .allocator = allocator, }; - const output = try render(allocator, weather, "%l%n%C", false); - defer allocator.free(output); + var output_buf: [1024]u8 = undefined; + var writer = std.Io.Writer.fixed(&output_buf); + + try render(&writer, weather, "%l%n%C", false); + + const output = output_buf[0..writer.end]; try std.testing.expect(std.mem.indexOf(u8, output, "Paris\nClear") != null); } @@ -194,8 +196,12 @@ test "render custom format with humidity and pressure" { .allocator = allocator, }; - const output = try render(allocator, weather, "Humidity: %h, Pressure: %P", false); - defer allocator.free(output); + var output_buf: [1024]u8 = undefined; + var writer = std.Io.Writer.fixed(&output_buf); + + try render(&writer, weather, "Humidity: %h, Pressure: %P", false); + + const output = output_buf[0..writer.end]; try std.testing.expect(std.mem.indexOf(u8, output, "85%") != null); try std.testing.expect(std.mem.indexOf(u8, output, "1012") != null); @@ -223,8 +229,12 @@ test "render custom format with imperial units" { .allocator = allocator, }; - const output = try render(allocator, weather, "%t %w %p", true); - defer allocator.free(output); + var output_buf: [1024]u8 = undefined; + var writer = std.Io.Writer.fixed(&output_buf); + + try render(&writer, weather, "%t %w %p", true); + + const output = output_buf[0..writer.end]; try std.testing.expect(std.mem.indexOf(u8, output, "+50.0°F") != null); try std.testing.expect(std.mem.indexOf(u8, output, "mph") != null); diff --git a/src/render/Formatted.zig b/src/render/Formatted.zig index 06c704c..892aa39 100644 --- a/src/render/Formatted.zig +++ b/src/render/Formatted.zig @@ -106,11 +106,8 @@ pub const RenderOptions = struct { format: Format = .ansi, }; -pub fn render(allocator: std.mem.Allocator, data: types.WeatherData, options: RenderOptions) ![]const u8 { - var output = std.Io.Writer.Allocating.init(allocator); - defer output.deinit(); - - const w = &output.writer; +pub fn render(writer: *std.Io.Writer, data: types.WeatherData, options: RenderOptions) !void { + const w = writer; if (options.format == .html) try w.writeAll("
");
if (!options.super_quiet)
try w.print(
@@ -128,8 +125,6 @@ pub fn render(allocator: std.mem.Allocator, data: types.WeatherData, options: Re
}
}
if (options.format == .html) try w.writeAll("");
-
- return output.toOwnedSlice();
}
fn renderCurrent(w: *std.Io.Writer, current: types.CurrentCondition, options: RenderOptions) !void {
@@ -678,8 +673,12 @@ test "render with imperial units" {
.allocator = std.testing.allocator,
};
- const output = try render(std.testing.allocator, data, .{ .use_imperial = true });
- defer std.testing.allocator.free(output);
+ var output_buf: [4096]u8 = undefined;
+ var writer = std.Io.Writer.fixed(&output_buf);
+
+ try render(&writer, data, .{ .use_imperial = true });
+
+ const output = output_buf[0..writer.end];
try std.testing.expect(std.mem.indexOf(u8, output, "+50") != null);
try std.testing.expect(std.mem.indexOf(u8, output, "°F") != null);
@@ -737,12 +736,12 @@ test "partly cloudy weather art" {
fn testArt(data: types.WeatherData) !void {
inline for (std.meta.fields(Format)) |f| {
const format: Format = @enumFromInt(f.value);
- const output = try render(
- std.testing.allocator,
- data,
- .{ .format = format },
- );
- defer std.testing.allocator.free(output);
+ var output_buf: [8192]u8 = undefined;
+ var writer = std.Io.Writer.fixed(&output_buf);
+
+ try render(&writer, data, .{ .format = format });
+
+ const output = output_buf[0..writer.end];
const target = getWeatherArt(
data.current.weather_code,
@@ -944,15 +943,23 @@ test "temperature matches between ansi and custom format" {
.allocator = std.testing.allocator,
};
- const ansi_output = try render(std.testing.allocator, data, .{ .use_imperial = true });
- defer std.testing.allocator.free(ansi_output);
+ var ansi_buf: [4096]u8 = undefined;
+ var ansi_writer = std.Io.Writer.fixed(&ansi_buf);
- const custom_output = try custom.render(std.testing.allocator, data, "%t", true);
- defer std.testing.allocator.free(custom_output);
+ try render(&ansi_writer, data, .{ .use_imperial = true });
+
+ const ansi_output = ansi_buf[0..ansi_writer.end];
+
+ var custom_buf: [1024]u8 = undefined;
+ var custom_writer = std.Io.Writer.fixed(&custom_buf);
+
+ try custom.render(&custom_writer, data, "%t", true);
+
+ const output = custom_buf[0..custom_writer.end];
// ANSI rounds to integer, custom shows decimal
try std.testing.expect(std.mem.indexOf(u8, ansi_output, "+56") != null);
- try std.testing.expect(std.mem.indexOf(u8, custom_output, "55.6°F") != null);
+ try std.testing.expect(std.mem.indexOf(u8, output, "55.6°F") != null);
}
test "tempColor returns correct colors for temperature ranges" {
@@ -1037,8 +1044,12 @@ test "plain text format - MetNo real data" {
const weather_data = try MetNo.parse(undefined, allocator, json_data);
defer weather_data.deinit();
- const output = try render(allocator, weather_data, .{ .format = .plain_text, .days = 3 });
- defer allocator.free(output);
+ var output_buf: [8192]u8 = undefined;
+ var writer = std.Io.Writer.fixed(&output_buf);
+
+ try render(&writer, weather_data, .{ .format = .plain_text, .days = 3 });
+
+ const output = output_buf[0..writer.end];
const expected =
\\Weather report: 47.6038,-122.3301
@@ -1245,8 +1256,12 @@ test "ansi format - MetNo real data - phoenix" {
const weather_data = try MetNo.parse(undefined, allocator, json_data);
defer weather_data.deinit();
- const output = try render(allocator, weather_data, .{ .format = .ansi, .days = 3, .use_imperial = true });
- defer allocator.free(output);
+ var output_buf: [16384]u8 = undefined;
+ var writer = std.Io.Writer.fixed(&output_buf);
+
+ try render(&writer, weather_data, .{ .format = .ansi, .days = 3, .use_imperial = true });
+
+ const output = output_buf[0..writer.end];
const expected = @embedFile("../tests/metno-phoenix.ansi");
diff --git a/src/render/Json.zig b/src/render/Json.zig
index bb3a294..cc9f4b0 100644
--- a/src/render/Json.zig
+++ b/src/render/Json.zig
@@ -1,7 +1,7 @@
const std = @import("std");
const types = @import("../weather/types.zig");
-pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData) ![]const u8 {
+pub fn render(writer: *std.Io.Writer, weather: types.WeatherData) !void {
const data = .{
.current_condition = .{
.temp_C = weather.current.temp_c,
@@ -16,7 +16,7 @@ pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData) ![]const
.weather = weather.forecast,
};
- return try std.fmt.allocPrint(allocator, "{f}", .{std.json.fmt(data, .{})});
+ try writer.print("{f}", .{std.json.fmt(data, .{})});
}
test "render json format" {
@@ -41,8 +41,12 @@ test "render json format" {
.allocator = allocator,
};
- const output = try render(allocator, weather);
- defer allocator.free(output);
+ var output_buf: [4096]u8 = undefined;
+ var writer = std.Io.Writer.fixed(&output_buf);
+
+ try render(&writer, weather);
+
+ const output = output_buf[0..writer.end];
try std.testing.expect(output.len > 0);
try std.testing.expect(std.mem.indexOf(u8, output, "temp_C") != null);
diff --git a/src/render/Line.zig b/src/render/Line.zig
index 15359b7..5942b86 100644
--- a/src/render/Line.zig
+++ b/src/render/Line.zig
@@ -3,11 +3,11 @@ const types = @import("../weather/types.zig");
const emoji = @import("emoji.zig");
const utils = @import("utils.zig");
-pub fn render(allocator: std.mem.Allocator, data: types.WeatherData, format: []const u8, use_imperial: bool) ![]const u8 {
+pub fn render(writer: *std.Io.Writer, data: types.WeatherData, format: []const u8, use_imperial: bool) !void {
if (std.mem.eql(u8, format, "1")) {
const temp = if (use_imperial) data.current.tempFahrenheit() else data.current.temp_c;
const unit = if (use_imperial) "°F" else "°C";
- return std.fmt.allocPrint(allocator, "{s}: {s} {d:.0}{s}", .{
+ try writer.print("{s}: {s} {d:.0}{s}", .{
data.location,
emoji.getWeatherEmoji(data.current.weather_code),
temp,
@@ -18,7 +18,7 @@ pub fn render(allocator: std.mem.Allocator, data: types.WeatherData, format: []c
const unit = if (use_imperial) "°F" else "°C";
const wind = if (use_imperial) data.current.windMph() 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}", .{
+ try writer.print("{s}: {s} {d:.0}{s} {s}{s}{d:.0}{s}", .{
data.location,
emoji.getWeatherEmoji(data.current.weather_code),
temp,
@@ -33,7 +33,7 @@ pub fn render(allocator: std.mem.Allocator, data: types.WeatherData, format: []c
const unit = if (use_imperial) "°F" else "°C";
const wind = if (use_imperial) data.current.windMph() 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}%%", .{
+ try writer.print("{s}: {s} {d:.0}{s} {s}{s}{d:.0}{s} {s}{d}%%", .{
data.location,
emoji.getWeatherEmoji(data.current.weather_code),
temp,
@@ -50,7 +50,7 @@ pub fn render(allocator: std.mem.Allocator, data: types.WeatherData, format: []c
const unit = if (use_imperial) "°F" else "°C";
const wind = if (use_imperial) data.current.windMph() 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}", .{
+ try writer.print("{s}: {s} {d:.0}{s} {s}{s}{d:.0}{s} {s}{d}%% {s}", .{
data.location,
emoji.getWeatherEmoji(data.current.weather_code),
temp,
@@ -64,58 +64,53 @@ pub fn render(allocator: std.mem.Allocator, data: types.WeatherData, format: []c
"☀️",
});
} else {
- return renderCustom(allocator, data, format, use_imperial);
+ try renderCustom(writer, 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);
-
+fn renderCustom(writer: *std.Io.Writer, data: types.WeatherData, format: []const u8, use_imperial: bool) !void {
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, emoji.getWeatherEmoji(data.current.weather_code)),
- 'C' => try output.appendSlice(allocator, data.current.condition),
- 'h' => try output.writer(allocator).print("{d}", .{data.current.humidity}),
+ 'c' => try writer.writeAll(emoji.getWeatherEmoji(data.current.weather_code)),
+ 'C' => try writer.writeAll(data.current.condition),
+ 'h' => try writer.print("{d}", .{data.current.humidity}),
't' => {
const temp = if (use_imperial) data.current.tempFahrenheit() else data.current.temp_c;
- try output.writer(allocator).print("{d:.0}", .{temp});
+ try writer.print("{d:.0}", .{temp});
},
'f' => {
const temp = if (use_imperial) data.current.tempFahrenheit() else data.current.temp_c;
- try output.writer(allocator).print("{d:.0}", .{temp});
+ try writer.print("{d:.0}", .{temp});
},
'w' => {
const wind = if (use_imperial) data.current.windMph() else data.current.wind_kph;
const wind_unit = if (use_imperial) "mph" else "km/h";
- try output.writer(allocator).print("{s}{d:.0}{s}", .{ utils.degreeToDirection(data.current.wind_deg), wind, wind_unit });
+ try writer.print("{s}{d:.0}{s}", .{ utils.degreeToDirection(data.current.wind_deg), wind, wind_unit });
},
- 'l' => try output.appendSlice(allocator, data.location),
+ 'l' => try writer.writeAll(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});
+ try writer.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 writer.print("{d:.0}", .{pressure});
},
- '%' => try output.append(allocator, '%'),
+ '%' => try writer.writeByte('%'),
else => {
- try output.append(allocator, '%');
- try output.append(allocator, code);
+ try writer.writeByte('%');
+ try writer.writeByte(code);
},
}
i += 2;
} else {
- try output.append(allocator, format[i]);
+ try writer.writeByte(format[i]);
i += 1;
}
}
-
- return output.toOwnedSlice(allocator);
}
test "format 1" {
@@ -138,8 +133,12 @@ test "format 1" {
.allocator = std.testing.allocator,
};
- const output = try render(std.testing.allocator, data, "1", false);
- defer std.testing.allocator.free(output);
+ var output_buf: [1024]u8 = undefined;
+ var writer = std.Io.Writer.fixed(&output_buf);
+
+ try render(&writer, data, "1", false);
+
+ const output = output_buf[0..writer.end];
try std.testing.expectEqualStrings("London: ☀️ 15°C", output);
}
@@ -164,8 +163,12 @@ test "custom format" {
.allocator = std.testing.allocator,
};
- const output = try render(std.testing.allocator, data, "%l: %c %t°C", false);
- defer std.testing.allocator.free(output);
+ var output_buf: [1024]u8 = undefined;
+ var writer = std.Io.Writer.fixed(&output_buf);
+
+ try render(&writer, data, "%l: %c %t°C", false);
+
+ const output = output_buf[0..writer.end];
try std.testing.expectEqualStrings("London: ☀️ 15°C", output);
}
@@ -190,8 +193,12 @@ test "format 2 with imperial units" {
.allocator = std.testing.allocator,
};
- const output = try render(std.testing.allocator, data, "2", true);
- defer std.testing.allocator.free(output);
+ var output_buf: [1024]u8 = undefined;
+ var writer = std.Io.Writer.fixed(&output_buf);
+
+ try render(&writer, data, "2", true);
+
+ const output = output_buf[0..writer.end];
try std.testing.expectEqualStrings("Portland: ☁️ 50°F 🌬️SE12mph", output);
}
diff --git a/src/render/Prometheus.zig b/src/render/Prometheus.zig
index 94ecdde..eecd3fd 100644
--- a/src/render/Prometheus.zig
+++ b/src/render/Prometheus.zig
@@ -1,11 +1,9 @@
const std = @import("std");
const types = @import("../weather/types.zig");
const Moon = @import("../Moon.zig");
+const utils = @import("utils.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);
+pub fn render(writer: *std.Io.Writer, weather: types.WeatherData, allocator: std.mem.Allocator) !void {
// Current conditions
try writer.print("# HELP temperature_feels_like_celsius Feels Like Temperature in Celsius\n", .{});
@@ -59,7 +57,7 @@ pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData) ![]const
try writer.print("weather_desc{{forecast=\"current\", description=\"{s}\"}} 1\n", .{weather.current.condition});
try writer.print("# HELP winddir_16_point Wind Direction on a 16-wind compass rose\n", .{});
- const wind_dir = degreeToDirection(weather.current.wind_deg);
+ const wind_dir = utils.degreeToDirection(weather.current.wind_deg);
try writer.print("winddir_16_point{{forecast=\"current\", description=\"{s}\"}} 1\n", .{wind_dir});
// Forecast days
@@ -109,15 +107,6 @@ pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData) ![]const
try writer.print("# HELP astronomy_sunset_min Minutes since start of the day until the moon disappears below the horizon\n", .{});
try writer.print("astronomy_sunset_min{{forecast=\"{s}\"}} 0\n", .{forecast_label}); // Not calculated
}
-
- return output.toOwnedSlice(allocator);
-}
-
-fn degreeToDirection(degrees: f64) []const u8 {
- const normalized = @mod(degrees + 11.25, 360);
- const index: usize = @intFromFloat(normalized / 22.5);
- const directions = [_][]const u8{ "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW" };
- return directions[index];
}
test "prometheus format includes required metrics" {
@@ -153,8 +142,12 @@ test "prometheus format includes required metrics" {
.allocator = allocator,
};
- const output = try render(allocator, weather);
- defer allocator.free(output);
+ var output_buf: [8192]u8 = undefined;
+ var writer = std.Io.Writer.fixed(&output_buf);
+
+ try render(&writer, weather, allocator);
+
+ const output = output_buf[0..writer.end];
// Check for key metrics
try std.testing.expect(std.mem.indexOf(u8, output, "temperature_celsius{forecast=\"current\"}") != null);
@@ -186,8 +179,12 @@ test "prometheus format has proper help comments" {
.allocator = allocator,
};
- const output = try render(allocator, weather);
- defer allocator.free(output);
+ var output_buf: [4096]u8 = undefined;
+ var writer = std.Io.Writer.fixed(&output_buf);
+
+ try render(&writer, weather, allocator);
+
+ const output = output_buf[0..writer.end];
// Check for HELP comments
try std.testing.expect(std.mem.indexOf(u8, output, "# HELP temperature_celsius Temperature in Celsius") != null);
diff --git a/src/render/V2.zig b/src/render/V2.zig
index 3366868..1e12b4e 100644
--- a/src/render/V2.zig
+++ b/src/render/V2.zig
@@ -2,11 +2,7 @@ const std = @import("std");
const types = @import("../weather/types.zig");
const utils = @import("utils.zig");
-pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, use_imperial: bool) ![]const u8 {
- var output: std.ArrayList(u8) = .empty;
- errdefer output.deinit(allocator);
- const writer = output.writer(allocator);
-
+pub fn render(writer: *std.Io.Writer, weather: types.WeatherData, use_imperial: bool) !void {
// Header with location
try writer.print("Weather report: {s}\n\n", .{weather.locationDisplayName()});
@@ -62,8 +58,6 @@ pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, use_impe
}
}
}
-
- return output.toOwnedSlice(allocator);
}
test "render v2 format" {
@@ -88,8 +82,12 @@ test "render v2 format" {
.allocator = allocator,
};
- const output = try render(allocator, weather, false);
- defer allocator.free(output);
+ var output_buf: [2048]u8 = undefined;
+ var writer = std.Io.Writer.fixed(&output_buf);
+
+ try render(&writer, weather, false);
+
+ const output = output_buf[0..writer.end];
try std.testing.expect(output.len > 0);
try std.testing.expect(std.mem.indexOf(u8, output, "Munich") != null);
@@ -119,8 +117,12 @@ test "render v2 format with imperial units" {
.allocator = allocator,
};
- const output = try render(allocator, weather, true);
- defer allocator.free(output);
+ var output_buf: [2048]u8 = undefined;
+ var writer = std.Io.Writer.fixed(&output_buf);
+
+ try render(&writer, weather, true);
+
+ const output = output_buf[0..writer.end];
try std.testing.expect(std.mem.indexOf(u8, output, "50.0°F") != null);
try std.testing.expect(std.mem.indexOf(u8, output, "mph") != null);