From 625257bcfc2997087ee7851daccf432adf81459c Mon Sep 17 00:00:00 2001 From: Bob Farrell Date: Wed, 27 Mar 2024 21:30:38 +0000 Subject: [PATCH] Allow auto-routing to markdown files If a route isn't matched but the URI can match directly to a markdown file, render it directly. This allows putting arbitrary markdown files in e.g. `/foo/bar/abc.md`, `/foo/bar/def.md` and rendering them as `/foo/bar/abc.html`, `/foo/bar/def.html`. Since markdown are static content only (i.e. no template data etc.) it makes sense for them to be renderable without needing to create a view for each one. --- src/jetzig/http/Server.zig | 85 ++++++++++++++++++++++++-------------- src/jetzig/markdown.zig | 5 +-- 2 files changed, 57 insertions(+), 33 deletions(-) diff --git a/src/jetzig/http/Server.zig b/src/jetzig/http/Server.zig index e8c86c3..2658b7b 100644 --- a/src/jetzig/http/Server.zig +++ b/src/jetzig/http/Server.zig @@ -157,39 +157,11 @@ fn renderHTML( }; setResponse(request, rendered, .{}); return; - } else if (try jetzig.markdown.render(request.allocator, matched_route, null)) |markdown_content| { - const rendered = self.renderView(matched_route, request, null) catch |err| { - if (isUnhandledError(err)) return err; - const rendered_error = try self.renderInternalServerError(request, err); - setResponse(request, rendered_error, .{}); - return; - }; - - try addTemplateConstants(rendered.view, matched_route); - - if (request.getLayout(matched_route)) |layout_name| { - // TODO: Allow user to configure layouts directory other than src/app/views/layouts/ - const prefixed_name = try std.mem.concat( - self.allocator, - u8, - &[_][]const u8{ "layouts_", layout_name }, - ); - defer self.allocator.free(prefixed_name); - - if (zmpl.manifest.find(prefixed_name)) |layout| { - rendered.view.data.content = .{ .data = markdown_content }; - request.response.content = try layout.render(rendered.view.data); - } else { - try self.logger.WARN("Unknown layout: {s}", .{layout_name}); - request.response.content = markdown_content; - } - } - request.response.status_code = rendered.view.status_code; - request.response.content_type = "text/html"; - return; } } + if (try self.renderMarkdown(request, route)) return; + request.response.content = ""; request.response.status_code = .not_found; request.response.content_type = "text/html"; @@ -217,6 +189,59 @@ fn renderJSON( } } +fn renderMarkdown(self: *Self, request: *jetzig.http.Request, maybe_route: ?*jetzig.views.Route) !bool { + const route = maybe_route orelse { + if (request.method != .GET) return false; + const content = try jetzig.markdown.render(request.allocator, request.path.base_path, null) orelse + return false; + + const rendered: RenderedView = .{ + .view = jetzig.views.View{ .data = request.response_data, .status_code = .ok }, + .content = content, + }; + setResponse(request, rendered, .{}); + return true; + }; + + const path = try std.mem.join( + request.allocator, + "/", + &[_][]const u8{ route.uri_path, @tagName(route.action) }, + ); + const markdown_content = try jetzig.markdown.render(request.allocator, path, null) orelse + return false; + + const rendered = self.renderView(route, request, null) catch |err| { + if (isUnhandledError(err)) return err; + const rendered_error = try self.renderInternalServerError(request, err); + setResponse(request, rendered_error, .{}); + return true; + }; + + try addTemplateConstants(rendered.view, route); + + if (request.getLayout(route)) |layout_name| { + // TODO: Allow user to configure layouts directory other than src/app/views/layouts/ + const prefixed_name = try std.mem.concat( + self.allocator, + u8, + &[_][]const u8{ "layouts_", layout_name }, + ); + defer self.allocator.free(prefixed_name); + + if (zmpl.manifest.find(prefixed_name)) |layout| { + rendered.view.data.content = .{ .data = markdown_content }; + request.response.content = try layout.render(rendered.view.data); + } else { + try self.logger.WARN("Unknown layout: {s}", .{layout_name}); + request.response.content = markdown_content; + } + } + request.response.status_code = rendered.view.status_code; + request.response.content_type = "text/html"; + return true; +} + const RenderedView = struct { view: jetzig.views.View, content: []const u8 }; fn renderView( diff --git a/src/jetzig/markdown.zig b/src/jetzig/markdown.zig index 96105ce..1e92410 100644 --- a/src/jetzig/markdown.zig +++ b/src/jetzig/markdown.zig @@ -5,7 +5,7 @@ const jetzig = @import("../jetzig.zig"); const Zmd = @import("zmd").Zmd; pub fn render( allocator: std.mem.Allocator, - route: *const jetzig.views.Route, + path: []const u8, custom_fragments: ?type, ) !?[]const u8 { const fragments = custom_fragments orelse jetzig.config.get(type, "markdown_fragments"); @@ -15,11 +15,10 @@ pub fn render( try path_buf.appendSlice(&[_][]const u8{ "src", "app", "views" }); - var it = std.mem.splitScalar(u8, route.uri_path, '/'); + var it = std.mem.splitScalar(u8, path, '/'); while (it.next()) |segment| { try path_buf.append(segment); } - try path_buf.append(@tagName(route.action)); const base_path = try std.fs.path.join(allocator, path_buf.items); defer allocator.free(base_path);