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.
This commit is contained in:
Bob Farrell 2024-03-10 14:10:53 +00:00
parent edbf433a81
commit a78d53a19a
7 changed files with 47 additions and 34 deletions

View File

@ -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"),

View File

@ -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;
}

View File

@ -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");

View File

@ -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);
}

View File

@ -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),

View File

@ -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());
}

View File

@ -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,
}