Merge pull request #55 from jetzig-framework/zmpl-v2

Zmpl v2
This commit is contained in:
bobf 2024-04-07 10:37:13 +01:00 committed by GitHub
commit b59292307c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 87 additions and 50 deletions

View File

@ -7,11 +7,20 @@ 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");
const zmpl_build = @import("zmpl");
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const template_path = b.option([]const u8, "zmpl_templates_path", "Path to templates") orelse "src/app/views/";
const template_path_option = b.option([]const u8, "zmpl_templates_path", "Path to templates") orelse
"src/app/views/";
const template_path: []const u8 = if (std.fs.path.isAbsolute(template_path_option))
try b.allocator.dupe(u8, template_path_option)
else
std.fs.cwd().realpathAlloc(b.allocator, template_path_option) catch |err| switch (err) {
error.FileNotFound => "",
else => return err,
};
const lib = b.addStaticLibrary(.{
.name = "jetzig",
@ -28,6 +37,24 @@ pub fn build(b: *std.Build) !void {
jetzig_module.addImport("mime_types", mime_module);
lib.root_module.addImport("jetzig", jetzig_module);
const zmpl_version = b.option(
enum { v1, v2 },
"zmpl_version",
"Zmpl syntax version (default: v1)",
) orelse .v2;
if (zmpl_version == .v1) {
std.debug.print(
\\[WARN] Zmpl v1 is deprecated and will soon be removed.
\\ Update to v2 by modifying `jetzigInit` in your `build.zig`:
\\
\\ try jetzig.jetzigInit(b, exe, .{{ .zmpl_version = .v2 }});
\\
\\ See https://jetzig.dev/documentation.html for information on migrating to Zmpl v2.
\\
, .{});
}
const zmpl_dep = b.dependency(
"zmpl",
.{
@ -35,10 +62,16 @@ pub fn build(b: *std.Build) !void {
.optimize = optimize,
.zmpl_templates_path = template_path,
.zmpl_auto_build = false,
.zmpl_version = zmpl_version,
.zmpl_constants = try zmpl_build.addTemplateConstants(b, struct {
jetzig_view: []const u8,
jetzig_action: []const u8,
}),
},
);
const zmpl_module = zmpl_dep.module("zmpl");
// This is the way to make it look nice in the zig build script
// If we would do it the other way around, we would have to do
// b.dependency("jetzig",.{}).builder.dependency("zmpl",.{}).module("zmpl");
@ -46,19 +79,6 @@ pub fn build(b: *std.Build) !void {
const zmd_dep = b.dependency("zmd", .{ .target = target, .optimize = optimize });
const ZmplBuild = @import("zmpl").ZmplBuild;
const ZmplTemplate = @import("zmpl").Template;
var zmpl_build = ZmplBuild.init(b, lib, template_path);
const TemplateConstants = struct {
jetzig_view: []const u8,
jetzig_action: []const u8,
};
const ZmplOptions = struct {
pub const template_constants = TemplateConstants;
};
const manifest_module = try zmpl_build.compile(ZmplTemplate, ZmplOptions);
zmpl_module.addImport("zmpl.manifest", manifest_module);
lib.root_module.addImport("zmpl", zmpl_module);
jetzig_module.addImport("zmpl", zmpl_module);
jetzig_module.addImport("args", zig_args_dep.module("args"));
@ -86,17 +106,17 @@ pub fn build(b: *std.Build) !void {
test_step.dependOn(&run_main_tests.step);
}
/// Placeholder for potential options we may add in future without breaking
/// backward-compatibility.
pub const JetzigInitOptions = struct {};
/// Build-time options for Jetzig.
pub const JetzigInitOptions = struct {
zmpl_version: enum { v1, v2 } = .v1,
};
pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigInitOptions) !void {
_ = options;
const target = b.host;
const optimize = exe.root_module.optimize orelse .Debug;
const jetzig_dep = b.dependency(
"jetzig",
.{ .optimize = optimize, .target = b.host },
.{ .optimize = optimize, .target = target, .zmpl_version = options.zmpl_version },
);
const jetzig_module = jetzig_dep.module("jetzig");
const zmpl_module = jetzig_dep.module("zmpl");
@ -159,5 +179,6 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
exe_static_routes.root_module.addImport("jetzig_app", &exe.root_module);
const run_static_routes_cmd = b.addRunArtifact(exe_static_routes);
run_static_routes_cmd.expectExitCode(0);
exe.step.dependOn(&run_static_routes_cmd.step);
}

View File

@ -7,8 +7,8 @@
.hash = "1220bfc5c29bc930b5a524c210712ef65c6cde6770450899bef01164a3089e6707fa",
},
.zmpl = .{
.url = "https://github.com/jetzig-framework/zmpl/archive/ffdbd3767da28d5ab07c1a786ea778152d2f79f6.tar.gz",
.hash = "12202cf05fd4ba2482a9b4b89c632b435310a76ac501b7a3d87dfd41006748dd138d",
.url = "https://github.com/jetzig-framework/zmpl/archive/d907a96ffa28721477f5c8895732a4a9396276f5.tar.gz",
.hash = "1220e96969db44c451577e6a59a0d99af73b4370781c9361e3ea7e0d6f24c7f13abb",
},
.args = .{
.url = "https://github.com/MasterQ32/zig-args/archive/01d72b9a0128c474aeeb9019edd48605fa6d95f7.tar.gz",

View File

@ -18,7 +18,7 @@ pub fn build(b: *std.Build) !void {
// All dependencies **must** be added to imports above this line.
try jetzig.jetzigInit(b, exe, .{});
try jetzig.jetzigInit(b, exe, .{ .zmpl_version = .v2 });
b.installArtifact(exe);

View File

@ -1,6 +1,7 @@
<div>
var it = zmpl.value.?.array.iterator();
while (it.next()) |iguana| {
<div>{(iguana.string.value)}</div>
@zig {
for (zmpl.items(.array)) |iguana| {
<div>{{iguana}}</div>
}
}
</div>

View File

@ -1,3 +1,4 @@
const std = @import("std");
const jetzig = @import("jetzig");
/// `src/app/views/root.zig` represents the root URL `/`
@ -18,7 +19,7 @@ pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
var root = try data.object();
// Add a string to the root object.
try root.put("message", data.string("Welcome to Jetzig!"));
try root.put("welcome_message", data.string("Welcome to Jetzig!"));
// Request params have the same type as a `data.object()` so they can be inserted them
// directly into the response data. Fetch `http://localhost:8080/?message=hello` to set the
@ -26,9 +27,7 @@ pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
// present.
const params = try request.params();
if (params.get("message")) |value| {
try root.put("message_param", value);
}
try root.put("message_param", params.get("message"));
// Set arbitrary response headers as required. `content-type` is automatically assigned for
// HTML, JSON responses.

View File

@ -1,5 +1,5 @@
// Renders the `message` response data value.
<h3 class="message text-[#39b54a]">{.message}</h3>
@args message: *ZmplValue
<h3 class="message text-[#39b54a]">{{message}}</h3>
<div><img class="p-3 mx-auto" src="/jetzig.png" /></div>

View File

@ -9,12 +9,14 @@
<body>
<div class="text-center pt-10 m-auto">
// If present, renders the `message_param` response data value, add `?message=hello` to the
// URL to see the output:
<h2 class="param text-3xl text-[#f7931e]">{.message_param}</h2>
<!-- If present, renders the `message_param` response data value, add `?message=hello` to the
URL to see the output: -->
<h2 class="param text-3xl text-[#f7931e]">{{.message_param}}</h2>
// Renders `src/app/views/init/_content.zmpl` with the same template data available:
<div>{^init/content}</div>
<!-- Renders `src/app/views/init/_content.zmpl`, passing in the `welcome_message` field from template data. -->
<div>
@partial init/content(message: .welcome_message)
</div>
</div>
</body>
</html>

View File

@ -9,7 +9,7 @@
</head>
<body>
<main>{zmpl.content}</main>
<main>{{zmpl.content}}</main>
<script src="/prism.js"></script>
</body>
</html>

View File

@ -0,0 +1,7 @@
# Dynamic Markdown Routes
_Markdown_ can be stored in any path in `src/app/views/` and _Jetzig_ will automatically render it if it matches a URI.
This _Markdown_ page can be accessed at `/nested/route/markdown.html` and `/nested/route/markdown`.
This functionality is particularly useful if you want to load _Markdown_ content with [htmx](https://htmx.org/).

View File

@ -1,2 +1,2 @@
<div>"{.quote}"</div>
<div><b>--{.author}</b></div>
<div>"{{.quote}}"</div>
<div><b>--{{.author}}</b></div>

View File

@ -1 +1 @@
<div>{.param}</div>
<div>{{.param}}</div>

View File

@ -1,5 +1,6 @@
@args message: *ZmplValue
<div>
<h1 class="text-3xl text-center p-3 pb-6 font-bold">{.message}</h1>
<h1 class="text-3xl text-center p-3 pb-6 font-bold">{{message}}</h1>
</div>
<button hx-get="/quotes/random" hx-trigger="click" hx-target="#quote" class="bg-[#39b54a] text-white font-bold py-2 px-4 rounded">Click Me</button>

View File

@ -1,8 +1,11 @@
<div class="text-center pt-10 m-auto">
<div><img class="p-3 mx-auto" src="/jetzig.png" /></div>
// Renders `src/app/views/root/_quotes.zmpl`:
<div>{^root/quotes}</div>
<!-- Renders `src/app/views/root/_quotes.zmpl`: -->
<div>
@partial root/quotes(message: .message)
</div>
<div>
<a href="https://github.com/jetzig-framework/zmpl">

1
demo/zmpl_options.zig Normal file
View File

@ -0,0 +1 @@
hello

View File

@ -181,6 +181,8 @@ fn writeRoute(self: *Self, writer: std.ArrayList(u8).Writer, route: Function) !v
std.mem.replaceScalar(u8, view_name, '\\', '/');
defer self.allocator.free(view_name);
const template = try std.mem.concat(self.allocator, u8, &[_][]const u8{ view_name, "/", route.name });
std.mem.replaceScalar(u8, module_path, '\\', '/');
const output = try std.fmt.allocPrint(self.allocator, output_template, .{
@ -190,7 +192,7 @@ fn writeRoute(self: *Self, writer: std.ArrayList(u8).Writer, route: Function) !v
if (route.static) "static" else "dynamic",
if (route.static) "true" else "false",
uri_path,
full_name,
template,
module_path,
try std.mem.join(self.allocator, ", \n", route.params.items),
});

View File

@ -107,7 +107,7 @@ fn dataValue(self: Query, value: ?[]const u8) *jetzig.data.Data.Value {
if (value) |item_value| {
return self.data.string(item_value);
} else {
return self.data._null();
return jetzig.zmpl.Data._null(self.data.getAllocator());
}
}

View File

@ -232,7 +232,7 @@ fn renderMarkdown(
);
defer self.allocator.free(prefixed_name);
if (zmpl.manifest.find(prefixed_name)) |layout| {
if (zmpl.find(prefixed_name)) |layout| {
rendered.view.data.content = .{ .data = markdown_content };
rendered.content = try layout.render(rendered.view.data);
} else {
@ -249,7 +249,7 @@ fn renderView(
self: *Self,
route: *jetzig.views.Route,
request: *jetzig.http.Request,
template: ?zmpl.manifest.Template,
template: ?zmpl.Template,
) !RenderedView {
// View functions return a `View` to help encourage users to return from a view function with
// `return request.render(.ok)`, but the actual rendered view is stored in
@ -290,7 +290,7 @@ fn renderView(
fn renderTemplateWithLayout(
self: *Self,
request: *jetzig.http.Request,
template: zmpl.manifest.Template,
template: zmpl.Template,
view: jetzig.views.View,
route: *jetzig.views.Route,
) ![]const u8 {
@ -298,10 +298,10 @@ fn renderTemplateWithLayout(
if (request.getLayout(route)) |layout_name| {
// TODO: Allow user to configure layouts directory other than src/app/views/layouts/
const prefixed_name = try std.mem.concat(self.allocator, u8, &[_][]const u8{ "layouts_", layout_name });
const prefixed_name = try std.mem.concat(self.allocator, u8, &[_][]const u8{ "layouts", "/", layout_name });
defer self.allocator.free(prefixed_name);
if (zmpl.manifest.find(prefixed_name)) |layout| {
if (zmpl.find(prefixed_name)) |layout| {
return try template.renderWithLayout(layout, view.data);
} else {
try self.logger.WARN("Unknown layout: {s}", .{layout_name});