move to std.Io.Writer rather than allocating strings everywhere
This commit is contained in:
parent
b46940a7a3
commit
17d94d9285
7 changed files with 167 additions and 131 deletions
|
|
@ -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 });
|
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.headers.add("X-Location-Coordinates", coords_header);
|
||||||
|
|
||||||
res.body = blk: {
|
if (params.format) |fmt| {
|
||||||
if (params.format) |fmt| {
|
// Anything except the json will be plain text
|
||||||
// Anything except the json will be plain text
|
res.content_type = .TEXT;
|
||||||
res.content_type = .TEXT;
|
if (std.mem.eql(u8, fmt, "j1")) {
|
||||||
if (std.mem.eql(u8, fmt, "j1")) {
|
res.content_type = .JSON; // reset to json
|
||||||
res.content_type = .JSON; // reset to json
|
try Json.render(res.writer(), weather);
|
||||||
break :blk try Json.render(req_alloc, weather);
|
} else if (std.mem.eql(u8, fmt, "p1")) {
|
||||||
}
|
try Prometheus.render(res.writer(), weather, req_alloc);
|
||||||
if (std.mem.eql(u8, fmt, "p1"))
|
} else if (std.mem.eql(u8, fmt, "v2")) {
|
||||||
break :blk try Prometheus.render(req_alloc, weather);
|
try V2.render(res.writer(), weather, render_options.use_imperial);
|
||||||
if (std.mem.eql(u8, fmt, "v2"))
|
} else if (std.mem.startsWith(u8, fmt, "%")) {
|
||||||
break :blk try V2.render(req_alloc, weather, render_options.use_imperial);
|
try Custom.render(res.writer(), weather, fmt, 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);
|
|
||||||
} else {
|
} else {
|
||||||
render_options.format = determineFormat(params, req.headers.get("user-agent"));
|
// fall back to line if we don't understand the format parameter
|
||||||
log.debug(
|
try Line.render(res.writer(), weather, fmt, render_options.use_imperial);
|
||||||
"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);
|
|
||||||
}
|
}
|
||||||
};
|
} 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 {
|
fn determineFormat(params: QueryParams, user_agent: ?[]const u8) Formatted.Format {
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,14 @@ const Astronomical = @import("../Astronomical.zig");
|
||||||
const TimeZoneOffsets = @import("../location/timezone_offsets.zig");
|
const TimeZoneOffsets = @import("../location/timezone_offsets.zig");
|
||||||
const Coordinates = @import("../Coordinates.zig");
|
const Coordinates = @import("../Coordinates.zig");
|
||||||
|
|
||||||
pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, format: []const u8, use_imperial: bool) ![]const u8 {
|
pub fn render(writer: *std.Io.Writer, weather: types.WeatherData, format: []const u8, use_imperial: bool) !void {
|
||||||
var output: std.ArrayList(u8) = .empty;
|
|
||||||
errdefer output.deinit(allocator);
|
|
||||||
const writer = output.writer(allocator);
|
|
||||||
|
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < format.len) {
|
while (i < format.len) {
|
||||||
if (format[i] == '%' and i + 1 < format.len) {
|
if (format[i] == '%' and i + 1 < format.len) {
|
||||||
const code = format[i + 1];
|
const code = format[i + 1];
|
||||||
switch (code) {
|
switch (code) {
|
||||||
'c' => try writer.print("{s}", .{emoji.getWeatherEmoji(weather.current.weather_code)}),
|
'c' => try writer.writeAll(emoji.getWeatherEmoji(weather.current.weather_code)),
|
||||||
'C' => try writer.print("{s}", .{weather.current.condition}),
|
'C' => try writer.writeAll(weather.current.condition),
|
||||||
'h' => try writer.print("{d}%", .{weather.current.humidity}),
|
'h' => try writer.print("{d}%", .{weather.current.humidity}),
|
||||||
't' => {
|
't' => {
|
||||||
const temp = if (use_imperial) weather.current.tempFahrenheit() else weather.current.temp_c;
|
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";
|
const unit = if (use_imperial) "mph" else "km/h";
|
||||||
try writer.print("{d:.0} {s} {s}", .{ wind, unit, utils.degreeToDirection(weather.current.wind_deg) });
|
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' => {
|
'p' => {
|
||||||
const precip = if (use_imperial) weather.current.precip_mm * 0.0393701 else weather.current.precip_mm;
|
const precip = if (use_imperial) weather.current.precip_mm * 0.0393701 else weather.current.precip_mm;
|
||||||
const unit = if (use_imperial) "in" else "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' => {
|
'm' => {
|
||||||
const now = try nowAt(weather.coords);
|
const now = try nowAt(weather.coords);
|
||||||
const moon = Moon.getPhase(now);
|
const moon = Moon.getPhase(now);
|
||||||
try writer.print("{s}", .{moon.emoji()});
|
try writer.writeAll(moon.emoji());
|
||||||
},
|
},
|
||||||
'M' => {
|
'M' => {
|
||||||
const now = try nowAt(weather.coords);
|
const now = try nowAt(weather.coords);
|
||||||
|
|
@ -101,8 +97,6 @@ pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, format:
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output.toOwnedSlice(allocator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nowAt(coords: Coordinates) !i64 {
|
fn nowAt(coords: Coordinates) !i64 {
|
||||||
|
|
@ -137,8 +131,12 @@ test "render custom format with location and temp" {
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(allocator, weather, "%l: %c %t", false);
|
var output_buf: [1024]u8 = undefined;
|
||||||
defer allocator.free(output);
|
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, "London") != null);
|
||||||
try std.testing.expect(std.mem.indexOf(u8, output, "+7.0°C") != 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,
|
.allocator = allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(allocator, weather, "%l%n%C", false);
|
var output_buf: [1024]u8 = undefined;
|
||||||
defer allocator.free(output);
|
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);
|
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,
|
.allocator = allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(allocator, weather, "Humidity: %h, Pressure: %P", false);
|
var output_buf: [1024]u8 = undefined;
|
||||||
defer allocator.free(output);
|
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, "85%") != null);
|
||||||
try std.testing.expect(std.mem.indexOf(u8, output, "1012") != 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,
|
.allocator = allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(allocator, weather, "%t %w %p", true);
|
var output_buf: [1024]u8 = undefined;
|
||||||
defer allocator.free(output);
|
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, "+50.0°F") != null);
|
||||||
try std.testing.expect(std.mem.indexOf(u8, output, "mph") != null);
|
try std.testing.expect(std.mem.indexOf(u8, output, "mph") != null);
|
||||||
|
|
|
||||||
|
|
@ -106,11 +106,8 @@ pub const RenderOptions = struct {
|
||||||
format: Format = .ansi,
|
format: Format = .ansi,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn render(allocator: std.mem.Allocator, data: types.WeatherData, options: RenderOptions) ![]const u8 {
|
pub fn render(writer: *std.Io.Writer, data: types.WeatherData, options: RenderOptions) !void {
|
||||||
var output = std.Io.Writer.Allocating.init(allocator);
|
const w = writer;
|
||||||
defer output.deinit();
|
|
||||||
|
|
||||||
const w = &output.writer;
|
|
||||||
if (options.format == .html) try w.writeAll("<pre>");
|
if (options.format == .html) try w.writeAll("<pre>");
|
||||||
if (!options.super_quiet)
|
if (!options.super_quiet)
|
||||||
try w.print(
|
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("</pre>");
|
if (options.format == .html) try w.writeAll("</pre>");
|
||||||
|
|
||||||
return output.toOwnedSlice();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn renderCurrent(w: *std.Io.Writer, current: types.CurrentCondition, options: RenderOptions) !void {
|
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,
|
.allocator = std.testing.allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(std.testing.allocator, data, .{ .use_imperial = true });
|
var output_buf: [4096]u8 = undefined;
|
||||||
defer std.testing.allocator.free(output);
|
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, "+50") != null);
|
||||||
try std.testing.expect(std.mem.indexOf(u8, output, "°F") != 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 {
|
fn testArt(data: types.WeatherData) !void {
|
||||||
inline for (std.meta.fields(Format)) |f| {
|
inline for (std.meta.fields(Format)) |f| {
|
||||||
const format: Format = @enumFromInt(f.value);
|
const format: Format = @enumFromInt(f.value);
|
||||||
const output = try render(
|
var output_buf: [8192]u8 = undefined;
|
||||||
std.testing.allocator,
|
var writer = std.Io.Writer.fixed(&output_buf);
|
||||||
data,
|
|
||||||
.{ .format = format },
|
try render(&writer, data, .{ .format = format });
|
||||||
);
|
|
||||||
defer std.testing.allocator.free(output);
|
const output = output_buf[0..writer.end];
|
||||||
|
|
||||||
const target = getWeatherArt(
|
const target = getWeatherArt(
|
||||||
data.current.weather_code,
|
data.current.weather_code,
|
||||||
|
|
@ -944,15 +943,23 @@ test "temperature matches between ansi and custom format" {
|
||||||
.allocator = std.testing.allocator,
|
.allocator = std.testing.allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ansi_output = try render(std.testing.allocator, data, .{ .use_imperial = true });
|
var ansi_buf: [4096]u8 = undefined;
|
||||||
defer std.testing.allocator.free(ansi_output);
|
var ansi_writer = std.Io.Writer.fixed(&ansi_buf);
|
||||||
|
|
||||||
const custom_output = try custom.render(std.testing.allocator, data, "%t", true);
|
try render(&ansi_writer, data, .{ .use_imperial = true });
|
||||||
defer std.testing.allocator.free(custom_output);
|
|
||||||
|
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
|
// 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, 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" {
|
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);
|
const weather_data = try MetNo.parse(undefined, allocator, json_data);
|
||||||
defer weather_data.deinit();
|
defer weather_data.deinit();
|
||||||
|
|
||||||
const output = try render(allocator, weather_data, .{ .format = .plain_text, .days = 3 });
|
var output_buf: [8192]u8 = undefined;
|
||||||
defer allocator.free(output);
|
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 =
|
const expected =
|
||||||
\\Weather report: 47.6038,-122.3301
|
\\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);
|
const weather_data = try MetNo.parse(undefined, allocator, json_data);
|
||||||
defer weather_data.deinit();
|
defer weather_data.deinit();
|
||||||
|
|
||||||
const output = try render(allocator, weather_data, .{ .format = .ansi, .days = 3, .use_imperial = true });
|
var output_buf: [16384]u8 = undefined;
|
||||||
defer allocator.free(output);
|
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");
|
const expected = @embedFile("../tests/metno-phoenix.ansi");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const types = @import("../weather/types.zig");
|
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 = .{
|
const data = .{
|
||||||
.current_condition = .{
|
.current_condition = .{
|
||||||
.temp_C = weather.current.temp_c,
|
.temp_C = weather.current.temp_c,
|
||||||
|
|
@ -16,7 +16,7 @@ pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData) ![]const
|
||||||
.weather = weather.forecast,
|
.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" {
|
test "render json format" {
|
||||||
|
|
@ -41,8 +41,12 @@ test "render json format" {
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(allocator, weather);
|
var output_buf: [4096]u8 = undefined;
|
||||||
defer allocator.free(output);
|
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(output.len > 0);
|
||||||
try std.testing.expect(std.mem.indexOf(u8, output, "temp_C") != null);
|
try std.testing.expect(std.mem.indexOf(u8, output, "temp_C") != null);
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ const types = @import("../weather/types.zig");
|
||||||
const emoji = @import("emoji.zig");
|
const emoji = @import("emoji.zig");
|
||||||
const utils = @import("utils.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")) {
|
if (std.mem.eql(u8, format, "1")) {
|
||||||
const temp = if (use_imperial) data.current.tempFahrenheit() else data.current.temp_c;
|
const temp = if (use_imperial) data.current.tempFahrenheit() else data.current.temp_c;
|
||||||
const unit = if (use_imperial) "°F" else "°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,
|
data.location,
|
||||||
emoji.getWeatherEmoji(data.current.weather_code),
|
emoji.getWeatherEmoji(data.current.weather_code),
|
||||||
temp,
|
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 unit = if (use_imperial) "°F" else "°C";
|
||||||
const wind = if (use_imperial) data.current.windMph() else data.current.wind_kph;
|
const wind = if (use_imperial) data.current.windMph() else data.current.wind_kph;
|
||||||
const wind_unit = if (use_imperial) "mph" else "km/h";
|
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,
|
data.location,
|
||||||
emoji.getWeatherEmoji(data.current.weather_code),
|
emoji.getWeatherEmoji(data.current.weather_code),
|
||||||
temp,
|
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 unit = if (use_imperial) "°F" else "°C";
|
||||||
const wind = if (use_imperial) data.current.windMph() else data.current.wind_kph;
|
const wind = if (use_imperial) data.current.windMph() else data.current.wind_kph;
|
||||||
const wind_unit = if (use_imperial) "mph" else "km/h";
|
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,
|
data.location,
|
||||||
emoji.getWeatherEmoji(data.current.weather_code),
|
emoji.getWeatherEmoji(data.current.weather_code),
|
||||||
temp,
|
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 unit = if (use_imperial) "°F" else "°C";
|
||||||
const wind = if (use_imperial) data.current.windMph() else data.current.wind_kph;
|
const wind = if (use_imperial) data.current.windMph() else data.current.wind_kph;
|
||||||
const wind_unit = if (use_imperial) "mph" else "km/h";
|
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,
|
data.location,
|
||||||
emoji.getWeatherEmoji(data.current.weather_code),
|
emoji.getWeatherEmoji(data.current.weather_code),
|
||||||
temp,
|
temp,
|
||||||
|
|
@ -64,58 +64,53 @@ pub fn render(allocator: std.mem.Allocator, data: types.WeatherData, format: []c
|
||||||
"☀️",
|
"☀️",
|
||||||
});
|
});
|
||||||
} else {
|
} 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 {
|
fn renderCustom(writer: *std.Io.Writer, data: types.WeatherData, format: []const u8, use_imperial: bool) !void {
|
||||||
var output: std.ArrayList(u8) = .empty;
|
|
||||||
errdefer output.deinit(allocator);
|
|
||||||
|
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < format.len) {
|
while (i < format.len) {
|
||||||
if (format[i] == '%' and i + 1 < format.len) {
|
if (format[i] == '%' and i + 1 < format.len) {
|
||||||
const code = format[i + 1];
|
const code = format[i + 1];
|
||||||
switch (code) {
|
switch (code) {
|
||||||
'c' => try output.appendSlice(allocator, emoji.getWeatherEmoji(data.current.weather_code)),
|
'c' => try writer.writeAll(emoji.getWeatherEmoji(data.current.weather_code)),
|
||||||
'C' => try output.appendSlice(allocator, data.current.condition),
|
'C' => try writer.writeAll(data.current.condition),
|
||||||
'h' => try output.writer(allocator).print("{d}", .{data.current.humidity}),
|
'h' => try writer.print("{d}", .{data.current.humidity}),
|
||||||
't' => {
|
't' => {
|
||||||
const temp = if (use_imperial) data.current.tempFahrenheit() else data.current.temp_c;
|
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' => {
|
'f' => {
|
||||||
const temp = if (use_imperial) data.current.tempFahrenheit() else data.current.temp_c;
|
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' => {
|
'w' => {
|
||||||
const wind = if (use_imperial) data.current.windMph() else data.current.wind_kph;
|
const wind = if (use_imperial) data.current.windMph() else data.current.wind_kph;
|
||||||
const wind_unit = if (use_imperial) "mph" else "km/h";
|
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' => {
|
'p' => {
|
||||||
const precip = if (use_imperial) data.current.precip_mm * 0.0393701 else data.current.precip_mm;
|
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' => {
|
'P' => {
|
||||||
const pressure = if (use_imperial) data.current.pressure_mb * 0.02953 else data.current.pressure_mb;
|
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 => {
|
else => {
|
||||||
try output.append(allocator, '%');
|
try writer.writeByte('%');
|
||||||
try output.append(allocator, code);
|
try writer.writeByte(code);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
i += 2;
|
i += 2;
|
||||||
} else {
|
} else {
|
||||||
try output.append(allocator, format[i]);
|
try writer.writeByte(format[i]);
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output.toOwnedSlice(allocator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "format 1" {
|
test "format 1" {
|
||||||
|
|
@ -138,8 +133,12 @@ test "format 1" {
|
||||||
.allocator = std.testing.allocator,
|
.allocator = std.testing.allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(std.testing.allocator, data, "1", false);
|
var output_buf: [1024]u8 = undefined;
|
||||||
defer std.testing.allocator.free(output);
|
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);
|
try std.testing.expectEqualStrings("London: ☀️ 15°C", output);
|
||||||
}
|
}
|
||||||
|
|
@ -164,8 +163,12 @@ test "custom format" {
|
||||||
.allocator = std.testing.allocator,
|
.allocator = std.testing.allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(std.testing.allocator, data, "%l: %c %t°C", false);
|
var output_buf: [1024]u8 = undefined;
|
||||||
defer std.testing.allocator.free(output);
|
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);
|
try std.testing.expectEqualStrings("London: ☀️ 15°C", output);
|
||||||
}
|
}
|
||||||
|
|
@ -190,8 +193,12 @@ test "format 2 with imperial units" {
|
||||||
.allocator = std.testing.allocator,
|
.allocator = std.testing.allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(std.testing.allocator, data, "2", true);
|
var output_buf: [1024]u8 = undefined;
|
||||||
defer std.testing.allocator.free(output);
|
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);
|
try std.testing.expectEqualStrings("Portland: ☁️ 50°F 🌬️SE12mph", output);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const types = @import("../weather/types.zig");
|
const types = @import("../weather/types.zig");
|
||||||
const Moon = @import("../Moon.zig");
|
const Moon = @import("../Moon.zig");
|
||||||
|
const utils = @import("utils.zig");
|
||||||
|
|
||||||
pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData) ![]const u8 {
|
pub fn render(writer: *std.Io.Writer, weather: types.WeatherData, allocator: std.mem.Allocator) !void {
|
||||||
var output: std.ArrayList(u8) = .empty;
|
|
||||||
errdefer output.deinit(allocator);
|
|
||||||
const writer = output.writer(allocator);
|
|
||||||
|
|
||||||
// Current conditions
|
// Current conditions
|
||||||
try writer.print("# HELP temperature_feels_like_celsius Feels Like Temperature in Celsius\n", .{});
|
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("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", .{});
|
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});
|
try writer.print("winddir_16_point{{forecast=\"current\", description=\"{s}\"}} 1\n", .{wind_dir});
|
||||||
|
|
||||||
// Forecast days
|
// 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("# 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
|
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" {
|
test "prometheus format includes required metrics" {
|
||||||
|
|
@ -153,8 +142,12 @@ test "prometheus format includes required metrics" {
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(allocator, weather);
|
var output_buf: [8192]u8 = undefined;
|
||||||
defer allocator.free(output);
|
var writer = std.Io.Writer.fixed(&output_buf);
|
||||||
|
|
||||||
|
try render(&writer, weather, allocator);
|
||||||
|
|
||||||
|
const output = output_buf[0..writer.end];
|
||||||
|
|
||||||
// Check for key metrics
|
// Check for key metrics
|
||||||
try std.testing.expect(std.mem.indexOf(u8, output, "temperature_celsius{forecast=\"current\"}") != null);
|
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,
|
.allocator = allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(allocator, weather);
|
var output_buf: [4096]u8 = undefined;
|
||||||
defer allocator.free(output);
|
var writer = std.Io.Writer.fixed(&output_buf);
|
||||||
|
|
||||||
|
try render(&writer, weather, allocator);
|
||||||
|
|
||||||
|
const output = output_buf[0..writer.end];
|
||||||
|
|
||||||
// Check for HELP comments
|
// Check for HELP comments
|
||||||
try std.testing.expect(std.mem.indexOf(u8, output, "# HELP temperature_celsius Temperature in Celsius") != null);
|
try std.testing.expect(std.mem.indexOf(u8, output, "# HELP temperature_celsius Temperature in Celsius") != null);
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,7 @@ const std = @import("std");
|
||||||
const types = @import("../weather/types.zig");
|
const types = @import("../weather/types.zig");
|
||||||
const utils = @import("utils.zig");
|
const utils = @import("utils.zig");
|
||||||
|
|
||||||
pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, use_imperial: bool) ![]const u8 {
|
pub fn render(writer: *std.Io.Writer, weather: types.WeatherData, use_imperial: bool) !void {
|
||||||
var output: std.ArrayList(u8) = .empty;
|
|
||||||
errdefer output.deinit(allocator);
|
|
||||||
const writer = output.writer(allocator);
|
|
||||||
|
|
||||||
// Header with location
|
// Header with location
|
||||||
try writer.print("Weather report: {s}\n\n", .{weather.locationDisplayName()});
|
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" {
|
test "render v2 format" {
|
||||||
|
|
@ -88,8 +82,12 @@ test "render v2 format" {
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(allocator, weather, false);
|
var output_buf: [2048]u8 = undefined;
|
||||||
defer allocator.free(output);
|
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(output.len > 0);
|
||||||
try std.testing.expect(std.mem.indexOf(u8, output, "Munich") != null);
|
try std.testing.expect(std.mem.indexOf(u8, output, "Munich") != null);
|
||||||
|
|
@ -119,8 +117,12 @@ test "render v2 format with imperial units" {
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = try render(allocator, weather, true);
|
var output_buf: [2048]u8 = undefined;
|
||||||
defer allocator.free(output);
|
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, "50.0°F") != null);
|
||||||
try std.testing.expect(std.mem.indexOf(u8, output, "mph") != null);
|
try std.testing.expect(std.mem.indexOf(u8, output, "mph") != null);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue