Merge pull request #44 from jetzig-framework/nested-routes

Implement nested routes
This commit is contained in:
bobf 2024-03-27 20:16:23 +00:00 committed by GitHub
commit d368850b1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 71 additions and 46 deletions

View File

@ -0,0 +1,22 @@
const std = @import("std");
const jetzig = @import("jetzig");
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
return request.render(.ok);
}
pub const static_params = .{
.get = .{
.{ .id = "foo", .params = .{ .foo = "bar" } },
.{ .id = "foo" },
},
};
pub fn get(id: []const u8, request: *jetzig.StaticRequest, data: *jetzig.Data) !jetzig.View {
var object = try data.object();
try object.put("id", data.string(id));
const params = try request.params();
if (params.get("foo")) |value| try object.put("foo", value);
return request.render(.ok);
}

View File

@ -0,0 +1,3 @@
<div>
<span>Content goes here</span>
</div>

View File

@ -1,10 +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 {
pub const static_index = @import("static/.index.zmpl.compiled.zig");
pub const static_get = @import("static/.get.zmpl.compiled.zig");
pub const root_index = @import("root/.index.zmpl.compiled.zig");
pub const quotes_post = @import("quotes/.post.zmpl.compiled.zig");
pub const quotes_get = @import("quotes/.get.zmpl.compiled.zig");
};

View File

@ -23,23 +23,27 @@ const Function = struct {
/// compilation.
/// path: `src/app/views/iguanas.zig`, action: `index` => `iguanas_index`
pub fn fullName(self: @This(), allocator: std.mem.Allocator) ![]const u8 {
// XXX: Currently we do not support nested routes, so we will need to adjust this if we
// add nested routes in future.
const extension = std.fs.path.extension(self.path);
const basename = std.fs.path.basename(self.path);
const name = basename[0 .. basename.len - extension.len];
const relative_path = try std.fs.path.relative(allocator, "src/app/views/", self.path);
defer allocator.free(relative_path);
return std.mem.concat(allocator, u8, &[_][]const u8{ name, "_", self.name });
const path = relative_path[0 .. relative_path.len - std.fs.path.extension(relative_path).len];
std.mem.replaceScalar(u8, path, '\\', '/');
std.mem.replaceScalar(u8, path, '/', '_');
return std.mem.concat(allocator, u8, &[_][]const u8{ path, "_", self.name });
}
/// The path used to match the route. Resource ID and extension is not included here and is
/// appended as needed during matching logic at run time.
pub fn uriPath(self: @This(), allocator: std.mem.Allocator) ![]const u8 {
const basename = std.fs.path.basename(self.path);
const name = basename[0 .. basename.len - std.fs.path.extension(basename).len];
if (std.mem.eql(u8, name, "root")) return try allocator.dupe(u8, "/");
const relative_path = try std.fs.path.relative(allocator, "src/app/views/", self.path);
defer allocator.free(relative_path);
return try std.mem.concat(allocator, u8, &[_][]const u8{ "/", name });
const path = relative_path[0 .. relative_path.len - std.fs.path.extension(relative_path).len];
std.mem.replaceScalar(u8, path, '\\', '/');
if (std.mem.eql(u8, path, "root")) return try allocator.dupe(u8, "/");
return try std.mem.concat(allocator, u8, &[_][]const u8{ "/", path });
}
pub fn lessThanFn(context: void, lhs: @This(), rhs: @This()) bool {
@ -247,6 +251,7 @@ fn generateRoutesForView(self: *Self, views_dir: std.fs.Dir, path: []const u8) !
.simple_var_decl => {
const decl = self.ast.simpleVarDecl(asNodeIndex(index));
if (self.isStaticParamsDecl(decl)) {
self.data.reset();
const params = try self.data.object();
try self.parseStaticParamsDecl(decl, params);
static_params = self.data.value;

View File

@ -62,6 +62,7 @@ pub fn init(allocator: std.mem.Allocator) Environment {
/// Generate server initialization options using command line args with defaults.
pub fn getServerOptions(self: Environment) !jetzig.http.Server.ServerOptions {
const options = try args.parseForCurrentProcess(Options, self.allocator, .print);
defer options.deinit();
if (options.options.help) {
const writer = std.io.getStdErr().writer();

View File

@ -473,32 +473,31 @@ fn staticPath(request: *jetzig.http.Request, route: jetzig.views.Route) !?[]cons
};
for (route.params.items, 0..) |static_params, index| {
if (try static_params.getValue("params")) |expected_params| {
switch (route.action) {
.index, .post => {},
inline else => {
if (try static_params.getValue("id")) |id| {
switch (id.*) {
.string => |capture| {
if (!std.mem.eql(u8, capture.value, request.path.resource_id)) continue;
},
// Should be unreachable - this means generated `routes.zig` is incoherent:
inline else => return error.JetzigRouteError,
}
}
},
}
if (!expected_params.eql(params)) continue;
const index_fmt = try std.fmt.allocPrint(request.allocator, "{}", .{index});
defer request.allocator.free(index_fmt);
return try std.mem.concat(
request.allocator,
u8,
&[_][]const u8{ route.name, "_", index_fmt, extension },
);
const expected_params = try static_params.getValue("params");
switch (route.action) {
.index, .post => {},
inline else => {
const id = try static_params.getValue("id");
if (id == null) return error.JetzigRouteError; // `routes.zig` is incoherent.
switch (id.?.*) {
.string => |capture| {
if (!std.mem.eql(u8, capture.value, request.path.resource_id)) continue;
},
// `routes.zig` is incoherent.
inline else => return error.JetzigRouteError,
}
},
}
if (expected_params != null and !expected_params.?.eql(params)) continue;
const index_fmt = try std.fmt.allocPrint(request.allocator, "{}", .{index});
defer request.allocator.free(index_fmt);
return try std.mem.concat(
request.allocator,
u8,
&[_][]const u8{ route.name, "_", index_fmt, extension },
);
}
switch (route.action) {

View File

@ -27,7 +27,12 @@ pub fn render(
const full_path = try std.mem.concat(allocator, u8, &[_][]const u8{ base_path, ".md" });
defer allocator.free(full_path);
const stat = try std.fs.cwd().statFile(full_path);
const stat = std.fs.cwd().statFile(full_path) catch |err| {
return switch (err) {
error.FileNotFound => null,
else => err,
};
};
const markdown_content = std.fs.cwd().readFileAlloc(allocator, full_path, stat.size) catch |err| {
switch (err) {
error.FileNotFound => return null,