Skip to content

Commit

Permalink
Closes #48: Simplify route generation
Browse files Browse the repository at this point in the history
Generate an array of `jetzig.views.Route` in `GenerateRoutes.zig` exe
instead of a meta-route that we later translate into an actual route.
This makes things much simpler for static routes at build time and
dynamic routes at run time as we no longer need to use comptime - we
just have an array of routes ready-made.
  • Loading branch information
bobf committed Apr 2, 2024
1 parent 800b72e commit 116668b
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 177 deletions.
4 changes: 2 additions & 2 deletions demo/src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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, .{});
}
62 changes: 22 additions & 40 deletions src/GenerateRoutes.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -128,30 +129,24 @@ 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{
\\
);

for (self.static_routes.items) |static_route| {
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});
Expand All @@ -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);

Expand All @@ -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);
Expand All @@ -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.*);
}
}
}
Expand Down
52 changes: 14 additions & 38 deletions src/compile_static_routes.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
};

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;

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 = &params,
};

if (static_params_len > 0) {
inline for (static_route.params, 0..) |json, index| {
var count: usize = 0;

for (routes) |route| {
if (!route.static) continue;

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

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

Expand Down
81 changes: 0 additions & 81 deletions src/jetzig.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
38 changes: 23 additions & 15 deletions src/jetzig/App.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion src/jetzig/views/Route.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 116668b

Please sign in to comment.