Upgrade Zmpl (latest version provides partials), fix mime map memory leak

This commit is contained in:
Bob Farrell 2024-03-03 14:12:35 +00:00
parent ce93abcd65
commit 29f4771264
14 changed files with 36 additions and 83 deletions

View File

@ -12,7 +12,6 @@ pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{}); const optimize = b.standardOptimizeOption(.{});
const template_path = b.option([]const u8, "zmpl_templates_path", "Path to templates") orelse "src/app/views/"; const template_path = b.option([]const u8, "zmpl_templates_path", "Path to templates") orelse "src/app/views/";
const manifest: []const u8 = b.pathJoin(&.{ template_path, "zmpl.manifest.zig" });
const lib = b.addStaticLibrary(.{ const lib = b.addStaticLibrary(.{
.name = "jetzig", .name = "jetzig",
@ -35,7 +34,6 @@ pub fn build(b: *std.Build) !void {
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.zmpl_templates_path = template_path, .zmpl_templates_path = template_path,
.zmpl_manifest_path = manifest,
}, },
); );

View File

@ -3,8 +3,8 @@
.version = "0.0.0", .version = "0.0.0",
.dependencies = .{ .dependencies = .{
.zmpl = .{ .zmpl = .{
.url = "https://github.com/jetzig-framework/zmpl/archive/84a712349e0cf679fc5c9900b805335d51d9ce86.tar.gz", .url = "https://github.com/jetzig-framework/zmpl/archive/aa7147f8a52d927dce6cdd4f5b3bb2de5080f28c.tar.gz",
.hash = "1220e9f2133f6cd24c370850cbe3816e3d8b97c33dd822bf7eaf8f6f0ea2cfd2f8db", .hash = "12203d262b39b2328adb981e41c5127507f3d47e977c1a4e69a96688a4213b986d04",
}, },
}, },

View File

@ -55,13 +55,9 @@ pub fn build(b: *std.Build) !void {
lib.root_module.addImport("routes", routes_module); lib.root_module.addImport("routes", routes_module);
routes_module.addImport("jetzig", jetzig_module); routes_module.addImport("jetzig", jetzig_module);
const templates_module = b.createModule(
.{ .root_source_file = .{ .path = "src/app/views/zmpl.manifest.zig" } },
);
exe_static_routes.root_module.addImport("routes", routes_module); exe_static_routes.root_module.addImport("routes", routes_module);
exe_static_routes.root_module.addImport("jetzig", jetzig_module); exe_static_routes.root_module.addImport("jetzig", jetzig_module);
exe_static_routes.root_module.addImport("templates", templates_module); exe_static_routes.root_module.addImport("zmpl", zmpl_module);
templates_module.addImport("zmpl", zmpl_module);
const run_static_routes_cmd = b.addRunArtifact(exe_static_routes); const run_static_routes_cmd = b.addRunArtifact(exe_static_routes);
exe.step.dependOn(&run_static_routes_cmd.step); exe.step.dependOn(&run_static_routes_cmd.step);

BIN
demo/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,9 @@
<div>
<h1 class="text-3xl text-center p-3 pb-6 font-bold">{.message}</h1>
</div>
<button hx-get="/quotes/random" hx-trigger="click" hx-target="#quote" class="bg-[#39b54a] text-white font-bold py-2 px-4 rounded">Click Me</button>
<div id="quote" class="p-7 mx-auto w-1/2">
<div hx-get="/quotes/init" hx-trigger="load"></div>
</div>

View File

@ -10,15 +10,8 @@
<div class="text-center pt-10 m-auto"> <div class="text-center pt-10 m-auto">
<div><img class="p-3 mx-auto" src="/jetzig.png" /></div> <div><img class="p-3 mx-auto" src="/jetzig.png" /></div>
<div> // Renders `src/app/views/root/_quotes.zmpl`:
<h1 class="text-3xl text-center p-3 pb-6 font-bold">{.message}</h1> <div>{^root/quotes}</div>
</div>
<button hx-get="/quotes/random" hx-trigger="click" hx-target="#quote" class="bg-[#39b54a] text-white font-bold py-2 px-4 rounded">Click Me</button>
<div id="quote" class="p-7 mx-auto w-1/2">
<div hx-get="/quotes/init" hx-trigger="load"></div>
</div>
<div> <div>
<a href="https://github.com/jetzig-framework/zmpl"> <a href="https://github.com/jetzig-framework/zmpl">

View File

@ -1,7 +1,6 @@
const std = @import("std"); const std = @import("std");
pub const jetzig = @import("jetzig"); pub const jetzig = @import("jetzig");
pub const templates = @import("app/views/zmpl.manifest.zig").templates;
pub const routes = @import("routes").routes; pub const routes = @import("routes").routes;
pub const jetzig_options = struct { pub const jetzig_options = struct {
@ -16,8 +15,5 @@ pub fn main() !void {
const app = try jetzig.init(allocator); const app = try jetzig.init(allocator);
defer app.deinit(); defer app.deinit();
try app.start( try app.start(comptime jetzig.route(routes));
comptime jetzig.route(routes),
comptime jetzig.loadTemplates(templates),
);
} }

View File

@ -1,5 +0,0 @@
// Zmpl template manifest.
// This file is automatically generated at build time. Manual edits will be discarded.
// This file should _not_ be stored in version control.
pub const templates = struct {
};

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const jetzig = @import("jetzig"); const jetzig = @import("jetzig");
const routes = @import("routes").routes; const routes = @import("routes").routes;
const templates = @import("templates").templates; const zmpl = @import("zmpl");
pub fn main() !void { pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
@ -94,8 +94,7 @@ fn writeContent(
std.debug.print("[jetzig] Compiled static route: {s}\n", .{json_path}); std.debug.print("[jetzig] Compiled static route: {s}\n", .{json_path});
if (@hasDecl(templates, route.template)) { if (zmpl.find(route.template)) |template| {
const template = @field(templates, route.template);
const html_path = try std.mem.concat( const html_path = try std.mem.concat(
allocator, allocator,
u8, u8,

View File

@ -1,8 +1,7 @@
const std = @import("std"); const std = @import("std");
pub const jetzig = @import("jetzig"); pub const jetzig = @import("jetzig");
pub const templates = @import("app/views/zmpl.manifest.zig").templates; pub const routes = @import("routes").routes;
pub const routes = @import("app/views/routes.zig").routes;
pub fn main() !void { pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
@ -12,8 +11,5 @@ pub fn main() !void {
const app = try jetzig.init(allocator); const app = try jetzig.init(allocator);
defer app.deinit(); defer app.deinit();
try app.start( try app.start(comptime jetzig.route(routes));
comptime jetzig.route(routes),
comptime jetzig.loadTemplates(templates),
);
} }

View File

@ -153,31 +153,6 @@ pub fn route(comptime routes: anytype) []views.Route {
return &detected; return &detected;
} }
// Receives a type (an imported module). All pub const declarations are considered as compiled
// Zmpl templates, each implementing a `render` function.
pub fn loadTemplates(comptime module: type) []TemplateFn {
var size: u16 = 0;
const decls = @typeInfo(module).Struct.decls;
for (decls) |_| size += 1;
var detected: [size]TemplateFn = undefined;
for (decls, 0..) |decl, decl_index| {
detected[decl_index] = .{
.render = @field(module, decl.name).render,
.name = decl.name,
};
}
return &detected;
}
pub const TemplateFn = struct {
name: []const u8,
render: *const fn (*zmpl.Data) anyerror![]const u8,
};
pub fn generateSecret(allocator: std.mem.Allocator) ![]const u8 { pub fn generateSecret(allocator: std.mem.Allocator) ![]const u8 {
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var secret: [64]u8 = undefined; var secret: [64]u8 = undefined;

View File

@ -18,7 +18,7 @@ pub fn deinit(self: Self) void {
/// Starts an application. `routes` should be `@import("routes").routes`, a generated file /// Starts an application. `routes` should be `@import("routes").routes`, a generated file
/// automatically created at build time. `templates` should be /// automatically created at build time. `templates` should be
/// `@import("src/app/views/zmpl.manifest.zig").templates`, created by Zmpl at compile time. /// `@import("src/app/views/zmpl.manifest.zig").templates`, created by Zmpl at compile time.
pub fn start(self: Self, comptime_routes: []jetzig.views.Route, templates: []jetzig.TemplateFn) !void { pub fn start(self: Self, comptime_routes: []jetzig.views.Route) !void {
var mime_map = jetzig.http.mime.MimeMap.init(self.allocator); var mime_map = jetzig.http.mime.MimeMap.init(self.allocator);
defer mime_map.deinit(); defer mime_map.deinit();
try mime_map.build(); try mime_map.build();
@ -54,7 +54,6 @@ pub fn start(self: Self, comptime_routes: []jetzig.views.Route, templates: []jet
self.port, self.port,
self.server_options, self.server_options,
routes.items, routes.items,
templates,
&mime_map, &mime_map,
); );

View File

@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const jetzig = @import("../../jetzig.zig"); const jetzig = @import("../../jetzig.zig");
const zmpl = @import("zmpl");
const root_file = @import("root"); const root_file = @import("root");
@ -24,7 +25,6 @@ logger: jetzig.loggers.Logger,
options: ServerOptions, options: ServerOptions,
start_time: i128 = undefined, start_time: i128 = undefined,
routes: []*jetzig.views.Route, routes: []*jetzig.views.Route,
templates: []jetzig.TemplateFn,
mime_map: *jetzig.http.mime.MimeMap, mime_map: *jetzig.http.mime.MimeMap,
std_net_server: std.net.Server = undefined, std_net_server: std.net.Server = undefined,
@ -36,7 +36,6 @@ pub fn init(
port: u16, port: u16,
options: ServerOptions, options: ServerOptions,
routes: []*jetzig.views.Route, routes: []*jetzig.views.Route,
templates: []jetzig.TemplateFn,
mime_map: *jetzig.http.mime.MimeMap, mime_map: *jetzig.http.mime.MimeMap,
) Self { ) Self {
return .{ return .{
@ -47,7 +46,6 @@ pub fn init(
.logger = options.logger, .logger = options.logger,
.options = options, .options = options,
.routes = routes, .routes = routes,
.templates = templates,
.mime_map = mime_map, .mime_map = mime_map,
}; };
} }
@ -123,7 +121,7 @@ fn renderResponse(self: *Self, request: *jetzig.http.Request) !void {
const rendered = try self.renderInternalServerError(request, err); const rendered = try self.renderInternalServerError(request, err);
request.response.content = rendered.content; request.response.content = rendered.content;
request.response.status_code = .internal_server_error; request.response.status_code = rendered.view.status_code;
request.response.content_type = "text/html"; request.response.content_type = "text/html";
return; return;
@ -155,15 +153,19 @@ fn renderHTML(
route: ?*jetzig.views.Route, route: ?*jetzig.views.Route,
) !void { ) !void {
if (route) |matched_route| { if (route) |matched_route| {
for (self.templates) |template| { if (zmpl.find(matched_route.template)) |template| {
// TODO: Use a hashmap to avoid O(n) const rendered = self.renderView(matched_route, request, template) catch |err| {
if (std.mem.eql(u8, matched_route.template, template.name)) { if (isUnhandledError(err)) return err;
const rendered = try self.renderView(matched_route, request, template); const rendered_error = try self.renderInternalServerError(request, err);
request.response.content = rendered.content; request.response.content = rendered_error.content;
request.response.status_code = rendered.view.status_code; request.response.status_code = rendered_error.view.status_code;
request.response.content_type = "text/html"; request.response.content_type = "text/html";
return; return;
} };
request.response.content = rendered.content;
request.response.status_code = rendered.view.status_code;
request.response.content_type = "text/html";
return;
} }
} }
@ -200,7 +202,7 @@ fn renderView(
self: *Self, self: *Self,
route: *jetzig.views.Route, route: *jetzig.views.Route,
request: *jetzig.http.Request, request: *jetzig.http.Request,
template: ?jetzig.TemplateFn, template: ?zmpl.manifest.Template,
) !RenderedView { ) !RenderedView {
const view = route.render(route.*, request) catch |err| { const view = route.render(route.*, request) catch |err| {
self.logger.debug("Encountered error: {s}", .{@errorName(err)}); self.logger.debug("Encountered error: {s}", .{@errorName(err)});

View File

@ -31,19 +31,14 @@ pub const MimeMap = struct {
} }
pub fn deinit(self: *MimeMap) void { pub fn deinit(self: *MimeMap) void {
var it = self.map.iterator();
while (it.next()) |item| {
self.allocator.free(item.key_ptr.*);
self.allocator.free(item.value_ptr.*);
}
self.map.deinit(); self.map.deinit();
} }
pub fn build(self: *MimeMap) !void { pub fn build(self: *MimeMap) !void {
for (mime_types) |mime_type| { for (mime_types) |mime_type| {
try self.map.put( try self.map.put(
try self.allocator.dupe(u8, mime_type.file_type), mime_type.file_type,
try self.allocator.dupe(u8, mime_type.name), mime_type.name,
); );
} }
} }