mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 22:16:08 +00:00
273 lines
8.8 KiB
Zig
273 lines
8.8 KiB
Zig
const std = @import("std");
|
|
const util = @import("../../util.zig");
|
|
|
|
/// Run the view generator. Create a view in `src/app/views/`
|
|
pub fn run(allocator: std.mem.Allocator, cwd: std.fs.Dir, args: [][]const u8, help: bool) !void {
|
|
if (help or args.len == 0) {
|
|
std.debug.print(
|
|
\\Generate a view. Pass optional action names from:
|
|
\\ index, get, post, put, patch, delete
|
|
\\
|
|
\\Optionally suffix actions with `:static` to use static routing.
|
|
\\Static requests are rendered at build time only. Use static routes
|
|
\\when rendering takes a long time and content does not change between
|
|
\\deployments.
|
|
\\
|
|
\\Omit action names to generate a view with all actions defined.
|
|
\\
|
|
\\Example:
|
|
\\
|
|
\\ jetzig generate view iguanas index:static get post delete
|
|
\\
|
|
, .{});
|
|
|
|
if (help) return;
|
|
|
|
return error.JetzigCommandError;
|
|
}
|
|
|
|
var buf = std.ArrayList(u8).init(allocator);
|
|
defer buf.deinit();
|
|
|
|
const writer = buf.writer();
|
|
|
|
try writer.writeAll(
|
|
\\const std = @import("std");
|
|
\\const jetzig = @import("jetzig");
|
|
\\
|
|
\\
|
|
);
|
|
|
|
const filename = try std.mem.concat(allocator, u8, &[_][]const u8{ args[0], ".zig" });
|
|
defer allocator.free(filename);
|
|
const action_args = if (args.len > 1)
|
|
args[1..]
|
|
else
|
|
&[_][]const u8{ "index", "get", "new", "post", "put", "patch", "delete" };
|
|
|
|
var actions = std.ArrayList(Action).init(allocator);
|
|
defer actions.deinit();
|
|
|
|
var static_actions = std.ArrayList(Action).init(allocator);
|
|
defer static_actions.deinit();
|
|
|
|
for (action_args) |arg| {
|
|
if (parseAction(arg)) |action| {
|
|
try actions.append(action);
|
|
if (action.static) try static_actions.append(action);
|
|
} else {
|
|
std.debug.print("Unexpected argument: {s}\n", .{arg});
|
|
return error.JetzigCommandError;
|
|
}
|
|
}
|
|
|
|
if (static_actions.items.len > 0) try writeStaticParams(allocator, static_actions.items, writer);
|
|
|
|
for (actions.items) |action| {
|
|
try writeAction(allocator, writer, action);
|
|
try writeTemplate(allocator, cwd, args[0], action);
|
|
}
|
|
|
|
for (actions.items) |action| {
|
|
try writeTest(allocator, writer, args[0], action);
|
|
}
|
|
|
|
var dir = try cwd.openDir("src/app/views", .{});
|
|
defer dir.close();
|
|
|
|
const file = dir.createFile(filename, .{ .exclusive = true }) catch |err| {
|
|
switch (err) {
|
|
error.PathAlreadyExists => {
|
|
std.debug.print("Path already exists, skipping view creation: {s}\n", .{filename});
|
|
return error.JetzigCommandError;
|
|
},
|
|
else => return err,
|
|
}
|
|
};
|
|
try file.writeAll(util.strip(buf.items));
|
|
try file.writeAll("\n");
|
|
file.close();
|
|
const realpath = try dir.realpathAlloc(allocator, filename);
|
|
defer allocator.free(realpath);
|
|
std.debug.print("Generated view: {s}\n", .{realpath});
|
|
}
|
|
|
|
const Method = enum { index, get, new, post, put, patch, delete };
|
|
const Action = struct {
|
|
method: Method,
|
|
static: bool,
|
|
};
|
|
|
|
// Parse a view arg. Grammar:
|
|
// [index[:static]|get[:static]|post[:static]|put[:static]|patch[:static]|delete[:static]]
|
|
fn parseAction(arg: []const u8) ?Action {
|
|
inline for (@typeInfo(Method).@"enum".fields) |tag| {
|
|
const with_static = tag.name ++ ":static";
|
|
const method: Method = @enumFromInt(tag.value);
|
|
|
|
if (std.mem.eql(u8, tag.name, arg)) return .{ .method = method, .static = false };
|
|
if (std.mem.eql(u8, with_static, arg)) return .{ .method = method, .static = true };
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Write a view function to the output buffer.
|
|
fn writeAction(allocator: std.mem.Allocator, writer: anytype, action: Action) !void {
|
|
const function = try std.fmt.allocPrint(
|
|
allocator,
|
|
\\pub fn {s}({s}request: *jetzig.{s}) !jetzig.View {{
|
|
\\ {s}return request.render({s});
|
|
\\}}
|
|
\\
|
|
\\
|
|
,
|
|
.{
|
|
@tagName(action.method),
|
|
switch (action.method) {
|
|
.index, .post, .new => "",
|
|
.get, .put, .patch, .delete => "id: []const u8, ",
|
|
},
|
|
if (action.static) "StaticRequest" else "Request",
|
|
switch (action.method) {
|
|
.index, .post, .new => "",
|
|
.get, .put, .patch, .delete => "_ = id;\n ",
|
|
},
|
|
switch (action.method) {
|
|
.index, .get, .new => ".ok",
|
|
.post => ".created",
|
|
.put, .patch, .delete => ".ok",
|
|
},
|
|
},
|
|
);
|
|
defer allocator.free(function);
|
|
try writer.writeAll(function);
|
|
}
|
|
|
|
// Write a view function to the output buffer.
|
|
fn writeTest(allocator: std.mem.Allocator, writer: anytype, name: []const u8, action: Action) !void {
|
|
const action_upper = try std.ascii.allocUpperString(allocator, @tagName(action.method));
|
|
defer allocator.free(action_upper);
|
|
|
|
const test_body = try std.fmt.allocPrint(
|
|
allocator,
|
|
\\
|
|
\\test "{s}" {{
|
|
\\ var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
|
|
\\ defer app.deinit();
|
|
\\
|
|
\\ const response = try app.request(.{s}, "/{s}{s}", .{{}});
|
|
\\ try response.expectStatus({s});
|
|
\\}}
|
|
\\
|
|
,
|
|
.{
|
|
@tagName(action.method),
|
|
switch (action.method) {
|
|
.index, .get, .new => "GET",
|
|
.put, .patch, .delete, .post => action_upper,
|
|
},
|
|
name,
|
|
switch (action.method) {
|
|
.index, .post => "",
|
|
.new => "/new",
|
|
.get, .put, .patch, .delete => "/example-id",
|
|
},
|
|
switch (action.method) {
|
|
.index, .get, .new => ".ok",
|
|
.post => ".created",
|
|
.put, .patch, .delete => ".ok",
|
|
},
|
|
},
|
|
);
|
|
defer allocator.free(test_body);
|
|
try writer.writeAll(test_body);
|
|
}
|
|
// Output static params example. Only invoked if one or more static routes are created.
|
|
fn writeStaticParams(allocator: std.mem.Allocator, actions: []Action, writer: anytype) !void {
|
|
try writer.writeAll(
|
|
\\// Define an array of params for each static view function.
|
|
\\// At build time, static outputs are generated for each set of params.
|
|
\\// At run time, requests matching the provided params will render the pre-rendered content.
|
|
\\pub const static_params = .{
|
|
\\
|
|
);
|
|
|
|
for (actions) |action| {
|
|
switch (action.method) {
|
|
.index, .post, .new => {
|
|
const output = try std.fmt.allocPrint(
|
|
allocator,
|
|
\\ .{s} = .{{
|
|
\\ .{{ .params = .{{ .foo = "bar", .baz = "qux" }} }},
|
|
\\ }},
|
|
\\
|
|
,
|
|
.{@tagName(action.method)},
|
|
);
|
|
defer allocator.free(output);
|
|
try writer.writeAll(output);
|
|
},
|
|
.get, .put, .patch, .delete => {
|
|
const output = try std.fmt.allocPrint(
|
|
allocator,
|
|
\\ .{s} = .{{
|
|
\\ .{{ .id = "1", .params = .{{ .foo = "bar", .baz = "qux" }} }},
|
|
\\ }},
|
|
\\
|
|
,
|
|
.{@tagName(action.method)},
|
|
);
|
|
defer allocator.free(output);
|
|
try writer.writeAll(output);
|
|
},
|
|
}
|
|
}
|
|
|
|
try writer.writeAll(
|
|
\\};
|
|
\\
|
|
\\
|
|
);
|
|
}
|
|
|
|
// Generates a Zmpl template for a corresponding view + action.
|
|
fn writeTemplate(allocator: std.mem.Allocator, cwd: std.fs.Dir, name: []const u8, action: Action) !void {
|
|
const path = try std.fs.path.join(allocator, &[_][]const u8{
|
|
"src",
|
|
"app",
|
|
"views",
|
|
name,
|
|
});
|
|
defer allocator.free(path);
|
|
|
|
var view_dir = try cwd.makeOpenPath(path, .{});
|
|
defer view_dir.close();
|
|
|
|
const filename = try std.mem.concat(allocator, u8, &[_][]const u8{ @tagName(action.method), ".zmpl" });
|
|
defer allocator.free(filename);
|
|
|
|
const file = view_dir.createFile(filename, .{ .exclusive = true }) catch |err| {
|
|
switch (err) {
|
|
error.PathAlreadyExists => {
|
|
std.debug.print("Path already exists, skipping template creation: {s}\n", .{filename});
|
|
return;
|
|
},
|
|
else => return err,
|
|
}
|
|
};
|
|
|
|
try file.writeAll(
|
|
\\<div>
|
|
\\ <span>Content goes here</span>
|
|
\\</div>
|
|
\\
|
|
);
|
|
|
|
file.close();
|
|
|
|
const realpath = try view_dir.realpathAlloc(allocator, filename);
|
|
defer allocator.free(realpath);
|
|
std.debug.print("Generated template: {s}\n", .{realpath});
|
|
}
|