partial changes for png generation
This commit is contained in:
parent
148bd862b5
commit
d0f08aacfa
4 changed files with 114 additions and 39 deletions
25
build.zig
25
build.zig
|
|
@ -16,6 +16,22 @@ pub fn build(b: *std.Build) void {
|
|||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const zigimg = b.dependency("zigimg", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const freetype = b.dependency("ghostty", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
}).builder.dependency("freetype", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const jetbrains_mono = b.dependency("jetbrains_mono", .{});
|
||||
const nerd_fonts = b.dependency("nerd_fonts_symbols_only", .{});
|
||||
|
||||
const openflights = b.dependency("openflights", .{});
|
||||
|
||||
const maxminddb_upstream = b.dependency("maxminddb", .{});
|
||||
|
|
@ -113,9 +129,17 @@ pub fn build(b: *std.Build) void {
|
|||
});
|
||||
root_module.addImport("httpz", httpz.module("httpz"));
|
||||
root_module.addImport("zeit", zeit.module("zeit"));
|
||||
root_module.addImport("zigimg", zigimg.module("zigimg"));
|
||||
root_module.addImport("freetype", freetype.module("freetype"));
|
||||
root_module.addAnonymousImport("airports.dat", .{
|
||||
.root_source_file = openflights.path("data/airports.dat"),
|
||||
});
|
||||
root_module.addAnonymousImport("JetBrainsMono-Regular.ttf", .{
|
||||
.root_source_file = jetbrains_mono.path("fonts/ttf/JetBrainsMono-Regular.ttf"),
|
||||
});
|
||||
root_module.addAnonymousImport("SymbolsNerdFont-Regular.ttf", .{
|
||||
.root_source_file = nerd_fonts.path("SymbolsNerdFont-Regular.ttf"),
|
||||
});
|
||||
root_module.addOptions("build_options", build_options);
|
||||
root_module.addIncludePath(maxminddb_upstream.path("include"));
|
||||
root_module.addIncludePath(b.path("libs/phoon_14Aug2014"));
|
||||
|
|
@ -125,6 +149,7 @@ pub fn build(b: *std.Build) void {
|
|||
maxminddb,
|
||||
phoon,
|
||||
sunriset,
|
||||
freetype.artifact("freetype"),
|
||||
};
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "wttr",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,22 @@
|
|||
},
|
||||
.phoon = .{ .path = "libs/phoon_14Aug2014" },
|
||||
.sunriset = .{ .path = "libs/sunriset" },
|
||||
.ghostty = .{
|
||||
.url = "git+https://github.com/ghostty-org/ghostty#ec2912dbafe50cc32b786d2327dcd0213c83ecc6",
|
||||
.hash = "ghostty-1.3.0-dev-5UdBC_y2RASwYWn5fjn71WsP-arlg8wSICLc0rYiozdf",
|
||||
},
|
||||
.zigimg = .{
|
||||
.url = "git+https://github.com/zigimg/zigimg#9714df09f76891323c7fdbbbf23a17b79024fffb",
|
||||
.hash = "zigimg-0.1.0-8_eo2j4mFwCU7tWnqvkYtzqe-OPRn_bxEql_IJhW85LT",
|
||||
},
|
||||
.jetbrains_mono = .{
|
||||
.url = "https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz",
|
||||
.hash = "N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x",
|
||||
},
|
||||
.nerd_fonts_symbols_only = .{
|
||||
.url = "https://deps.files.ghostty.org/NerdFontsSymbolsOnly-3.4.0.tar.gz",
|
||||
.hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO26s",
|
||||
},
|
||||
},
|
||||
.fingerprint = 0x710c2b57e81aa678,
|
||||
.minimum_zig_version = "0.15.2",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ format: ?[]const u8 = null,
|
|||
lang: ?[]const u8 = null,
|
||||
location: ?[]const u8 = null,
|
||||
transparency: ?u8 = null,
|
||||
background: ?[]const u8 = null,
|
||||
add_frame: bool = false,
|
||||
/// A: Ignore user agent and force ansi mode
|
||||
ansi: bool = false,
|
||||
/// T: Avoid terminal sequences and just output plain text
|
||||
|
|
@ -60,6 +62,7 @@ pub fn parse(allocator: std.mem.Allocator, query_string: []const u8) !QueryParam
|
|||
'A' => params.ansi = true,
|
||||
'T' => params.text_only = true,
|
||||
't' => params.transparency = 150,
|
||||
'p' => params.add_frame = true,
|
||||
else => continue,
|
||||
}
|
||||
}
|
||||
|
|
@ -77,6 +80,8 @@ pub fn parse(allocator: std.mem.Allocator, query_string: []const u8) !QueryParam
|
|||
if (value) |v| {
|
||||
params.transparency = try std.fmt.parseInt(u8, v, 10);
|
||||
}
|
||||
} else if (std.mem.eql(u8, key, "background")) {
|
||||
params.background = if (value) |v| try allocator.dupe(u8, v) else null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ const Json = @import("../render/Json.zig");
|
|||
const V2 = @import("../render/V2.zig");
|
||||
const Custom = @import("../render/Custom.zig");
|
||||
const Prometheus = @import("../render/Prometheus.zig");
|
||||
const Png = @import("../render/Png.zig");
|
||||
const help = @import("help.zig");
|
||||
const types = @import("../weather/types.zig");
|
||||
|
||||
const log = std.log.scoped(.handler);
|
||||
|
||||
|
|
@ -33,12 +35,14 @@ pub fn handleWeather(
|
|||
defer {
|
||||
if (params.format) |f| req.arena.free(f);
|
||||
if (params.lang) |l| req.arena.free(l);
|
||||
if (params.background) |b| req.arena.free(b);
|
||||
}
|
||||
|
||||
if (params.location) |loc| {
|
||||
break :blk loc;
|
||||
} else break :blk client_ip; // no location, just use client ip instead
|
||||
};
|
||||
|
||||
if (std.mem.eql(u8, "favicon.ico", location)) {
|
||||
res.header("Content-Type", "image/x-icon");
|
||||
res.body = @embedFile("favicon.ico");
|
||||
|
|
@ -75,16 +79,20 @@ fn handleWeatherInternal(
|
|||
) !void {
|
||||
const req_alloc = req.arena;
|
||||
|
||||
// Check for PNG request
|
||||
const is_png = std.mem.endsWith(u8, location_query, ".png");
|
||||
const location_str = if (is_png) location_query[0 .. location_query.len - 4] else location_query;
|
||||
|
||||
// Resolve location. By the time we get here, we really
|
||||
// should have a location from the path, query string, or
|
||||
// client IP lookup. So if we have an empty location parameter, it
|
||||
// is better to 404 than to fake it with a London response
|
||||
if (location_query.len == 0) {
|
||||
if (location_str.len == 0) {
|
||||
res.status = 404;
|
||||
res.body = "Location not found\n";
|
||||
return;
|
||||
}
|
||||
const location = opts.resolver.resolve(location_query) catch |err| {
|
||||
const location = opts.resolver.resolve(location_str) catch |err| {
|
||||
switch (err) {
|
||||
error.LocationNotFound => {
|
||||
log.debug("Location not found for query {s}", .{location_query});
|
||||
|
|
@ -118,6 +126,7 @@ fn handleWeatherInternal(
|
|||
defer {
|
||||
if (params.format) |f| req_alloc.free(f);
|
||||
if (params.lang) |l| req_alloc.free(l);
|
||||
if (params.background) |b| req_alloc.free(b);
|
||||
}
|
||||
|
||||
var render_options = params.render_options;
|
||||
|
|
@ -140,50 +149,70 @@ 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);
|
||||
|
||||
// Render weather data
|
||||
if (is_png) {
|
||||
res.content_type = .PNG;
|
||||
var png_renderer = Png.init(req_alloc);
|
||||
defer png_renderer.deinit();
|
||||
|
||||
var png_buffer: [1024 * 1024]u8 = undefined;
|
||||
var png_writer_impl = std.Io.Writer.fixed(&png_buffer);
|
||||
const png_writer = &png_writer_impl;
|
||||
|
||||
render_options.format = .ansi; // Force ANSI for PNG
|
||||
try renderWeatherData(png_writer, weather, params, render_options);
|
||||
|
||||
const text_output = png_buffer[0..png_writer_impl.end];
|
||||
try png_renderer.buffer.appendSlice(req_alloc, text_output);
|
||||
|
||||
const png_options = Png.PngOptions{
|
||||
.transparency = params.transparency orelse 150,
|
||||
.background = params.background,
|
||||
.add_frame = params.add_frame,
|
||||
};
|
||||
try png_renderer.render(res.writer(), png_options);
|
||||
return;
|
||||
}
|
||||
// Set content type based on format
|
||||
if (params.format) |fmt| {
|
||||
// Anything except the json will be plain text
|
||||
res.content_type = .TEXT;
|
||||
if (std.mem.eql(u8, fmt, "1")) {
|
||||
try Line.render(res.writer(), weather, .@"1", render_options.use_imperial);
|
||||
return;
|
||||
}
|
||||
if (std.mem.eql(u8, fmt, "2")) {
|
||||
try Line.render(res.writer(), weather, .@"2", render_options.use_imperial);
|
||||
return;
|
||||
}
|
||||
if (std.mem.eql(u8, fmt, "3")) {
|
||||
try Line.render(res.writer(), weather, .@"3", render_options.use_imperial);
|
||||
return;
|
||||
}
|
||||
if (std.mem.eql(u8, fmt, "4")) {
|
||||
try Line.render(res.writer(), weather, .@"4", render_options.use_imperial);
|
||||
return;
|
||||
}
|
||||
if (std.mem.eql(u8, fmt, "j1")) {
|
||||
res.content_type = .JSON; // reset to json
|
||||
try Json.render(res.writer(), weather);
|
||||
return;
|
||||
}
|
||||
if (std.mem.eql(u8, fmt, "p1")) {
|
||||
try Prometheus.render(res.writer(), weather);
|
||||
return;
|
||||
}
|
||||
if (std.mem.eql(u8, fmt, "v2")) {
|
||||
try V2.render(res.writer(), weather, render_options.use_imperial);
|
||||
return;
|
||||
}
|
||||
// Everything else goes to Custom renderer
|
||||
try Custom.render(res.writer(), weather, fmt, render_options.use_imperial);
|
||||
res.content_type = if (std.mem.eql(u8, fmt, "j1")) .JSON else .TEXT;
|
||||
} 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);
|
||||
res.content_type = if (render_options.format == .html) .HTML else .TEXT;
|
||||
}
|
||||
try renderWeatherData(res.writer(), weather, params, render_options);
|
||||
}
|
||||
|
||||
fn renderWeatherData(
|
||||
writer: *std.Io.Writer,
|
||||
weather: types.WeatherData,
|
||||
params: QueryParams,
|
||||
render_options: Formatted.RenderOptions,
|
||||
) !void {
|
||||
if (params.format) |fmt| {
|
||||
if (std.mem.eql(u8, fmt, "1")) {
|
||||
try Line.render(writer, weather, .@"1", render_options.use_imperial);
|
||||
} else if (std.mem.eql(u8, fmt, "2")) {
|
||||
try Line.render(writer, weather, .@"2", render_options.use_imperial);
|
||||
} else if (std.mem.eql(u8, fmt, "3")) {
|
||||
try Line.render(writer, weather, .@"3", render_options.use_imperial);
|
||||
} else if (std.mem.eql(u8, fmt, "4")) {
|
||||
try Line.render(writer, weather, .@"4", render_options.use_imperial);
|
||||
} else if (std.mem.eql(u8, fmt, "j1")) {
|
||||
try Json.render(writer, weather);
|
||||
} else if (std.mem.eql(u8, fmt, "p1")) {
|
||||
try Prometheus.render(writer, weather);
|
||||
} else if (std.mem.eql(u8, fmt, "v2")) {
|
||||
try V2.render(writer, weather, render_options.use_imperial);
|
||||
} else {
|
||||
try Custom.render(writer, weather, fmt, render_options.use_imperial);
|
||||
}
|
||||
} else {
|
||||
try Formatted.render(writer, weather, render_options);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue