From a78d53a19a8ae3ef144737982b4fba8eda4425ae Mon Sep 17 00:00:00 2001 From: Bob Farrell Date: Sun, 10 Mar 2024 14:10:53 +0000 Subject: [PATCH] Allow importing .zig files from src/ in views Views are copied to zig-cache, meaning that any files in `src/` are not available for `@import` by default. Copy all .zig files from `src/` into zig-cache so that relative imports work as expected. This also removes the need to specifically copy views into zig-cache as the full tree is now present. Also fix a bug in static route fallback - remove superfluous underscore so static routes with non-matching params render default HTML/JSON content. --- build.zig | 22 +++++++++++++++++-- demo/src/app/lib/example.zig | 4 ++-- demo/src/app/views/root.zig | 5 ++++- demo/src/app/views/static.zig | 1 + src/GenerateRoutes.zig | 41 +++++++++++++++-------------------- src/jetzig.zig | 4 ---- src/jetzig/http/Server.zig | 4 +++- 7 files changed, 47 insertions(+), 34 deletions(-) diff --git a/build.zig b/build.zig index 2b2db84..82532bc 100644 --- a/build.zig +++ b/build.zig @@ -90,10 +90,28 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn try generate_routes.generateRoutes(); const write_files = b.addWriteFiles(); const routes_file = write_files.add("routes.zig", generate_routes.buffer.items); - for (generate_routes.static_routes.items) |route| _ = write_files.add(route.path, route.source); - for (generate_routes.dynamic_routes.items) |route| _ = write_files.add(route.path, route.source); const routes_module = b.createModule(.{ .root_source_file = routes_file }); + var src_dir = try std.fs.openDirAbsolute(b.pathFromRoot("src"), .{ .iterate = true }); + defer src_dir.close(); + var walker = try src_dir.walk(b.allocator); + defer walker.deinit(); + + while (try walker.next()) |entry| { + if (entry.kind == .file) { + if (!std.mem.eql(u8, ".zig", std.fs.path.extension(entry.path))) continue; + + const stat = try src_dir.statFile(entry.path); + const src_data = try src_dir.readFileAlloc(b.allocator, entry.path, stat.size); + defer b.allocator.free(src_data); + + const relpath = try std.fs.path.join(b.allocator, &[_][]const u8{ "src", entry.path }); + defer b.allocator.free(relpath); + + _ = write_files.add(relpath, src_data); + } + } + const exe_static_routes = b.addExecutable(.{ .name = "static", .root_source_file = jetzig_dep.path("src/compile_static_routes.zig"), diff --git a/demo/src/app/lib/example.zig b/demo/src/app/lib/example.zig index eb10c1f..7c32b28 100644 --- a/demo/src/app/lib/example.zig +++ b/demo/src/app/lib/example.zig @@ -1,3 +1,3 @@ -pub fn exampleFunction() []const u8 { - return "example value"; +pub fn exampleFunction(a: i64, b: i64, c: i64) i64 { + return a * b * c; } diff --git a/demo/src/app/views/root.zig b/demo/src/app/views/root.zig index 1353b9e..e12ef72 100644 --- a/demo/src/app/views/root.zig +++ b/demo/src/app/views/root.zig @@ -1,9 +1,12 @@ const jetzig = @import("jetzig"); +const importedFunction = @import("../lib/example.zig").exampleFunction; + pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View { var root = try data.object(); try root.put("message", data.string("Welcome to Jetzig!")); - try root.put("number", data.integer(customFunction(100, 200, 300))); + try root.put("custom_number", data.integer(customFunction(100, 200, 300))); + try root.put("imported_number", data.integer(importedFunction(100, 200, 300))); try request.response.headers.append("x-example-header", "example header value"); diff --git a/demo/src/app/views/static.zig b/demo/src/app/views/static.zig index e0d3b1c..1acd8ba 100644 --- a/demo/src/app/views/static.zig +++ b/demo/src/app/views/static.zig @@ -48,6 +48,7 @@ pub fn index(request: *jetzig.StaticRequest, data: *jetzig.Data) !jetzig.View { const params = try request.params(); if (params.get("foo")) |foo| try root.put("foo", foo); + if (params.get("bar")) |foo| try root.put("bar", foo); return request.render(.ok); } diff --git a/src/GenerateRoutes.zig b/src/GenerateRoutes.zig index 119f116..3b3636f 100644 --- a/src/GenerateRoutes.zig +++ b/src/GenerateRoutes.zig @@ -18,30 +18,27 @@ const Function = struct { source: []const u8, params: std.ArrayList([]const u8), + /// The full name of a route. This **must** match the naming convention used by static route + /// compilation. + /// path: `src/app/views/iguanas.zig`, action: `index` => `iguanas_index` pub fn fullName(self: @This(), allocator: std.mem.Allocator) ![]const u8 { - var path = try allocator.dupe(u8, self.path); - const extension = std.fs.path.extension(path); - defer allocator.free(path); - std.mem.replaceScalar(u8, path, std.fs.path.sep, '_'); - return std.mem.concat( - allocator, - u8, - &[_][]const u8{ path[0 .. path.len - extension.len], "_", self.name }, - ); + // 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]; + + return std.mem.concat(allocator, u8, &[_][]const u8{ name, "_", 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 { - if (std.mem.eql(u8, self.path, "root.zig")) return try allocator.dupe(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, "/"); - var path = try allocator.dupe(u8, self.path); - const extension = std.fs.path.extension(path); - defer allocator.free(path); - std.mem.replaceScalar(u8, path, std.fs.path.sep, '/'); - return std.mem.concat( - allocator, - u8, - &[_][]const u8{ "/", path[0 .. path.len - extension.len] }, - ); + return try std.mem.concat(allocator, u8, &[_][]const u8{ "/", name }); } pub fn lessThanFn(context: void, lhs: @This(), rhs: @This()) bool { @@ -108,11 +105,7 @@ pub fn generateRoutes(self: *Self) !void { if (entry.kind != .file) continue; const extension = std.fs.path.extension(entry.path); - const basename = std.fs.path.basename(entry.path); - if (std.mem.eql(u8, basename, "routes.zig")) continue; - if (std.mem.eql(u8, basename, "zmpl.manifest.zig")) continue; - if (std.mem.startsWith(u8, basename, ".")) continue; if (!std.mem.eql(u8, extension, ".zig")) continue; const view_routes = try self.generateRoutesForView(views_dir, entry.path); @@ -446,7 +439,7 @@ fn parseFunction( return .{ .name = function_name, - .path = try self.allocator.dupe(u8, path), + .path = try std.fs.path.join(self.allocator, &[_][]const u8{ "src", "app", "views", path }), .args = try self.allocator.dupe(Arg, args.items), .source = try self.allocator.dupe(u8, source), .params = std.ArrayList([]const u8).init(self.allocator), diff --git a/src/jetzig.zig b/src/jetzig.zig index 22945eb..7a84745 100644 --- a/src/jetzig.zig +++ b/src/jetzig.zig @@ -185,7 +185,3 @@ pub fn base64Decode(allocator: std.mem.Allocator, string: []const u8) ![]const u try decoder.decode(ptr, string); return ptr; } - -test { - @import("std").testing.refAllDecls(@This()); -} diff --git a/src/jetzig/http/Server.zig b/src/jetzig/http/Server.zig index feb0d39..23fb40c 100644 --- a/src/jetzig/http/Server.zig +++ b/src/jetzig/http/Server.zig @@ -331,6 +331,8 @@ fn matchRoute(self: *Self, request: *jetzig.http.Request, static: bool) !?*jetzi const StaticResource = struct { content: []const u8, mime_type: []const u8 = "application/octet-stream" }; fn matchStaticResource(self: *Self, request: *jetzig.http.Request) !?StaticResource { + // TODO: Map public and static routes at launch to avoid accessing the file system when + // matching any route - currently every request causes file system traversal. const public_resource = try self.matchPublicContent(request); if (public_resource) |resource| return resource; @@ -458,7 +460,7 @@ fn staticPath(request: *jetzig.http.Request, route: jetzig.views.Route) !?[]cons .index, .post => return try std.mem.concat( request.allocator, u8, - &[_][]const u8{ route.name, "_", extension }, + &[_][]const u8{ route.name, extension }, ), else => return null, }