diff --git a/demo/src/app/views/nested/route/example.zig b/demo/src/app/views/nested/route/example.zig
new file mode 100644
index 0000000..c7a1eac
--- /dev/null
+++ b/demo/src/app/views/nested/route/example.zig
@@ -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);
+}
diff --git a/demo/src/app/views/nested/route/example/index.zmpl b/demo/src/app/views/nested/route/example/index.zmpl
new file mode 100644
index 0000000..76457d0
--- /dev/null
+++ b/demo/src/app/views/nested/route/example/index.zmpl
@@ -0,0 +1,3 @@
+
+ Content goes here
+
diff --git a/demo/src/app/views/zmpl.manifest.zig b/demo/src/app/views/zmpl.manifest.zig
deleted file mode 100644
index eb2409b..0000000
--- a/demo/src/app/views/zmpl.manifest.zig
+++ /dev/null
@@ -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");
-};
diff --git a/src/GenerateRoutes.zig b/src/GenerateRoutes.zig
index 23026cf..3198e2b 100644
--- a/src/GenerateRoutes.zig
+++ b/src/GenerateRoutes.zig
@@ -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;
diff --git a/src/jetzig/Environment.zig b/src/jetzig/Environment.zig
index be8f842..a822573 100644
--- a/src/jetzig/Environment.zig
+++ b/src/jetzig/Environment.zig
@@ -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();
diff --git a/src/jetzig/http/Server.zig b/src/jetzig/http/Server.zig
index dbc270d..e8c86c3 100644
--- a/src/jetzig/http/Server.zig
+++ b/src/jetzig/http/Server.zig
@@ -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) {
diff --git a/src/jetzig/markdown.zig b/src/jetzig/markdown.zig
index ce9f29c..96105ce 100644
--- a/src/jetzig/markdown.zig
+++ b/src/jetzig/markdown.zig
@@ -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,