diff --git a/demo/src/main.zig b/demo/src/main.zig index 6c5d3a0..c7438a3 100644 --- a/demo/src/main.zig +++ b/demo/src/main.zig @@ -2,7 +2,7 @@ const std = @import("std"); pub const jetzig = @import("jetzig"); -pub const routes = @import("routes").routes; +pub const routes = @import("routes"); // Override default settings in `jetzig.config` here: pub const jetzig_options = struct { @@ -92,5 +92,5 @@ pub fn main() !void { const app = try jetzig.init(allocator); defer app.deinit(); - try app.start(comptime jetzig.route(routes)); + try app.start(routes, .{}); } diff --git a/src/GenerateRoutes.zig b/src/GenerateRoutes.zig index 26dc339..a45b1ff 100644 --- a/src/GenerateRoutes.zig +++ b/src/GenerateRoutes.zig @@ -18,6 +18,7 @@ const Function = struct { path: []const u8, source: []const u8, params: std.ArrayList([]const u8), + static: bool = false, /// The full name of a route. This **must** match the naming convention used by static route /// compilation. @@ -128,8 +129,9 @@ pub fn generateRoutes(self: *Self) !void { std.sort.pdq(Function, self.dynamic_routes.items, {}, Function.lessThanFn); try writer.writeAll( - \\pub const routes = struct { - \\ pub const static = .{ + \\const jetzig = @import("jetzig"); + \\ + \\pub const routes = [_]jetzig.views.Route{ \\ ); @@ -137,21 +139,14 @@ pub fn generateRoutes(self: *Self) !void { try self.writeRoute(writer, static_route); } - try writer.writeAll( - \\ }; - \\ - \\ pub const dynamic = .{ - \\ - ); - for (self.dynamic_routes.items) |dynamic_route| { try self.writeRoute(writer, dynamic_route); const name = try dynamic_route.fullName(self.allocator); defer self.allocator.free(name); - std.debug.print("[jetzig] Imported route: {s}\n", .{name}); } - try writer.writeAll(" };\n"); + std.debug.print("[jetzig] Imported {} route(s)\n", .{self.dynamic_routes.items.len}); + try writer.writeAll("};"); // std.debug.print("routes.zig\n{s}\n", .{self.buffer.items}); @@ -166,33 +161,19 @@ fn writeRoute(self: *Self, writer: std.ArrayList(u8).Writer, route: Function) !v const output_template = \\ .{{ - \\ .name = "{s}", - \\ .action = "{s}", - \\ .view_name = "{s}", - \\ .uri_path = "{s}", - \\ .template = "{s}", - \\ .module = @import("{s}"), - \\ .function = @import("{s}").{s}, - \\ .params = {s}, + \\ .name = "{0s}", + \\ .action = .{1s}, + \\ .view_name = "{2s}", + \\ .view = jetzig.views.Route.ViewType{{ .{3s} = .{{ .{1s} = @import("{7s}").{1s} }} }}, + \\ .static = {4s}, + \\ .uri_path = "{5s}", + \\ .template = "{6s}", + \\ .layout = if (@hasDecl(@import("{7s}"), "layout")) @import("{7s}").layout else null, + \\ .json_params = &[_][]const u8 {{ {8s} }}, \\ }}, \\ ; - var params_buf = std.ArrayList(u8).init(self.allocator); - const params_writer = params_buf.writer(); - defer params_buf.deinit(); - if (route.params.items.len > 0) { - try params_writer.writeAll(".{\n"); - for (route.params.items) |item| { - try params_writer.writeAll(" "); - try params_writer.writeAll(item); - try params_writer.writeAll(",\n"); - } - try params_writer.writeAll(" }"); - } else { - try params_writer.writeAll(".{}"); - } - const module_path = try self.allocator.dupe(u8, route.path); defer self.allocator.free(module_path); @@ -205,12 +186,12 @@ fn writeRoute(self: *Self, writer: std.ArrayList(u8).Writer, route: Function) !v full_name, route.name, route.view_name, + if (route.static) "static" else "dynamic", + if (route.static) "true" else "false", uri_path, full_name, module_path, - module_path, - route.name, - params_buf.items, + try std.mem.join(self.allocator, ", \n", route.params.items), }); defer self.allocator.free(output); @@ -237,13 +218,14 @@ fn generateRoutesForView(self: *Self, views_dir: std.fs.Dir, path: []const u8) ! switch (tag) { .fn_proto_multi => { const function = try self.parseFunction(index, path, source); - if (function) |capture| { + if (function) |*capture| { for (capture.args) |arg| { if (std.mem.eql(u8, try arg.typeBasename(), "StaticRequest")) { - try static_routes.append(capture); + @constCast(capture).static = true; + try static_routes.append(capture.*); } if (std.mem.eql(u8, try arg.typeBasename(), "Request")) { - try dynamic_routes.append(capture); + try dynamic_routes.append(capture.*); } } } diff --git a/src/compile_static_routes.zig b/src/compile_static_routes.zig index 1b744c6..0327090 100644 --- a/src/compile_static_routes.zig +++ b/src/compile_static_routes.zig @@ -19,42 +19,16 @@ pub fn main() !void { fn compileStaticRoutes(allocator: std.mem.Allocator) !void { std.fs.cwd().deleteTree("static") catch {}; - inline for (routes.static) |static_route| { - const static_view = jetzig.views.Route.ViewType{ - .static = @unionInit( - jetzig.views.Route.StaticViewType, - static_route.action, - static_route.function, - ), - }; + var count: usize = 0; - comptime var static_params_len = 0; - inline for (static_route.params) |_| static_params_len += 1; - comptime var params: [static_params_len][]const u8 = undefined; - inline for (static_route.params, 0..) |json, index| params[index] = json; + for (routes) |route| { + if (!route.static) continue; - const layout: ?[]const u8 = if (@hasDecl(static_route.module, "layout")) - static_route.module.layout - else - null; - - const route = jetzig.views.Route{ - .name = static_route.name, - .action = @field(jetzig.views.Route.Action, static_route.action), - .view_name = static_route.view_name, - .view = static_view, - .static = true, - .uri_path = static_route.uri_path, - .layout = layout, - .template = static_route.template, - .json_params = ¶ms, - }; - - if (static_params_len > 0) { - inline for (static_route.params, 0..) |json, index| { + if (route.json_params.len > 0) { + for (route.json_params, 0..) |json, index| { var request = try jetzig.http.StaticRequest.init(allocator, json); defer request.deinit(); - try writeContent(allocator, route, &request, index); + try writeContent(allocator, route, &request, index, &count); } } @@ -64,18 +38,20 @@ fn compileStaticRoutes(allocator: std.mem.Allocator) !void { .index, .post => { var request = try jetzig.http.StaticRequest.init(allocator, "{}"); defer request.deinit(); - try writeContent(allocator, route, &request, null); + try writeContent(allocator, route, &request, null, &count); }, inline else => {}, } } + std.debug.print("[jetzig] Compiled {} static output(s)\n", .{count}); } fn writeContent( allocator: std.mem.Allocator, - comptime route: jetzig.views.Route, + route: jetzig.views.Route, request: *jetzig.http.StaticRequest, index: ?usize, + count: *usize, ) !void { const index_suffix = if (index) |capture| try std.fmt.allocPrint(allocator, "_{}", .{capture}) @@ -100,7 +76,7 @@ fn writeContent( try json_file.writeAll(try view.data.toJson()); defer json_file.close(); - std.debug.print("[jetzig] Compiled static route: {s}\n", .{json_path}); + count.* += 1; const html_content = try renderZmplTemplate(allocator, route, view) orelse try renderMarkdown(allocator, route, view) orelse @@ -116,7 +92,7 @@ fn writeContent( try html_file.writeAll(content); defer html_file.close(); allocator.free(content); - std.debug.print("[jetzig] Compiled static route: {s}\n", .{html_path}); + count.* += 1; } } diff --git a/src/jetzig.zig b/src/jetzig.zig index dcb684c..594c4e6 100644 --- a/src/jetzig.zig +++ b/src/jetzig.zig @@ -96,84 +96,3 @@ pub fn init(allocator: std.mem.Allocator) !App { .allocator = allocator, }; } - -// Receives an array of imported modules and detects functions defined on them. -// Each detected function is stored as a Route which can be accessed at runtime to route requests -// to the appropriate View. -pub fn route(comptime routes: anytype) []views.Route { - var size: usize = 0; - - for (routes.dynamic) |_| { - size += 1; - } - - for (routes.static) |_| { - size += 1; - } - - var detected: [size]views.Route = undefined; - var index: usize = 0; - - for (routes.dynamic) |dynamic_route| { - const view = views.Route.ViewType{ - .dynamic = @unionInit( - views.Route.DynamicViewType, - dynamic_route.action, - dynamic_route.function, - ), - }; - - const layout: ?[]const u8 = if (@hasDecl(dynamic_route.module, "layout")) - dynamic_route.module.layout - else - null; - - detected[index] = .{ - .name = dynamic_route.name, - .action = @field(views.Route.Action, dynamic_route.action), - .view_name = dynamic_route.view_name, - .view = view, - .static = false, - .uri_path = dynamic_route.uri_path, - .layout = layout, - .template = dynamic_route.template, - .json_params = &.{}, - }; - index += 1; - } - - for (routes.static) |static_route| { - const view = views.Route.ViewType{ - .static = @unionInit( - views.Route.StaticViewType, - static_route.action, - static_route.function, - ), - }; - - comptime var params_size = 0; - inline for (static_route.params) |_| params_size += 1; - comptime var static_params: [params_size][]const u8 = undefined; - inline for (static_route.params, 0..) |json, params_index| static_params[params_index] = json; - - const layout: ?[]const u8 = if (@hasDecl(static_route.module, "layout")) - static_route.module.layout - else - null; - - detected[index] = .{ - .name = static_route.name, - .action = @field(views.Route.Action, static_route.action), - .view_name = static_route.view_name, - .view = view, - .static = true, - .uri_path = static_route.uri_path, - .layout = layout, - .template = static_route.template, - .json_params = &static_params, - }; - index += 1; - } - - return &detected; -} diff --git a/src/jetzig/App.zig b/src/jetzig/App.zig index 8a9a9df..65ab375 100644 --- a/src/jetzig/App.zig +++ b/src/jetzig/App.zig @@ -14,35 +14,43 @@ pub fn deinit(self: Self) void { _ = self; } +// Not used yet, but allows us to add new options to `start()` without breaking +// backward-compatibility. +const AppOptions = struct {}; + /// Starts an application. `routes` should be `@import("routes").routes`, a generated file /// automatically created at build time. `templates` should be /// `@import("src/app/views/zmpl.manifest.zig").templates`, created by Zmpl at compile time. -pub fn start(self: Self, comptime_routes: []jetzig.views.Route) !void { +pub fn start(self: Self, routes_module: type, options: AppOptions) !void { + _ = options; // See `AppOptions` + var mime_map = jetzig.http.mime.MimeMap.init(self.allocator); defer mime_map.deinit(); try mime_map.build(); var routes = std.ArrayList(*jetzig.views.Route).init(self.allocator); - for (comptime_routes) |*comptime_route| { + for (routes_module.routes) |const_route| { var route = try self.allocator.create(jetzig.views.Route); - route.* = jetzig.views.Route{ - .name = comptime_route.name, - .action = comptime_route.action, - .view_name = comptime_route.view_name, - .uri_path = comptime_route.uri_path, - .view = comptime_route.view, - .static_view = comptime_route.static_view, - .static = comptime_route.static, - .render = comptime_route.render, - .renderStatic = comptime_route.renderStatic, - .layout = comptime_route.layout, - .template = comptime_route.template, - .json_params = comptime_route.json_params, + route.* = .{ + .name = const_route.name, + .action = const_route.action, + .view_name = const_route.view_name, + .uri_path = const_route.uri_path, + .view = const_route.view, + .static_view = const_route.static_view, + .static = const_route.static, + .render = const_route.render, + .renderStatic = const_route.renderStatic, + .layout = const_route.layout, + .template = const_route.template, + .json_params = const_route.json_params, }; + try route.initParams(self.allocator); try routes.append(route); } + defer routes.deinit(); defer for (routes.items) |route| { route.deinitParams(); diff --git a/src/jetzig/views/Route.zig b/src/jetzig/views/Route.zig index 77230d6..eed3e3e 100644 --- a/src/jetzig/views/Route.zig +++ b/src/jetzig/views/Route.zig @@ -47,7 +47,7 @@ render: RenderFn = renderFn, renderStatic: RenderStaticFn = renderStaticFn, layout: ?[]const u8, template: []const u8, -json_params: [][]const u8, +json_params: []const []const u8, params: std.ArrayList(*jetzig.data.Data) = undefined, /// Initializes a route's static params on server launch. Converts static params (JSON strings)