implement ansi/text and user-agent based html output
This commit is contained in:
parent
b466acb70a
commit
06b4a6057a
2 changed files with 86 additions and 22 deletions
|
|
@ -168,27 +168,64 @@ fn handleWeatherInternal(
|
||||||
break :blk false;
|
break :blk false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const output = if (params.format) |fmt| blk: {
|
|
||||||
if (std.mem.eql(u8, fmt, "j1")) {
|
|
||||||
res.content_type = .JSON;
|
|
||||||
break :blk try json.render(req_alloc, weather);
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, fmt, "v2"))
|
|
||||||
break :blk try v2.render(req_alloc, weather, use_imperial);
|
|
||||||
if (std.mem.startsWith(u8, fmt, "%"))
|
|
||||||
break :blk try custom.render(req_alloc, weather, fmt, use_imperial);
|
|
||||||
// fall back to line if we don't understant the format parameter
|
|
||||||
break :blk try line.render(req_alloc, weather, fmt, use_imperial);
|
|
||||||
} else try formatted.render(req_alloc, weather, .{ .use_imperial = use_imperial });
|
|
||||||
|
|
||||||
// Add coordinates header using response allocator
|
// Add coordinates header using response allocator
|
||||||
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);
|
||||||
|
|
||||||
if (res.content_type != .JSON)
|
res.body = blk: {
|
||||||
res.content_type = .TEXT;
|
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, "v2"))
|
||||||
|
break :blk try v2.render(req_alloc, weather, use_imperial);
|
||||||
|
if (std.mem.startsWith(u8, fmt, "%"))
|
||||||
|
break :blk try custom.render(req_alloc, weather, fmt, use_imperial);
|
||||||
|
// fall back to line if we don't understand the format parameter
|
||||||
|
break :blk try line.render(req_alloc, weather, fmt, use_imperial);
|
||||||
|
} else {
|
||||||
|
const format: formatted.Format = determineFormat(params, req.headers.get("user-agent"));
|
||||||
|
log.debug(
|
||||||
|
"Format: {}. params.ansi {}, params.text {}, user agent: {?s}",
|
||||||
|
.{ format, params.ansi, params.text_only, req.headers.get("user-agent") },
|
||||||
|
);
|
||||||
|
if (format != .html) res.content_type = .TEXT else res.content_type = .HTML;
|
||||||
|
break :blk try formatted.render(req_alloc, weather, .{ .use_imperial = use_imperial, .format = format });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
res.body = output;
|
fn determineFormat(params: QueryParams, user_agent: ?[]const u8) formatted.Format {
|
||||||
|
if (params.ansi or params.text_only) {
|
||||||
|
// user explicitly requested something. If both are set, text will win
|
||||||
|
if (params.text_only) return .plain_text;
|
||||||
|
return .ansi;
|
||||||
|
}
|
||||||
|
const ua = user_agent orelse "";
|
||||||
|
// https://github.com/chubin/wttr.in/blob/master/lib/globals.py#L82C1-L97C2
|
||||||
|
const plain_text_agents = &[_][]const u8{
|
||||||
|
"curl",
|
||||||
|
"httpie",
|
||||||
|
"lwp-request",
|
||||||
|
"wget",
|
||||||
|
"python-requests",
|
||||||
|
"python-httpx",
|
||||||
|
"openbsd ftp",
|
||||||
|
"powershell",
|
||||||
|
"fetch",
|
||||||
|
"aiohttp",
|
||||||
|
"http_get",
|
||||||
|
"xh",
|
||||||
|
"nushell",
|
||||||
|
"zig",
|
||||||
|
};
|
||||||
|
for (plain_text_agents) |agent|
|
||||||
|
if (std.mem.indexOf(u8, ua, agent)) |_|
|
||||||
|
return .ansi;
|
||||||
|
return .html;
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parseXForwardedFor extracts first IP" {
|
test "parseXForwardedFor extracts first IP" {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,33 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
///Units:
|
||||||
|
///
|
||||||
|
/// m # metric (SI) (used by default everywhere except US)
|
||||||
|
/// u # USCS (used by default in US)
|
||||||
|
/// M # show wind speed in m/s
|
||||||
|
///
|
||||||
|
///View options:
|
||||||
|
///
|
||||||
|
/// 0 # only current weather
|
||||||
|
/// 1 # current weather + today's forecast
|
||||||
|
/// 2 # current weather + today's + tomorrow's forecast
|
||||||
|
/// A # ignore User-Agent and force ANSI output format (terminal)
|
||||||
|
/// d # restrict output to standard console font glyphs
|
||||||
|
/// F # do not show the "Follow" line
|
||||||
|
/// n # narrow version (only day and night)
|
||||||
|
/// q # quiet version (no "Weather report" text)
|
||||||
|
/// Q # superquiet version (no "Weather report", no city name)
|
||||||
|
/// T # switch terminal sequences off (no colors)
|
||||||
pub const QueryParams = struct {
|
pub const QueryParams = struct {
|
||||||
format: ?[]const u8 = null,
|
format: ?[]const u8 = null,
|
||||||
lang: ?[]const u8 = null,
|
lang: ?[]const u8 = null,
|
||||||
location: ?[]const u8 = null,
|
location: ?[]const u8 = null,
|
||||||
units: ?Units = null,
|
units: ?Units = null,
|
||||||
transparency: ?u8 = null,
|
transparency: ?u8 = null,
|
||||||
|
/// A: Ignore user agent and force ansi mode
|
||||||
|
ansi: bool = false,
|
||||||
|
/// T: Avoid terminal sequences and just output plain text
|
||||||
|
text_only: bool = false,
|
||||||
|
|
||||||
pub const Units = enum {
|
pub const Units = enum {
|
||||||
metric,
|
metric,
|
||||||
|
|
@ -23,16 +45,22 @@ pub const QueryParams = struct {
|
||||||
const key = kv.next() orelse continue;
|
const key = kv.next() orelse continue;
|
||||||
const value = kv.next();
|
const value = kv.next();
|
||||||
|
|
||||||
|
if (key.len == 1) {
|
||||||
|
switch (key[0]) {
|
||||||
|
'u' => params.units = .uscs,
|
||||||
|
'm' => params.units = .metric,
|
||||||
|
'A' => params.ansi = true,
|
||||||
|
'T' => params.text_only = true,
|
||||||
|
't' => params.transparency = 150,
|
||||||
|
else => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
if (std.mem.eql(u8, key, "format")) {
|
if (std.mem.eql(u8, key, "format")) {
|
||||||
params.format = if (value) |v| try allocator.dupe(u8, v) else null;
|
params.format = if (value) |v| try allocator.dupe(u8, v) else null;
|
||||||
} else if (std.mem.eql(u8, key, "lang")) {
|
} else if (std.mem.eql(u8, key, "lang")) {
|
||||||
params.lang = if (value) |v| try allocator.dupe(u8, v) else null;
|
params.lang = if (value) |v| try allocator.dupe(u8, v) else null;
|
||||||
} else if (std.mem.eql(u8, key, "location")) {
|
} else if (std.mem.eql(u8, key, "location")) {
|
||||||
params.location = if (value) |v| try allocator.dupe(u8, v) else null;
|
params.location = if (value) |v| try allocator.dupe(u8, v) else null;
|
||||||
} else if (std.mem.eql(u8, key, "u")) {
|
|
||||||
params.units = .uscs;
|
|
||||||
} else if (std.mem.eql(u8, key, "m")) {
|
|
||||||
params.units = .metric;
|
|
||||||
} else if (std.mem.eql(u8, key, "use_imperial")) {
|
} else if (std.mem.eql(u8, key, "use_imperial")) {
|
||||||
params.units = .uscs;
|
params.units = .uscs;
|
||||||
} else if (std.mem.eql(u8, key, "use_metric")) {
|
} else if (std.mem.eql(u8, key, "use_metric")) {
|
||||||
|
|
@ -41,8 +69,6 @@ pub const QueryParams = struct {
|
||||||
if (value) |v| {
|
if (value) |v| {
|
||||||
params.transparency = try std.fmt.parseInt(u8, v, 10);
|
params.transparency = try std.fmt.parseInt(u8, v, 10);
|
||||||
}
|
}
|
||||||
} else if (std.mem.eql(u8, key, "t")) {
|
|
||||||
params.transparency = 150;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,6 +134,7 @@ test "parse multiple parameters" {
|
||||||
test "parse transparency" {
|
test "parse transparency" {
|
||||||
const allocator = std.testing.allocator;
|
const allocator = std.testing.allocator;
|
||||||
const params_t = try QueryParams.parse(allocator, "t");
|
const params_t = try QueryParams.parse(allocator, "t");
|
||||||
|
try std.testing.expect(params_t.transparency != null);
|
||||||
try std.testing.expectEqual(@as(u8, 150), params_t.transparency.?);
|
try std.testing.expectEqual(@as(u8, 150), params_t.transparency.?);
|
||||||
|
|
||||||
const params_custom = try QueryParams.parse(allocator, "transparency=200");
|
const params_custom = try QueryParams.parse(allocator, "transparency=200");
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue