mirror of
https://github.com/jetzig-framework/jetzig.git
synced 2025-05-14 14:06:08 +00:00
Static routes
Generate views defined with `request: *jetzig.http.StaticRequest` as static content into `static/` directory so that exported JSON and HTML can be rendered direct from disk, skipping runtime rendering.
This commit is contained in:
parent
6d487392a8
commit
31927cdb6b
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@ zig-cache/
|
||||
*.core
|
||||
src/app/views/**/*.compiled.zig
|
||||
archive.tar.gz
|
||||
static/
|
||||
|
94
build.zig
94
build.zig
@ -1,5 +1,12 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const GenerateRoutes = @import("src/GenerateRoutes.zig");
|
||||
pub const TemplateFn = @import("src/jetzig.zig").TemplateFn;
|
||||
pub const StaticRequest = @import("src/jetzig.zig").StaticRequest;
|
||||
pub const http = @import("src/jetzig/http.zig");
|
||||
pub const data = @import("src/jetzig/data.zig");
|
||||
pub const views = @import("src/jetzig/views.zig");
|
||||
|
||||
pub fn build(b: *std.Build) !void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
@ -25,12 +32,16 @@ pub fn build(b: *std.Build) !void {
|
||||
b.installArtifact(exe);
|
||||
|
||||
const jetzig_module = b.addModule("jetzig", .{ .root_source_file = .{ .path = "src/jetzig.zig" } });
|
||||
exe.root_module.addImport("jetzig", jetzig_module);
|
||||
lib.root_module.addImport("jetzig", jetzig_module);
|
||||
|
||||
const zmpl_dep = b.dependency(
|
||||
"zmpl",
|
||||
.{ .target = target, .optimize = optimize, .zmpl_templates_path = template_path, .zmpl_manifest_path = manifest },
|
||||
.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.zmpl_templates_path = template_path,
|
||||
.zmpl_manifest_path = manifest,
|
||||
},
|
||||
);
|
||||
|
||||
lib.root_module.addImport("zmpl", zmpl_dep.module("zmpl"));
|
||||
@ -61,82 +72,3 @@ pub fn build(b: *std.Build) !void {
|
||||
const test_step = b.step("test", "Run library tests");
|
||||
test_step.dependOn(&run_main_tests.step);
|
||||
}
|
||||
|
||||
const ViewItem = struct {
|
||||
path: []const u8,
|
||||
name: []const u8,
|
||||
};
|
||||
|
||||
pub const CompileViewsStepOptions = struct {
|
||||
template_path: []const u8 = "src/app/views/",
|
||||
max_rss: usize = 0,
|
||||
};
|
||||
pub const CompileViewsStep = struct {
|
||||
step: std.Build.Step,
|
||||
template_path: []const u8,
|
||||
|
||||
fn make(step: *std.Build.Step, prog_node: *std.Progress.Node) !void {
|
||||
const self = @fieldParentPtr(CompileViewsStep, "step", step);
|
||||
try compileViews(step.owner, self.template_path);
|
||||
prog_node.completeOne();
|
||||
}
|
||||
|
||||
pub fn create(owner: *std.Build, options: CompileViewsStepOptions) *CompileViewsStep {
|
||||
const step = std.Build.Step.init(.{
|
||||
.id = std.Build.Step.Id.custom,
|
||||
.name = "Compile views",
|
||||
.owner = owner,
|
||||
.max_rss = options.max_rss,
|
||||
.makeFn = &make,
|
||||
});
|
||||
const compile_step_view = owner.allocator.create(CompileViewsStep) catch @panic("Out of memory");
|
||||
compile_step_view.* = .{
|
||||
.step = step,
|
||||
.template_path = options.template_path,
|
||||
};
|
||||
return compile_step_view;
|
||||
}
|
||||
|
||||
fn findViews(allocator: std.mem.Allocator, template_path: []const u8) !std.ArrayList(*ViewItem) {
|
||||
var array = std.ArrayList(*ViewItem).init(allocator);
|
||||
const dir = try std.fs.cwd().openDir(template_path, .{ .iterate = true });
|
||||
var walker = try dir.walk(allocator);
|
||||
defer walker.deinit();
|
||||
while (try walker.next()) |entry| {
|
||||
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;
|
||||
|
||||
var sanitized_array = std.ArrayList(u8).init(allocator);
|
||||
for (entry.path) |char| {
|
||||
if (std.mem.indexOfAny(u8, &[_]u8{char}, "abcdefghijklmnopqrstuvwxyz")) |_| try sanitized_array.append(char);
|
||||
}
|
||||
const ptr = try allocator.create(ViewItem);
|
||||
ptr.* = .{
|
||||
.path = try allocator.dupe(u8, entry.path),
|
||||
.name = try allocator.dupe(u8, sanitized_array.items),
|
||||
};
|
||||
try array.append(ptr);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
fn compileViews(b: *std.Build, template_path: []const u8) !void {
|
||||
var dir = b.build_root.handle;
|
||||
var views_dir = try dir.makeOpenPath(template_path, .{});
|
||||
var file = try views_dir.createFile("routes.zig", .{ .truncate = true });
|
||||
try file.writeAll("pub const routes = .{\n");
|
||||
const views = try findViews(b.allocator, template_path);
|
||||
for (views.items) |view| {
|
||||
try file.writeAll(try std.fmt.allocPrint(b.allocator, " @import(\"{s}\"),\n", .{view.path}));
|
||||
std.debug.print("[jetzig] Imported view: {s}\n", .{view.path});
|
||||
}
|
||||
|
||||
try file.writeAll("};\n");
|
||||
file.close();
|
||||
}
|
||||
};
|
||||
|
@ -1,57 +1,15 @@
|
||||
.{
|
||||
.name = "jetzig",
|
||||
// This is a [Semantic Version](https://semver.org/).
|
||||
// In a future version of Zig it will be used for package deduplication.
|
||||
.version = "0.0.0",
|
||||
|
||||
// This field is optional.
|
||||
// This is currently advisory only; Zig does not yet do anything
|
||||
// with this value.
|
||||
//.minimum_zig_version = "0.11.0",
|
||||
|
||||
// This field is optional.
|
||||
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
||||
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
||||
// Once all dependencies are fetched, `zig build` no longer requires
|
||||
// Internet connectivity.
|
||||
.dependencies = .{
|
||||
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
|
||||
.zmpl = .{
|
||||
// When updating this field to a new URL, be sure to delete the corresponding
|
||||
// `hash`, otherwise you are communicating that you expect to find the old hash at
|
||||
// the new URL.
|
||||
//
|
||||
.url = "https://github.com/jetzig-framework/zmpl/archive/dde755c61b2cf7ddbb65c81b98a8877c0806cf6e.tar.gz",
|
||||
.hash = "122093ac6e80128800873132da4a2f2e754cd2b7b7c3f0e84bdf6fa385f80615c37d",
|
||||
// This is computed from the file contents of the directory of files that is
|
||||
// obtained after fetching `url` and applying the inclusion rules given by
|
||||
// `paths`.
|
||||
//
|
||||
// This field is the source of truth; packages do not come from an `url`; they
|
||||
// come from a `hash`. `url` is just one of many possible mirrors for how to
|
||||
// obtain a package matching this `hash`.
|
||||
//
|
||||
// Uses the [multihash](https://multiformats.io/multihash/) format.
|
||||
|
||||
// When this is provided, the package is found in a directory relative to the
|
||||
// build root. In this case the package's hash is irrelevant and therefore not
|
||||
// computed. This field and `url` are mutually exclusive.
|
||||
.url = "https://github.com/jetzig-framework/zmpl/archive/40158f08fbde4a67d44bf0387649e324ad7ef668.tar.gz",
|
||||
.hash = "12201558739a1032ba9c104d9c900055988f11e2af8014b4159c259b7d1103398f0b",
|
||||
},
|
||||
},
|
||||
|
||||
// Specifies the set of files and directories that are included in this package.
|
||||
// Only files and directories listed here are included in the `hash` that
|
||||
// is computed for this package.
|
||||
// Paths are relative to the build root. Use the empty string (`""`) to refer to
|
||||
// the build root itself.
|
||||
// A directory listed here means that all files within, recursively, are included.
|
||||
.paths = .{
|
||||
// This makes *all* files, recursively, included in this package. It is generally
|
||||
// better to explicitly list the files and directories instead, to insure that
|
||||
// fetching from tarballs, file system paths, and version control all result
|
||||
// in the same contents hash.
|
||||
"",
|
||||
// For example...
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src/jetzig",
|
||||
|
4
demo/.gitignore
vendored
Normal file
4
demo/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
zig-cache/
|
||||
zig-out/
|
||||
static/
|
||||
src/app/views/**/.*.zig
|
111
demo/build.zig
Normal file
111
demo/build.zig
Normal file
@ -0,0 +1,111 @@
|
||||
const std = @import("std");
|
||||
const jetzig_build = @import("jetzig");
|
||||
pub const zmpl = jetzig_build.zmpl;
|
||||
|
||||
const GenerateRoutes = @import("jetzig").GenerateRoutes;
|
||||
|
||||
pub fn build(b: *std.Build) !void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
const jetzig_dep = b.dependency("jetzig", .{ .optimize = optimize, .target = target });
|
||||
|
||||
const lib = b.addStaticLibrary(.{
|
||||
.name = "jetzig-demo",
|
||||
.root_source_file = .{ .path = "src/main.zig" },
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
b.installArtifact(lib);
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "jetzig-demo",
|
||||
.root_source_file = .{ .path = "src/main.zig" },
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const template_path = b.option([]const u8, "zmpl_templates_path", "Path to templates") orelse "src/app/views/";
|
||||
const manifest_path: []const u8 = b.pathJoin(&.{ template_path, "zmpl.manifest.zig" });
|
||||
|
||||
const zmpl_dep = b.dependency(
|
||||
"zmpl",
|
||||
.{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.zmpl_templates_path = template_path,
|
||||
.zmpl_manifest_path = manifest_path,
|
||||
},
|
||||
);
|
||||
|
||||
const jetzig_module = jetzig_dep.module("jetzig");
|
||||
const zmpl_module = zmpl_dep.module("zmpl");
|
||||
|
||||
exe.root_module.addImport("jetzig", jetzig_module);
|
||||
lib.root_module.addImport("jetzig", jetzig_module);
|
||||
exe.root_module.addImport("zmpl", zmpl_module);
|
||||
lib.root_module.addImport("zmpl", zmpl_module);
|
||||
|
||||
b.installArtifact(exe);
|
||||
|
||||
var generate_routes = GenerateRoutes.init(b.allocator, "src/app/views");
|
||||
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 });
|
||||
|
||||
const exe_static_routes = b.addExecutable(.{
|
||||
.name = "static",
|
||||
.root_source_file = jetzig_dep.path("src/compile_static_routes.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
exe.root_module.addImport("routes", routes_module);
|
||||
lib.root_module.addImport("routes", routes_module);
|
||||
routes_module.addImport("jetzig", jetzig_module);
|
||||
|
||||
const templates_module = b.createModule(
|
||||
.{ .root_source_file = .{ .path = "src/app/views/zmpl.manifest.zig" } },
|
||||
);
|
||||
exe_static_routes.root_module.addImport("routes", routes_module);
|
||||
exe_static_routes.root_module.addImport("jetzig", jetzig_module);
|
||||
exe_static_routes.root_module.addImport("templates", templates_module);
|
||||
templates_module.addImport("zmpl", zmpl_module);
|
||||
|
||||
const run_static_routes_cmd = b.addRunArtifact(exe_static_routes);
|
||||
exe.step.dependOn(&run_static_routes_cmd.step);
|
||||
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
|
||||
const lib_unit_tests = b.addTest(.{
|
||||
.root_source_file = .{ .path = "src/root.zig" },
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
|
||||
|
||||
const exe_unit_tests = b.addTest(.{
|
||||
.root_source_file = .{ .path = "src/main.zig" },
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
|
||||
|
||||
const test_step = b.step("test", "Run unit tests");
|
||||
test_step.dependOn(&run_lib_unit_tests.step);
|
||||
test_step.dependOn(&run_exe_unit_tests.step);
|
||||
}
|
39
demo/build.zig.zon
Normal file
39
demo/build.zig.zon
Normal file
@ -0,0 +1,39 @@
|
||||
.{
|
||||
.name = "sandbox-jetzig",
|
||||
// This is a [Semantic Version](https://semver.org/).
|
||||
// In a future version of Zig it will be used for package deduplication.
|
||||
.version = "0.0.0",
|
||||
|
||||
// This field is optional.
|
||||
// This is currently advisory only; Zig does not yet do anything
|
||||
// with this value.
|
||||
//.minimum_zig_version = "0.11.0",
|
||||
|
||||
// This field is optional.
|
||||
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
||||
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
||||
// Once all dependencies are fetched, `zig build` no longer requires
|
||||
// internet connectivity.
|
||||
.dependencies = .{
|
||||
.jetzig = .{
|
||||
.path = "../",
|
||||
},
|
||||
.zmpl = .{
|
||||
.url = "https://github.com/jetzig-framework/zmpl/archive/40158f08fbde4a67d44bf0387649e324ad7ef668.tar.gz",
|
||||
.hash = "12201558739a1032ba9c104d9c900055988f11e2af8014b4159c259b7d1103398f0b",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
// This makes *all* files, recursively, included in this package. It is generally
|
||||
// better to explicitly list the files and directories instead, to insure that
|
||||
// fetching from tarballs, file system paths, and version control all result
|
||||
// in the same contents hash.
|
||||
"",
|
||||
// For example...
|
||||
//"build.zig",
|
||||
//"build.zig.zon",
|
||||
//"src",
|
||||
//"LICENSE",
|
||||
//"README.md",
|
||||
},
|
||||
}
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
BIN
demo/public/zmpl.png
Normal file
BIN
demo/public/zmpl.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
21
demo/src/app/routes.zig
Normal file
21
demo/src/app/routes.zig
Normal file
@ -0,0 +1,21 @@
|
||||
pub const routes = struct {
|
||||
pub const static = .{
|
||||
.{
|
||||
.name = "root_index",
|
||||
.action = "index",
|
||||
.uri_path = "/",
|
||||
.template = "root_index",
|
||||
.function = @import("root.zig").index,
|
||||
},
|
||||
};
|
||||
|
||||
pub const dynamic = .{
|
||||
.{
|
||||
.name = "quotes_get",
|
||||
.action = "get",
|
||||
.uri_path = "/quotes",
|
||||
.template = "quotes_get",
|
||||
.function = @import("quotes.zig").get,
|
||||
},
|
||||
};
|
||||
};
|
6
demo/src/app/views/root.zig
Normal file
6
demo/src/app/views/root.zig
Normal file
@ -0,0 +1,6 @@
|
||||
const jetzig = @import("jetzig");
|
||||
|
||||
pub fn index(request: *jetzig.http.StaticRequest, data: *jetzig.data.Data) anyerror!jetzig.views.View {
|
||||
_ = data;
|
||||
return request.render(.ok);
|
||||
}
|
@ -20,8 +20,14 @@
|
||||
<div hx-get="/quotes/init" hx-trigger="load"></div>
|
||||
</div>
|
||||
|
||||
<div>Take a look at the <span class="font-mono">src/app/</span> directory to see how this application works.</div>
|
||||
<div>Visit <a class="font-bold text-[#39b54a]" href="https://www.jetzig.dev/">jetzig.dev</a> to get started.</div>
|
||||
<div>
|
||||
<a href="https://github.com/jetzig-framework/zmpl">
|
||||
<img class="p-3 m-3 mx-auto" src="/zmpl.png" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div>Take a look at the <span class="font-mono">/demo/src/app/</span> directory to see how this application works.</div>
|
||||
<div>Visit <a class="font-bold text-[#39b54a]" href="https://jetzig.dev/">jetzig.dev</a> to get started.</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
21
demo/src/app/views/routes.zig
Normal file
21
demo/src/app/views/routes.zig
Normal file
@ -0,0 +1,21 @@
|
||||
pub const routes = struct {
|
||||
pub const static = .{
|
||||
.{
|
||||
.name = "root_index",
|
||||
.action = "index",
|
||||
.uri_path = "/",
|
||||
.template = "root_index",
|
||||
.function = @import("root.zig").index,
|
||||
},
|
||||
};
|
||||
|
||||
pub const dynamic = .{
|
||||
.{
|
||||
.name = "quotes_get",
|
||||
.action = "get",
|
||||
.uri_path = "/quotes",
|
||||
.template = "quotes_get",
|
||||
.function = @import("quotes.zig").get,
|
||||
},
|
||||
};
|
||||
};
|
@ -2,7 +2,6 @@
|
||||
// This file is automatically generated at build time. Manual edits will be discarded.
|
||||
// This file should _not_ be stored in version control.
|
||||
pub const templates = struct {
|
||||
pub const index = @import(".index.zmpl.compiled.zig");
|
||||
pub const root_index = @import("root/.index.zmpl.compiled.zig");
|
||||
pub const quotes_get = @import("quotes/.get.zmpl.compiled.zig");
|
||||
pub const users_get = @import("users/.get.zmpl.compiled.zig");
|
||||
};
|
19
demo/src/main.zig
Normal file
19
demo/src/main.zig
Normal file
@ -0,0 +1,19 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const jetzig = @import("jetzig");
|
||||
pub const templates = @import("app/views/zmpl.manifest.zig").templates;
|
||||
pub const routes = @import("routes").routes;
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer std.debug.assert(gpa.deinit() == .ok);
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
const app = try jetzig.init(allocator);
|
||||
defer app.deinit();
|
||||
|
||||
try app.start(
|
||||
comptime jetzig.route(routes),
|
||||
comptime jetzig.loadTemplates(templates),
|
||||
);
|
||||
}
|
269
src/GenerateRoutes.zig
Normal file
269
src/GenerateRoutes.zig
Normal file
@ -0,0 +1,269 @@
|
||||
const std = @import("std");
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
views_path: []const u8,
|
||||
buffer: std.ArrayList(u8),
|
||||
dynamic_routes: std.ArrayList(Function),
|
||||
static_routes: std.ArrayList(Function),
|
||||
|
||||
const Self = @This();
|
||||
|
||||
const Function = struct {
|
||||
name: []const u8,
|
||||
params: []Param,
|
||||
path: []const u8,
|
||||
source: []const u8,
|
||||
|
||||
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 },
|
||||
);
|
||||
}
|
||||
|
||||
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, "/");
|
||||
|
||||
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] },
|
||||
);
|
||||
}
|
||||
|
||||
pub fn lessThanFn(context: void, lhs: @This(), rhs: @This()) bool {
|
||||
_ = context;
|
||||
return std.mem.order(u8, lhs.name, rhs.name).compare(std.math.CompareOperator.lt);
|
||||
}
|
||||
};
|
||||
|
||||
const Param = struct {
|
||||
name: []const u8,
|
||||
type_name: []const u8,
|
||||
|
||||
pub fn typeBasename(self: @This()) ![]const u8 {
|
||||
if (std.mem.indexOfScalar(u8, self.type_name, '.')) |_| {
|
||||
var it = std.mem.splitBackwardsScalar(u8, self.type_name, '.');
|
||||
while (it.next()) |capture| {
|
||||
return capture;
|
||||
}
|
||||
}
|
||||
|
||||
const pointer_start = std.mem.indexOfScalar(u8, self.type_name, '*');
|
||||
if (pointer_start) |index| {
|
||||
if (self.type_name.len < index + 1) return error.JetzigAstParserError;
|
||||
return self.type_name[index + 1 ..];
|
||||
} else {
|
||||
return self.type_name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, views_path: []const u8) Self {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.views_path = views_path,
|
||||
.buffer = std.ArrayList(u8).init(allocator),
|
||||
.static_routes = std.ArrayList(Function).init(allocator),
|
||||
.dynamic_routes = std.ArrayList(Function).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.buffer.deinit();
|
||||
self.static_routes.deinit();
|
||||
self.dynamic_routes.deinit();
|
||||
}
|
||||
|
||||
pub fn generateRoutes(self: *Self) !void {
|
||||
const writer = self.buffer.writer();
|
||||
|
||||
var views_dir = try std.fs.cwd().openDir(self.views_path, .{ .iterate = true });
|
||||
defer views_dir.close();
|
||||
|
||||
var walker = try views_dir.walk(self.allocator);
|
||||
defer walker.deinit();
|
||||
|
||||
while (try walker.next()) |entry| {
|
||||
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 routes = try self.generateRoute(views_dir, entry.path);
|
||||
|
||||
for (routes.static) |route| {
|
||||
try self.static_routes.append(route);
|
||||
}
|
||||
|
||||
for (routes.dynamic) |route| {
|
||||
try self.dynamic_routes.append(route);
|
||||
}
|
||||
}
|
||||
|
||||
std.sort.pdq(Function, self.static_routes.items, {}, Function.lessThanFn);
|
||||
std.sort.pdq(Function, self.dynamic_routes.items, {}, Function.lessThanFn);
|
||||
|
||||
try writer.writeAll("pub const routes = struct {\n");
|
||||
try writer.writeAll(" pub const static = .{\n");
|
||||
|
||||
for (self.static_routes.items) |static_route| {
|
||||
try self.writeRoute(writer, static_route);
|
||||
}
|
||||
|
||||
try writer.writeAll(" };\n\n");
|
||||
try writer.writeAll(" pub const dynamic = .{\n");
|
||||
|
||||
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");
|
||||
try writer.writeAll("};");
|
||||
}
|
||||
|
||||
fn writeRoute(self: *Self, writer: std.ArrayList(u8).Writer, route: Function) !void {
|
||||
const full_name = try route.fullName(self.allocator);
|
||||
defer self.allocator.free(full_name);
|
||||
|
||||
const uri_path = try route.uriPath(self.allocator);
|
||||
defer self.allocator.free(uri_path);
|
||||
|
||||
const output_template =
|
||||
\\ .{{
|
||||
\\ .name = "{s}",
|
||||
\\ .action = "{s}",
|
||||
\\ .uri_path = "{s}",
|
||||
\\ .template = "{s}",
|
||||
\\ .function = @import("{s}").{s},
|
||||
\\ }},
|
||||
\\
|
||||
;
|
||||
|
||||
const output = try std.fmt.allocPrint(self.allocator, output_template, .{
|
||||
full_name,
|
||||
route.name,
|
||||
uri_path,
|
||||
full_name,
|
||||
route.path,
|
||||
route.name,
|
||||
});
|
||||
|
||||
defer self.allocator.free(output);
|
||||
try writer.writeAll(output);
|
||||
}
|
||||
|
||||
const RouteSet = struct {
|
||||
dynamic: []Function,
|
||||
static: []Function,
|
||||
};
|
||||
|
||||
fn generateRoute(self: *Self, views_dir: std.fs.Dir, path: []const u8) !RouteSet {
|
||||
// REVIEW: Choose a sensible upper limit or allow user to take their own risks here ?
|
||||
const stat = try views_dir.statFile(path);
|
||||
const source = try views_dir.readFileAllocOptions(self.allocator, path, stat.size, null, @alignOf(u8), 0);
|
||||
defer self.allocator.free(source);
|
||||
|
||||
var ast = try std.zig.Ast.parse(self.allocator, source, .zig);
|
||||
defer ast.deinit(self.allocator);
|
||||
|
||||
var static_routes = std.ArrayList(Function).init(self.allocator);
|
||||
var dynamic_routes = std.ArrayList(Function).init(self.allocator);
|
||||
|
||||
for (ast.nodes.items(.tag), 0..) |tag, index| {
|
||||
const function = try self.parseTag(ast, tag, index, path, source);
|
||||
if (function) |capture| {
|
||||
for (capture.params) |param| {
|
||||
if (std.mem.eql(u8, try param.typeBasename(), "StaticRequest")) {
|
||||
try static_routes.append(capture);
|
||||
}
|
||||
if (std.mem.eql(u8, try param.typeBasename(), "Request")) {
|
||||
try dynamic_routes.append(capture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return .{ .dynamic = dynamic_routes.items, .static = static_routes.items };
|
||||
}
|
||||
|
||||
fn parseTag(
|
||||
self: *Self,
|
||||
ast: std.zig.Ast,
|
||||
tag: std.zig.Ast.Node.Tag,
|
||||
index: usize,
|
||||
path: []const u8,
|
||||
source: []const u8,
|
||||
) !?Function {
|
||||
switch (tag) {
|
||||
.fn_proto_multi => {
|
||||
const fn_proto = ast.fnProtoMulti(@as(u32, @intCast(index)));
|
||||
if (fn_proto.name_token) |token| {
|
||||
const function_name = try self.allocator.dupe(u8, ast.tokenSlice(token));
|
||||
var it = fn_proto.iterate(&ast);
|
||||
var params = std.ArrayList(Param).init(self.allocator);
|
||||
defer params.deinit();
|
||||
|
||||
while (it.next()) |param| {
|
||||
if (param.name_token) |param_token| {
|
||||
const param_name = ast.tokenSlice(param_token);
|
||||
const node = ast.nodes.get(param.type_expr);
|
||||
const type_name = try self.parseTypeExpr(ast, node);
|
||||
try params.append(.{ .name = param_name, .type_name = type_name });
|
||||
}
|
||||
}
|
||||
|
||||
return .{
|
||||
.name = function_name,
|
||||
.path = try self.allocator.dupe(u8, path),
|
||||
.params = try self.allocator.dupe(Param, params.items),
|
||||
.source = try self.allocator.dupe(u8, source),
|
||||
};
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn parseTypeExpr(self: *Self, ast: std.zig.Ast, node: std.zig.Ast.Node) ![]const u8 {
|
||||
switch (node.tag) {
|
||||
// Currently all expected params are pointers, keeping this here in case that changes in future:
|
||||
.identifier => {},
|
||||
.ptr_type_aligned => {
|
||||
var buf = std.ArrayList([]const u8).init(self.allocator);
|
||||
defer buf.deinit();
|
||||
|
||||
for (0..(ast.tokens.len - node.main_token)) |index| {
|
||||
const token = ast.tokens.get(node.main_token + index);
|
||||
switch (token.tag) {
|
||||
.asterisk, .period, .identifier => {
|
||||
try buf.append(ast.tokenSlice(@as(u32, @intCast(node.main_token + index))));
|
||||
},
|
||||
else => return try std.mem.concat(self.allocator, u8, buf.items),
|
||||
}
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
return error.JetzigAstParserError;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
const std = @import("std");
|
||||
const jetzig = @import("jetzig");
|
||||
const Request = jetzig.http.Request;
|
||||
const Data = jetzig.data.Data;
|
||||
const View = jetzig.views.View;
|
||||
|
||||
pub fn index(request: *jetzig.http.Request, data: *jetzig.data.Data) anyerror!jetzig.views.View {
|
||||
var object = try data.object();
|
||||
try object.put("message", data.string("Welcome to Jetzig!"));
|
||||
return request.render(.ok);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
pub const routes = .{
|
||||
@import("users.zig"),
|
||||
@import("quotes.zig"),
|
||||
@import("index.zig"),
|
||||
};
|
@ -1,17 +0,0 @@
|
||||
const jetzig = @import("jetzig");
|
||||
const std = @import("std");
|
||||
|
||||
const Request = jetzig.http.Request;
|
||||
const Data = jetzig.data.Data;
|
||||
const View = jetzig.views.View;
|
||||
|
||||
pub fn get(id: []const u8, request: *Request, data: *Data) anyerror!View {
|
||||
var user = try data.object();
|
||||
|
||||
try user.put("email", data.string("user@example.com"));
|
||||
try user.put("name", data.string("Ziggy Ziguana"));
|
||||
try user.put("id", data.string(id));
|
||||
try user.put("authenticated", data.boolean(true));
|
||||
|
||||
return request.render(.ok);
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
<div>
|
||||
if (std.crypto.random.int(u1) == 1) {
|
||||
<span>User won the coin toss!</span>
|
||||
}
|
||||
<span>ID: {.id}</span>
|
||||
<span>Name: {.name}</span>
|
||||
<span>Email: {.email}</span>
|
||||
</div>
|
69
src/compile_static_routes.zig
Normal file
69
src/compile_static_routes.zig
Normal file
@ -0,0 +1,69 @@
|
||||
const std = @import("std");
|
||||
const jetzig = @import("jetzig");
|
||||
const routes = @import("routes").routes;
|
||||
const templates = @import("templates").templates;
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer std.debug.assert(gpa.deinit() == .ok);
|
||||
const gpa_allocator = gpa.allocator();
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(gpa_allocator);
|
||||
const allocator = arena.allocator();
|
||||
defer arena.deinit();
|
||||
|
||||
try compileStaticRoutes(allocator);
|
||||
}
|
||||
|
||||
fn compileStaticRoutes(allocator: std.mem.Allocator) !void {
|
||||
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,
|
||||
),
|
||||
};
|
||||
const route = jetzig.views.Route{
|
||||
.name = static_route.name,
|
||||
.action = @field(jetzig.views.Route.Action, static_route.action),
|
||||
.view = static_view,
|
||||
.static = true,
|
||||
.uri_path = static_route.uri_path,
|
||||
.template = static_route.template,
|
||||
};
|
||||
|
||||
var request = try jetzig.http.StaticRequest.init(allocator);
|
||||
defer request.deinit();
|
||||
|
||||
const view = try route.renderStatic(route, &request);
|
||||
defer view.deinit();
|
||||
|
||||
const dir = try std.fs.cwd().makeOpenPath("static", .{});
|
||||
|
||||
const json_path = try std.mem.concat(
|
||||
allocator,
|
||||
u8,
|
||||
&[_][]const u8{ route.name, ".json" },
|
||||
);
|
||||
defer allocator.free(json_path);
|
||||
const json_file = try dir.createFile(json_path, .{ .truncate = true });
|
||||
try json_file.writeAll(try view.data.toJson());
|
||||
defer json_file.close();
|
||||
std.debug.print("[jetzig] Compiled static route: {s}\n", .{json_path});
|
||||
|
||||
if (@hasDecl(templates, route.template)) {
|
||||
const template = @field(templates, route.template);
|
||||
const html_path = try std.mem.concat(
|
||||
allocator,
|
||||
u8,
|
||||
&[_][]const u8{ route.name, ".html" },
|
||||
);
|
||||
defer allocator.free(html_path);
|
||||
const html_file = try dir.createFile(html_path, .{ .truncate = true });
|
||||
try html_file.writeAll(try template.render(view.data));
|
||||
defer html_file.close();
|
||||
std.debug.print("[jetzig] Compiled static route: {s}\n", .{html_path});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
[{"quote": "Life isn’t about getting and having, it’s about giving and being.", "author": "Kevin Kruse"},
|
||||
{"quote": "Whatever the mind of man can conceive and believe, it can achieve.", "author": "Napoleon Hill"},
|
||||
{"quote": "Strive not to be a success, but rather to be of value.", "author": "Albert Einstein"},
|
||||
{"quote": "Two roads diverged in a wood, and I—I took the one less traveled by, And that has made all the difference.", "author": "Robert Frost"},
|
||||
{"quote": "I attribute my success to this: I never gave or took any excuse.", "author": "Florence Nightingale"},
|
||||
{"quote": "You miss 100% of the shots you don’t take.", "author": "Wayne Gretzky"},
|
||||
{"quote": "I’ve missed more than 9000 shots in my career. I’ve lost almost 300 games. 26 times I’ve been trusted to take the game winning shot and missed. I’ve failed over and over and over again in my life. And that is why I succeed.", "author": "Michael Jordan"},
|
||||
{"quote": "The most difficult thing is the decision to act, the rest is merely tenacity.", "author": "Amelia Earhart"},
|
||||
{"quote": "Every strike brings me closer to the next home run.", "author": "Babe Ruth"},
|
||||
{"quote": "Definiteness of purpose is the starting point of all achievement.", "author": "W. Clement Stone"},
|
||||
{"quote": "We must balance conspicuous consumption with conscious capitalism.", "author": "Kevin Kruse"},
|
||||
{"quote": "Life is what happens to you while you’re busy making other plans.", "author": "John Lennon"},
|
||||
{"quote": "We become what we think about.", "author": "Earl Nightingale"},
|
||||
{"quote": "14.Twenty years from now you will be more disappointed by the things that you didn’t do than by the ones you did do, so throw off the bowlines, sail away from safe harbor, catch the trade winds in your sails. Explore, Dream, Discover.", "author": "Mark Twain"},
|
||||
{"quote": "15.Life is 10% what happens to me and 90% of how I react to it.", "author": "Charles Swindoll"},
|
||||
{"quote": "The most common way people give up their power is by thinking they don’t have any.", "author": "Alice Walker"},
|
||||
{"quote": "The mind is everything. What you think you become.", "author": "Buddha"},
|
||||
{"quote": "The best time to plant a tree was 20 years ago. The second best time is now.", "author": "Chinese Proverb"},
|
||||
{"quote": "An unexamined life is not worth living.", "author": "Socrates"},
|
||||
{"quote": "Eighty percent of success is showing up.", "author": "Woody Allen"},
|
||||
{"quote": "Your time is limited, so don’t waste it living someone else’s life.", "author": "Steve Jobs"},
|
||||
{"quote": "Winning isn’t everything, but wanting to win is.", "author": "Vince Lombardi"},
|
||||
{"quote": "I am not a product of my circumstances. I am a product of my decisions.", "author": "Stephen Covey"},
|
||||
{"quote": "Every child is an artist. The problem is how to remain an artist once he grows up.", "author": "Pablo Picasso"},
|
||||
{"quote": "You can never cross the ocean until you have the courage to lose sight of the shore.", "author": "Christopher Columbus"},
|
||||
{"quote": "I’ve learned that people will forget what you said, people will forget what you did, but people will never forget how you made them feel.", "author": "Maya Angelou"},
|
||||
{"quote": "Either you run the day, or the day runs you.", "author": "Jim Rohn"},
|
||||
{"quote": "Whether you think you can or you think you can’t, you’re right.", "author": "Henry Ford"},
|
||||
{"quote": "The two most important days in your life are the day you are born and the day you find out why.", "author": "Mark Twain"},
|
||||
{"quote": "Whatever you can do, or dream you can, begin it. Boldness has genius, power and magic in it.", "author": "Johann Wolfgang von Goethe"},
|
||||
{"quote": "The best revenge is massive success.", "author": "Frank Sinatra"},
|
||||
{"quote": "People often say that motivation doesn’t last. Well, neither does bathing. That’s why we recommend it daily.", "author": "Zig Ziglar"},
|
||||
{"quote": "Life shrinks or expands in proportion to one’s courage.", "author": "Anais Nin"},
|
||||
{"quote": "If you hear a voice within you say “you cannot paint,” then by all means paint and that voice will be silenced.", "author": "Vincent Van Gogh"},
|
||||
{"quote": "There is only one way to avoid criticism: do nothing, say nothing, and be nothing.", "author": "Aristotle"},
|
||||
{"quote": "Ask and it will be given to you; search, and you will find; knock and the door will be opened for you.", "author": "Jesus"},
|
||||
{"quote": "The only person you are destined to become is the person you decide to be.", "author": "Ralph Waldo Emerson"},
|
||||
{"quote": "Go confidently in the direction of your dreams. Live the life you have imagined.", "author": "Henry David Thoreau"},
|
||||
{"quote": "When I stand before God at the end of my life, I would hope that I would not have a single bit of talent left and could say, I used everything you gave me.", "author": "Erma Bombeck"},
|
||||
{"quote": "Few things can help an individual more than to place responsibility on him, and to let him know that you trust him.", "author": "Booker T. Washington"},
|
||||
{"quote": "Certain things catch your eye, but pursue only those that capture the heart.", "author": " Ancient Indian Proverb"},
|
||||
{"quote": "Believe you can and you’re halfway there.", "author": "Theodore Roosevelt"},
|
||||
{"quote": "Everything you’ve ever wanted is on the other side of fear.", "author": "George Addair"},
|
||||
{"quote": "We can easily forgive a child who is afraid of the dark; the real tragedy of life is when men are afraid of the light.", "author": "Plato"},
|
||||
{"quote": "Teach thy tongue to say, “I do not know,” and thous shalt progress.", "author": "Maimonides"},
|
||||
{"quote": "Start where you are. Use what you have. Do what you can.", "author": "Arthur Ashe"},
|
||||
{"quote": "When I was 5 years old, my mother always told me that happiness was the key to life. When I went to school, they asked me what I wanted to be when I grew up. I wrote down ‘happy’. They told me I didn’t understand the assignment, and I told them they didn’t understand life.", "author": "John Lennon"},
|
||||
{"quote": "Fall seven times and stand up eight.", "author": "Japanese Proverb"},
|
||||
{"quote": "When one door of happiness closes, another opens, but often we look so long at the closed door that we do not see the one that has been opened for us.", "author": "Helen Keller"},
|
||||
{"quote": "Everything has beauty, but not everyone can see.", "author": "Confucius"},
|
||||
{"quote": "How wonderful it is that nobody need wait a single moment before starting to improve the world.", "author": "Anne Frank"},
|
||||
{"quote": "When I let go of what I am, I become what I might be.", "author": "Lao Tzu"},
|
||||
{"quote": "Life is not measured by the number of breaths we take, but by the moments that take our breath away.", "author": "Maya Angelou"},
|
||||
{"quote": "Happiness is not something readymade. It comes from your own actions.", "author": "Dalai Lama"},
|
||||
{"quote": "If you’re offered a seat on a rocket ship, don’t ask what seat! Just get on.", "author": "Sheryl Sandberg"},
|
||||
{"quote": "First, have a definite, clear practical ideal; a goal, an objective. Second, have the necessary means to achieve your ends; wisdom, money, materials, and methods. Third, adjust all your means to that end.", "author": "Aristotle"},
|
||||
{"quote": "If the wind will not serve, take to the oars.", "author": "Latin Proverb"},
|
||||
{"quote": "You can’t fall if you don’t climb. But there’s no joy in living your whole life on the ground.", "author": "Unknown"},
|
||||
{"quote": "We must believe that we are gifted for something, and that this thing, at whatever cost, must be attained.", "author": "Marie Curie"},
|
||||
{"quote": "Too many of us are not living our dreams because we are living our fears.", "author": "Les Brown"},
|
||||
{"quote": "Challenges are what make life interesting and overcoming them is what makes life meaningful.", "author": "Joshua J. Marine"},
|
||||
{"quote": "If you want to lift yourself up, lift up someone else.", "author": "Booker T. Washington"},
|
||||
{"quote": "I have been impressed with the urgency of doing. Knowing is not enough; we must apply. Being willing is not enough; we must do.", "author": "Leonardo da Vinci"},
|
||||
{"quote": "Limitations live only in our minds. But if we use our imaginations, our possibilities become limitless.", "author": "Jamie Paolinetti"},
|
||||
{"quote": "You take your life in your own hands, and what happens? A terrible thing, no one to blame.", "author": "Erica Jong"},
|
||||
{"quote": "What’s money? A man is a success if he gets up in the morning and goes to bed at night and in between does what he wants to do.", "author": "Bob Dylan"},
|
||||
{"quote": "I didn’t fail the test. I just found 100 ways to do it wrong.", "author": "Benjamin Franklin"},
|
||||
{"quote": "In order to succeed, your desire for success should be greater than your fear of failure.", "author": "Bill Cosby"},
|
||||
{"quote": "A person who never made a mistake never tried anything new.", "author": " Albert Einstein"},
|
||||
{"quote": "The person who says it cannot be done should not interrupt the person who is doing it.", "author": "Chinese Proverb"},
|
||||
{"quote": "There are no traffic jams along the extra mile.", "author": "Roger Staubach"},
|
||||
{"quote": "It is never too late to be what you might have been.", "author": "George Eliot"},
|
||||
{"quote": "You become what you believe.", "author": "Oprah Winfrey"},
|
||||
{"quote": "I would rather die of passion than of boredom.", "author": "Vincent van Gogh"},
|
||||
{"quote": "A truly rich man is one whose children run into his arms when his hands are empty.", "author": "Unknown"},
|
||||
{"quote": "It is not what you do for your children, but what you have taught them to do for themselves, that will make them successful human beings.", "author": "Ann Landers"},
|
||||
{"quote": "If you want your children to turn out well, spend twice as much time with them, and half as much money.", "author": "Abigail Van Buren"},
|
||||
{"quote": "Build your own dreams, or someone else will hire you to build theirs.", "author": "Farrah Gray"},
|
||||
{"quote": "The battles that count aren’t the ones for gold medals. The struggles within yourself–the invisible battles inside all of us–that’s where it’s at.", "author": "Jesse Owens"},
|
||||
{"quote": "Education costs money. But then so does ignorance.", "author": "Sir Claus Moser"},
|
||||
{"quote": "I have learned over the years that when one’s mind is made up, this diminishes fear.", "author": "Rosa Parks"},
|
||||
{"quote": "It does not matter how slowly you go as long as you do not stop.", "author": "Confucius"},
|
||||
{"quote": "If you look at what you have in life, you’ll always have more. If you look at what you don’t have in life, you’ll never have enough.", "author": "Oprah Winfrey"},
|
||||
{"quote": "Remember that not getting what you want is sometimes a wonderful stroke of luck.", "author": "Dalai Lama"},
|
||||
{"quote": "You can’t use up creativity. The more you use, the more you have.", "author": "Maya Angelou"},
|
||||
{"quote": "Dream big and dare to fail.", "author": "Norman Vaughan"},
|
||||
{"quote": "Our lives begin to end the day we become silent about things that matter.", "author": "Martin Luther King Jr."},
|
||||
{"quote": "Do what you can, where you are, with what you have.", "author": "Teddy Roosevelt"},
|
||||
{"quote": "If you do what you’ve always done, you’ll get what you’ve always gotten.", "author": "Tony Robbins"},
|
||||
{"quote": "Dreaming, after all, is a form of planning.", "author": "Gloria Steinem"},
|
||||
{"quote": "It’s your place in the world; it’s your life. Go on and do all you can with it, and make it the life you want to live.", "author": "Mae Jemison"},
|
||||
{"quote": "You may be disappointed if you fail, but you are doomed if you don’t try.", "author": "Beverly Sills"},
|
||||
{"quote": "Remember no one can make you feel inferior without your consent.", "author": "Eleanor Roosevelt"},
|
||||
{"quote": "Life is what we make it, always has been, always will be.", "author": "Grandma Moses"},
|
||||
{"quote": "The question isn’t who is going to let me; it’s who is going to stop me.", "author": "Ayn Rand"},
|
||||
{"quote": "When everything seems to be going against you, remember that the airplane takes off against the wind, not with it.", "author": "Henry Ford"},
|
||||
{"quote": "It’s not the years in your life that count. It’s the life in your years.", "author": "Abraham Lincoln"},
|
||||
{"quote": "Change your thoughts and you change your world.", "author": "Norman Vincent Peale"},
|
||||
{"quote": "Either write something worth reading or do something worth writing.", "author": "Benjamin Franklin"},
|
||||
{"quote": "Nothing is impossible, the word itself says, “I’m possible!”", "author": "–Audrey Hepburn"},
|
||||
{"quote": "The only way to do great work is to love what you do.", "author": "Steve Jobs"},
|
||||
{"quote": "If you can dream it, you can achieve it.", "author": "Zig Ziglar"}]
|
@ -1,6 +1,6 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const zmpl = @import("zmpl");
|
||||
pub const zmpl = @import("zmpl").zmpl;
|
||||
|
||||
pub const http = @import("jetzig/http.zig");
|
||||
pub const loggers = @import("jetzig/loggers.zig");
|
||||
@ -67,37 +67,58 @@ pub fn init(allocator: std.mem.Allocator) !App {
|
||||
// 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 modules: anytype) []views.Route {
|
||||
pub fn route(comptime routes: anytype) []views.Route {
|
||||
var size: usize = 0;
|
||||
|
||||
for (modules) |module| {
|
||||
const decls = @typeInfo(module).Struct.decls;
|
||||
for (routes.dynamic) |_| {
|
||||
size += 1;
|
||||
}
|
||||
|
||||
for (decls) |decl| {
|
||||
if (@hasField(views.Route.ViewType, decl.name)) size += 1;
|
||||
}
|
||||
for (routes.static) |_| {
|
||||
size += 1;
|
||||
}
|
||||
|
||||
var detected: [size]views.Route = undefined;
|
||||
var index: usize = 0;
|
||||
|
||||
for (modules) |module| {
|
||||
const decls = @typeInfo(module).Struct.decls;
|
||||
for (routes.dynamic) |dynamic_route| {
|
||||
const view = views.Route.ViewType{
|
||||
.dynamic = @unionInit(
|
||||
views.Route.DynamicViewType,
|
||||
dynamic_route.action,
|
||||
dynamic_route.function,
|
||||
),
|
||||
};
|
||||
|
||||
for (decls) |decl| {
|
||||
if (!@hasField(views.Route.ViewType, decl.name)) {
|
||||
// TODO: Figure out how to log a warning here (comptime issues).
|
||||
continue;
|
||||
}
|
||||
const view = @unionInit(views.Route.ViewType, decl.name, @field(module, decl.name));
|
||||
detected[index] = .{
|
||||
.name = dynamic_route.name,
|
||||
.action = @field(views.Route.Action, dynamic_route.action),
|
||||
.view = view,
|
||||
.static = false,
|
||||
.uri_path = dynamic_route.uri_path,
|
||||
.template = dynamic_route.template,
|
||||
};
|
||||
index += 1;
|
||||
}
|
||||
|
||||
detected[index] = .{
|
||||
.name = @typeName(module),
|
||||
.action = @field(views.Route.Action, decl.name),
|
||||
.view = view,
|
||||
};
|
||||
index += 1;
|
||||
}
|
||||
for (routes.static) |static_route| {
|
||||
const view = views.Route.ViewType{
|
||||
.static = @unionInit(
|
||||
views.Route.StaticViewType,
|
||||
static_route.action,
|
||||
static_route.function,
|
||||
),
|
||||
};
|
||||
|
||||
detected[index] = .{
|
||||
.name = static_route.name,
|
||||
.action = @field(views.Route.Action, static_route.action),
|
||||
.view = view,
|
||||
.static = true,
|
||||
.uri_path = static_route.uri_path,
|
||||
.template = static_route.template,
|
||||
};
|
||||
index += 1;
|
||||
}
|
||||
|
||||
return &detected;
|
||||
|
@ -14,13 +14,13 @@ pub fn deinit(self: Self) void {
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn start(self: Self, views: []jetzig.views.Route, templates: []jetzig.TemplateFn) !void {
|
||||
pub fn start(self: Self, routes: []jetzig.views.Route, templates: []jetzig.TemplateFn) !void {
|
||||
var server = jetzig.http.Server.init(
|
||||
self.allocator,
|
||||
self.host,
|
||||
self.port,
|
||||
self.server_options,
|
||||
views,
|
||||
routes,
|
||||
templates,
|
||||
);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
const std = @import("std");
|
||||
|
||||
const zmpl = @import("zmpl");
|
||||
const zmpl = @import("zmpl").zmpl;
|
||||
|
||||
pub const Writer = zmpl.Data.Writer;
|
||||
pub const Data = zmpl.Data;
|
||||
|
@ -1,9 +1,8 @@
|
||||
const std = @import("std");
|
||||
|
||||
const colors = @import("colors.zig");
|
||||
|
||||
pub const Server = @import("http/Server.zig");
|
||||
pub const Request = @import("http/Request.zig");
|
||||
pub const StaticRequest = @import("http/StaticRequest.zig");
|
||||
pub const Response = @import("http/Response.zig");
|
||||
pub const Session = @import("http/Session.zig");
|
||||
pub const Cookies = @import("http/Cookies.zig");
|
||||
|
@ -23,9 +23,24 @@ cookies: *jetzig.http.Cookies,
|
||||
pub fn init(
|
||||
allocator: std.mem.Allocator,
|
||||
server: *jetzig.http.Server,
|
||||
response: *std.http.Server.Response,
|
||||
response: ?*std.http.Server.Response,
|
||||
) !Self {
|
||||
const method = switch (response.request.method) {
|
||||
if (response) |_| {} else {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.path = "/",
|
||||
.method = .GET,
|
||||
.headers = jetzig.http.Headers.init(allocator, std.http.Headers{ .allocator = allocator }),
|
||||
.server = server,
|
||||
.segments = std.ArrayList([]const u8).init(allocator),
|
||||
.cookies = try allocator.create(jetzig.http.Cookies),
|
||||
.session = try allocator.create(jetzig.http.Session),
|
||||
.response_data = try allocator.create(jetzig.data.Data),
|
||||
};
|
||||
}
|
||||
|
||||
const resp = response.?;
|
||||
const method = switch (resp.request.method) {
|
||||
.DELETE => Method.DELETE,
|
||||
.GET => Method.GET,
|
||||
.PATCH => Method.PATCH,
|
||||
@ -35,17 +50,17 @@ pub fn init(
|
||||
.CONNECT => Method.CONNECT,
|
||||
.OPTIONS => Method.OPTIONS,
|
||||
.TRACE => Method.TRACE,
|
||||
_ => return error.jetzig_unsupported_http_method,
|
||||
_ => return error.JetzigUnsupportedHttpMethod,
|
||||
};
|
||||
|
||||
var it = std.mem.splitScalar(u8, response.request.target, '/');
|
||||
var it = std.mem.splitScalar(u8, resp.request.target, '/');
|
||||
var segments = std.ArrayList([]const u8).init(allocator);
|
||||
while (it.next()) |segment| try segments.append(segment);
|
||||
|
||||
var cookies = try allocator.create(jetzig.http.Cookies);
|
||||
cookies.* = jetzig.http.Cookies.init(
|
||||
allocator,
|
||||
response.request.headers.getFirstValue("Cookie") orelse "",
|
||||
resp.request.headers.getFirstValue("Cookie") orelse "",
|
||||
);
|
||||
try cookies.parse();
|
||||
|
||||
@ -66,9 +81,9 @@ pub fn init(
|
||||
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.path = response.request.target,
|
||||
.path = resp.request.target,
|
||||
.method = method,
|
||||
.headers = jetzig.http.Headers.init(allocator, response.request.headers),
|
||||
.headers = jetzig.http.Headers.init(allocator, resp.request.headers),
|
||||
.server = server,
|
||||
.segments = segments,
|
||||
.cookies = cookies,
|
||||
@ -150,6 +165,8 @@ pub fn resourceModifier(self: *Self) ?Modifier {
|
||||
}
|
||||
|
||||
pub fn resourceName(self: *Self) []const u8 {
|
||||
if (self.segments.items.len == 0) return "default";
|
||||
|
||||
const basename = std.fs.path.basename(self.segments.items[self.segments.items.len - 1]);
|
||||
const extension = std.fs.path.extension(basename);
|
||||
return basename[0 .. basename.len - extension.len];
|
||||
@ -172,14 +189,8 @@ pub fn match(self: *Self, route: jetzig.views.Route) !bool {
|
||||
switch (self.method) {
|
||||
.GET => {
|
||||
return switch (route.action) {
|
||||
.index => blk: {
|
||||
if (std.mem.eql(u8, self.path, "/") and std.mem.eql(u8, route.name, "app.views.index")) {
|
||||
break :blk true;
|
||||
} else {
|
||||
break :blk std.mem.eql(u8, try self.fullName(), route.name);
|
||||
}
|
||||
},
|
||||
.get => std.mem.eql(u8, try self.fullNameWithStrippedResourceId(), route.name),
|
||||
.index => std.mem.eql(u8, self.pathWithoutExtension(), route.uri_path),
|
||||
.get => std.mem.eql(u8, self.pathWithoutResourceId(), route.uri_path),
|
||||
else => false,
|
||||
};
|
||||
},
|
||||
@ -193,16 +204,20 @@ pub fn match(self: *Self, route: jetzig.views.Route) !bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
fn isEditAction(self: *Self) bool {
|
||||
if (self.resourceModifier()) |modifier| {
|
||||
return modifier == .edit;
|
||||
} else return false;
|
||||
fn pathWithoutExtension(self: *Self) []const u8 {
|
||||
const index = std.mem.indexOfScalar(u8, self.path, '.');
|
||||
if (index) |capture| return self.path[0..capture] else return self.path;
|
||||
}
|
||||
|
||||
fn isNewAction(self: *Self) bool {
|
||||
if (self.resourceModifier()) |modifier| {
|
||||
return modifier == .new;
|
||||
} else return false;
|
||||
fn pathWithoutResourceId(self: *Self) []const u8 {
|
||||
const path = self.pathWithoutExtension();
|
||||
const index = std.mem.lastIndexOfScalar(u8, self.path, '/');
|
||||
if (index) |capture| {
|
||||
if (capture == 0) return "/";
|
||||
return path[0..capture];
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
fn fullName(self: *Self) ![]const u8 {
|
||||
@ -216,13 +231,12 @@ fn fullNameWithStrippedResourceId(self: *Self) ![]const u8 {
|
||||
fn name(self: *Self, with_resource_id: bool) ![]const u8 {
|
||||
const dirname = try std.mem.join(
|
||||
self.allocator,
|
||||
".",
|
||||
"_",
|
||||
self.segments.items[0 .. self.segments.items.len - 1],
|
||||
);
|
||||
defer self.allocator.free(dirname);
|
||||
|
||||
return std.mem.concat(self.allocator, u8, &[_][]const u8{
|
||||
"app.views",
|
||||
dirname,
|
||||
if (with_resource_id) "." else "",
|
||||
if (with_resource_id) self.resourceName() else "",
|
||||
|
@ -130,6 +130,7 @@ fn processNextRequest(self: *Self, response: *std.http.Server.Response) !void {
|
||||
response.transfer_encoding = .{ .content_length = result.value.content.len };
|
||||
var cookie_it = request.cookies.headerIterator();
|
||||
while (try cookie_it.next()) |header| {
|
||||
// FIXME: Skip setting cookies that are already present ?
|
||||
try response.headers.append("Set-Cookie", header);
|
||||
}
|
||||
try response.headers.append("Content-Type", result.value.content_type);
|
||||
@ -192,12 +193,11 @@ fn renderResponse(self: *Self, request: *jetzig.http.Request) !jetzig.http.Respo
|
||||
|
||||
fn renderStatic(self: *Self, request: *jetzig.http.Request, resource: StaticResource) !jetzig.http.Response {
|
||||
_ = request;
|
||||
// TODO: Select an appropriate header from MIME type and add to request.response.headers
|
||||
return .{
|
||||
.allocator = self.allocator,
|
||||
.status_code = .ok,
|
||||
.content = resource.content,
|
||||
.content_type = "application/octet-stream",
|
||||
.content_type = resource.mime_type,
|
||||
};
|
||||
}
|
||||
|
||||
@ -207,13 +207,9 @@ fn renderHTML(
|
||||
route: ?jetzig.views.Route,
|
||||
) !jetzig.http.Response {
|
||||
if (route) |matched_route| {
|
||||
const expected_name = try matched_route.templateName(self.allocator);
|
||||
defer self.allocator.free(expected_name);
|
||||
|
||||
for (self.templates) |template| {
|
||||
// FIXME: Tidy this up and use a hashmap for templates (or a more comprehensive
|
||||
// matching system) instead of an array.
|
||||
if (std.mem.eql(u8, expected_name, template.name)) {
|
||||
// TODO: Use a hashmap to avoid O(n)
|
||||
if (std.mem.eql(u8, matched_route.template, template.name)) {
|
||||
const rendered = try self.renderView(matched_route, request, template);
|
||||
return .{
|
||||
.allocator = self.allocator,
|
||||
@ -270,11 +266,11 @@ const RenderedView = struct { view: jetzig.views.View, content: []const u8 };
|
||||
|
||||
fn renderView(
|
||||
self: *Self,
|
||||
matched_route: jetzig.views.Route,
|
||||
route: jetzig.views.Route,
|
||||
request: *jetzig.http.Request,
|
||||
template: ?jetzig.TemplateFn,
|
||||
) !RenderedView {
|
||||
const view = matched_route.render(matched_route, request) catch |err| {
|
||||
const view = route.render(route, request) catch |err| {
|
||||
self.logger.debug("Encountered error: {s}", .{@errorName(err)});
|
||||
if (isUnhandledError(err)) return err;
|
||||
return try self.internalServerError(request, err);
|
||||
@ -357,9 +353,25 @@ fn matchRoute(self: *Self, request: *jetzig.http.Request) !?jetzig.views.Route {
|
||||
return null;
|
||||
}
|
||||
|
||||
const StaticResource = struct { content: []const u8, mime_type: []const u8 = undefined };
|
||||
const StaticResource = struct { content: []const u8, mime_type: []const u8 = "application/octet-stream" };
|
||||
|
||||
fn matchStaticResource(self: *Self, request: *jetzig.http.Request) !?StaticResource {
|
||||
const public_content = try self.matchPublicContent(request);
|
||||
if (public_content) |content| return .{ .content = content };
|
||||
|
||||
const static_content = try self.matchStaticContent(request);
|
||||
if (static_content) |content| return .{
|
||||
.content = content,
|
||||
.mime_type = switch (request.requestFormat()) {
|
||||
.HTML, .UNKNOWN => "text/html",
|
||||
.JSON => "application/json",
|
||||
},
|
||||
};
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn matchPublicContent(self: *Self, request: *jetzig.http.Request) !?[]const u8 {
|
||||
_ = self;
|
||||
|
||||
if (request.path.len < 2) return null;
|
||||
@ -371,19 +383,54 @@ fn matchStaticResource(self: *Self, request: *jetzig.http.Request) !?StaticResou
|
||||
else => return err,
|
||||
}
|
||||
};
|
||||
|
||||
var walker = try iterable_dir.walk(request.allocator);
|
||||
|
||||
while (try walker.next()) |file| {
|
||||
if (file.kind != .file) continue;
|
||||
|
||||
if (std.mem.eql(u8, file.path, request.path[1..])) {
|
||||
return .{
|
||||
.content = try iterable_dir.readFileAlloc(
|
||||
request.allocator,
|
||||
file.path,
|
||||
jetzig.config.max_bytes_static_content,
|
||||
),
|
||||
return try iterable_dir.readFileAlloc(
|
||||
request.allocator,
|
||||
file.path,
|
||||
jetzig.config.max_bytes_static_content,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn matchStaticContent(self: *Self, request: *jetzig.http.Request) !?[]const u8 {
|
||||
var static_dir = std.fs.cwd().openDir("static", .{}) catch |err| {
|
||||
switch (err) {
|
||||
error.FileNotFound => return null,
|
||||
else => return err,
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Use a hashmap to avoid O(n)
|
||||
for (self.routes) |route| {
|
||||
if (route.static and try request.match(route)) {
|
||||
const extension = switch (request.requestFormat()) {
|
||||
.HTML, .UNKNOWN => ".html",
|
||||
.JSON => ".json",
|
||||
};
|
||||
|
||||
const path = try std.mem.concat(request.allocator, u8, &[_][]const u8{ route.name, extension });
|
||||
|
||||
return static_dir.readFileAlloc(
|
||||
request.allocator,
|
||||
path,
|
||||
jetzig.config.max_bytes_static_content,
|
||||
) catch |err| {
|
||||
switch (err) {
|
||||
error.FileNotFound => return null,
|
||||
else => return err,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
26
src/jetzig/http/StaticRequest.zig
Normal file
26
src/jetzig/http/StaticRequest.zig
Normal file
@ -0,0 +1,26 @@
|
||||
const std = @import("std");
|
||||
const Self = @This();
|
||||
const jetzig = @import("../../jetzig.zig");
|
||||
|
||||
response_data: *jetzig.data.Data,
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) !Self {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.response_data = try allocator.create(jetzig.data.Data),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn render(self: *Self, status_code: jetzig.http.status_codes.StatusCode) jetzig.views.View {
|
||||
return .{ .data = self.response_data, .status_code = status_code };
|
||||
}
|
||||
|
||||
pub fn resourceId(self: *Self) []const u8 {
|
||||
_ = self;
|
||||
return "TODO";
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
const std = @import("std");
|
||||
|
||||
const root = @import("root");
|
||||
const jetzig = @import("../jetzig.zig");
|
||||
|
||||
pub const StatusCode = enum {
|
||||
ok,
|
||||
@ -20,13 +20,13 @@ pub fn StatusCodeType(comptime code: []const u8, comptime message: []const u8) t
|
||||
const full_message = code ++ " " ++ message;
|
||||
|
||||
if (std.mem.startsWith(u8, code, "2")) {
|
||||
return root.colors.green(full_message);
|
||||
return jetzig.colors.green(full_message);
|
||||
} else if (std.mem.startsWith(u8, code, "3")) {
|
||||
return root.colors.blue(full_message);
|
||||
return jetzig.colors.blue(full_message);
|
||||
} else if (std.mem.startsWith(u8, code, "4")) {
|
||||
return root.colors.yellow(full_message);
|
||||
return jetzig.colors.yellow(full_message);
|
||||
} else if (std.mem.startsWith(u8, code, "5")) {
|
||||
return root.colors.red(full_message);
|
||||
return jetzig.colors.red(full_message);
|
||||
} else {
|
||||
return full_message;
|
||||
}
|
||||
|
@ -1,4 +1,2 @@
|
||||
const root = @import("root");
|
||||
|
||||
pub const Route = @import("views/Route.zig");
|
||||
pub const View = @import("views/View.zig");
|
||||
|
@ -6,11 +6,14 @@ const Self = @This();
|
||||
|
||||
pub const Action = enum { index, get, post, put, patch, delete };
|
||||
pub const RenderFn = *const fn (Self, *jetzig.http.Request) anyerror!jetzig.views.View;
|
||||
pub const RenderStaticFn = *const fn (Self, *jetzig.http.StaticRequest) anyerror!jetzig.views.View;
|
||||
|
||||
const ViewWithoutId = *const fn (*jetzig.http.Request, *jetzig.data.Data) anyerror!jetzig.views.View;
|
||||
const ViewWithId = *const fn (id: []const u8, *jetzig.http.Request, *jetzig.data.Data) anyerror!jetzig.views.View;
|
||||
const StaticViewWithoutId = *const fn (*jetzig.http.StaticRequest, *jetzig.data.Data) anyerror!jetzig.views.View;
|
||||
const StaticViewWithId = *const fn (id: []const u8, *jetzig.http.StaticRequest, *jetzig.data.Data) anyerror!jetzig.views.View;
|
||||
|
||||
pub const ViewType = union(Action) {
|
||||
pub const DynamicViewType = union(Action) {
|
||||
index: ViewWithoutId,
|
||||
get: ViewWithId,
|
||||
post: ViewWithoutId,
|
||||
@ -19,33 +22,53 @@ pub const ViewType = union(Action) {
|
||||
delete: ViewWithId,
|
||||
};
|
||||
|
||||
pub const StaticViewType = union(Action) {
|
||||
index: StaticViewWithoutId,
|
||||
get: StaticViewWithId,
|
||||
post: StaticViewWithoutId,
|
||||
put: StaticViewWithId,
|
||||
patch: StaticViewWithId,
|
||||
delete: StaticViewWithId,
|
||||
};
|
||||
|
||||
pub const ViewType = union(enum) {
|
||||
static: StaticViewType,
|
||||
dynamic: DynamicViewType,
|
||||
};
|
||||
|
||||
name: []const u8,
|
||||
action: Action,
|
||||
view: ViewType,
|
||||
uri_path: []const u8,
|
||||
view: ?ViewType = null,
|
||||
static_view: ?StaticViewType = null,
|
||||
static: bool,
|
||||
render: RenderFn = renderFn,
|
||||
|
||||
pub fn templateName(self: Self, allocator: std.mem.Allocator) ![]const u8 {
|
||||
if (std.mem.eql(u8, self.name, "app.views.index") and self.action == .index)
|
||||
return try allocator.dupe(u8, "index");
|
||||
|
||||
const underscored_name = try std.mem.replaceOwned(u8, allocator, self.name, ".", "_");
|
||||
defer allocator.free(underscored_name);
|
||||
|
||||
// FIXME: Store names in a normalised way so we don't need to do this stuff:
|
||||
const unprefixed = try allocator.dupe(u8, underscored_name["app_views_".len..self.name.len]);
|
||||
defer allocator.free(unprefixed);
|
||||
|
||||
const suffixed = try std.mem.concat(allocator, u8, &[_][]const u8{
|
||||
unprefixed,
|
||||
"_",
|
||||
@tagName(self.action),
|
||||
});
|
||||
|
||||
return suffixed;
|
||||
}
|
||||
renderStatic: RenderStaticFn = renderStaticFn,
|
||||
template: []const u8,
|
||||
|
||||
fn renderFn(self: Self, request: *jetzig.http.Request) anyerror!jetzig.views.View {
|
||||
switch (self.view) {
|
||||
switch (self.view.?) {
|
||||
.dynamic => {},
|
||||
// We only end up here if a static route is defined but its output is not found in the
|
||||
// file system (e.g. if it was manually deleted after build). This should be avoidable by
|
||||
// including the content as an artifact in the compiled executable (TODO):
|
||||
.static => return error.JetzigMissingStaticContent,
|
||||
}
|
||||
|
||||
switch (self.view.?.dynamic) {
|
||||
.index => |view| return try view(request, request.response_data),
|
||||
.get => |view| return try view(request.resourceId(), request, request.response_data),
|
||||
.post => |view| return try view(request, request.response_data),
|
||||
.patch => |view| return try view(request.resourceId(), request, request.response_data),
|
||||
.put => |view| return try view(request.resourceId(), request, request.response_data),
|
||||
.delete => |view| return try view(request.resourceId(), request, request.response_data),
|
||||
}
|
||||
}
|
||||
|
||||
fn renderStaticFn(self: Self, request: *jetzig.http.StaticRequest) anyerror!jetzig.views.View {
|
||||
request.response_data.* = jetzig.data.Data.init(request.allocator);
|
||||
|
||||
switch (self.view.?.static) {
|
||||
.index => |view| return try view(request, request.response_data),
|
||||
.get => |view| return try view(request.resourceId(), request, request.response_data),
|
||||
.post => |view| return try view(request, request.response_data),
|
||||
|
@ -1,6 +1,12 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Self = @This();
|
||||
|
||||
const jetzig = @import("../../jetzig.zig");
|
||||
|
||||
data: *jetzig.data.Data,
|
||||
status_code: jetzig.http.status_codes.StatusCode,
|
||||
|
||||
pub fn deinit(self: Self) void {
|
||||
_ = self;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const jetzig = @import("jetzig");
|
||||
pub const jetzig = @import("jetzig.zig");
|
||||
pub const templates = @import("app/views/zmpl.manifest.zig").templates;
|
||||
pub const routes = @import("app/views/routes.zig").routes;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user