From 16204a48327ffa6e06d76f62ee448857538ede03 Mon Sep 17 00:00:00 2001 From: Bob Farrell Date: Fri, 5 Apr 2024 00:24:32 +0100 Subject: [PATCH] Zmpl v2 Update to Zmpl v2, update demo app to be compatible with v2 syntax. Add deprecation warning for v1 (v1 is default for now - will force v2 soon). --- build.zig | 59 ++++++++++++++------- build.zig.zon | 4 +- demo/build.zig | 2 +- demo/src/app/views/iguanas/index.zmpl | 7 +-- demo/src/app/views/init.zig | 7 ++- demo/src/app/views/init/_content.zmpl | 4 +- demo/src/app/views/init/index.zmpl | 12 +++-- demo/src/app/views/layouts/application.zmpl | 2 +- demo/src/app/views/nested/route/markdown.md | 7 +++ demo/src/app/views/quotes/get.zmpl | 4 +- demo/src/app/views/quotes/post.zmpl | 2 +- demo/src/app/views/root/_quotes.zmpl | 3 +- demo/src/app/views/root/index.zmpl | 7 ++- demo/zmpl_options.zig | 1 + src/GenerateRoutes.zig | 4 +- src/jetzig/http/Query.zig | 2 +- src/jetzig/http/Server.zig | 10 ++-- 17 files changed, 87 insertions(+), 50 deletions(-) create mode 100644 demo/src/app/views/nested/route/markdown.md create mode 100644 demo/zmpl_options.zig diff --git a/build.zig b/build.zig index f86691f..4f91f1f 100644 --- a/build.zig +++ b/build.zig @@ -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); } diff --git a/build.zig.zon b/build.zig.zon index dbb8401..7bb3441 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -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", diff --git a/demo/build.zig b/demo/build.zig index 8f2e0ae..c6eaaec 100644 --- a/demo/build.zig +++ b/demo/build.zig @@ -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); diff --git a/demo/src/app/views/iguanas/index.zmpl b/demo/src/app/views/iguanas/index.zmpl index 7651164..c07004f 100644 --- a/demo/src/app/views/iguanas/index.zmpl +++ b/demo/src/app/views/iguanas/index.zmpl @@ -1,6 +1,7 @@
- var it = zmpl.value.?.array.iterator(); - while (it.next()) |iguana| { -
{(iguana.string.value)}
+@zig { + for (zmpl.items(.array)) |iguana| { +
{{iguana}}
} +}
diff --git a/demo/src/app/views/init.zig b/demo/src/app/views/init.zig index 0fa146c..d567fc5 100644 --- a/demo/src/app/views/init.zig +++ b/demo/src/app/views/init.zig @@ -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. diff --git a/demo/src/app/views/init/_content.zmpl b/demo/src/app/views/init/_content.zmpl index 6d3a665..03da91f 100644 --- a/demo/src/app/views/init/_content.zmpl +++ b/demo/src/app/views/init/_content.zmpl @@ -1,5 +1,5 @@ -// Renders the `message` response data value. -

{.message}

+@args message: *ZmplValue +

{{message}}

diff --git a/demo/src/app/views/init/index.zmpl b/demo/src/app/views/init/index.zmpl index c7c3cb9..100d770 100644 --- a/demo/src/app/views/init/index.zmpl +++ b/demo/src/app/views/init/index.zmpl @@ -9,12 +9,14 @@
- // If present, renders the `message_param` response data value, add `?message=hello` to the - // URL to see the output: -

{.message_param}

+ +

{{.message_param}}

- // Renders `src/app/views/init/_content.zmpl` with the same template data available: -
{^init/content}
+ +
+ @partial init/content(message: .welcome_message) +
diff --git a/demo/src/app/views/layouts/application.zmpl b/demo/src/app/views/layouts/application.zmpl index 71deebd..c776ae2 100644 --- a/demo/src/app/views/layouts/application.zmpl +++ b/demo/src/app/views/layouts/application.zmpl @@ -9,7 +9,7 @@ -
{zmpl.content}
+
{{zmpl.content}}
diff --git a/demo/src/app/views/nested/route/markdown.md b/demo/src/app/views/nested/route/markdown.md new file mode 100644 index 0000000..f871ab3 --- /dev/null +++ b/demo/src/app/views/nested/route/markdown.md @@ -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/). diff --git a/demo/src/app/views/quotes/get.zmpl b/demo/src/app/views/quotes/get.zmpl index dca9318..65beae6 100644 --- a/demo/src/app/views/quotes/get.zmpl +++ b/demo/src/app/views/quotes/get.zmpl @@ -1,2 +1,2 @@ -
"{.quote}"
-
--{.author}
+
"{{.quote}}"
+
--{{.author}}
diff --git a/demo/src/app/views/quotes/post.zmpl b/demo/src/app/views/quotes/post.zmpl index 0c69b5d..fc7b3d6 100644 --- a/demo/src/app/views/quotes/post.zmpl +++ b/demo/src/app/views/quotes/post.zmpl @@ -1 +1 @@ -
{.param}
+
{{.param}}
diff --git a/demo/src/app/views/root/_quotes.zmpl b/demo/src/app/views/root/_quotes.zmpl index ef479e8..d72ed79 100644 --- a/demo/src/app/views/root/_quotes.zmpl +++ b/demo/src/app/views/root/_quotes.zmpl @@ -1,5 +1,6 @@ +@args message: *ZmplValue
-

{.message}

+

{{message}}

diff --git a/demo/src/app/views/root/index.zmpl b/demo/src/app/views/root/index.zmpl index ca64125..1fb1e42 100644 --- a/demo/src/app/views/root/index.zmpl +++ b/demo/src/app/views/root/index.zmpl @@ -1,8 +1,11 @@
+
- // Renders `src/app/views/root/_quotes.zmpl`: -
{^root/quotes}
+ +
+ @partial root/quotes(message: .message) +
diff --git a/demo/zmpl_options.zig b/demo/zmpl_options.zig new file mode 100644 index 0000000..b6fc4c6 --- /dev/null +++ b/demo/zmpl_options.zig @@ -0,0 +1 @@ +hello \ No newline at end of file diff --git a/src/GenerateRoutes.zig b/src/GenerateRoutes.zig index 04c3bd9..c638798 100644 --- a/src/GenerateRoutes.zig +++ b/src/GenerateRoutes.zig @@ -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), }); diff --git a/src/jetzig/http/Query.zig b/src/jetzig/http/Query.zig index 14c76d1..c8f073b 100644 --- a/src/jetzig/http/Query.zig +++ b/src/jetzig/http/Query.zig @@ -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()); } } diff --git a/src/jetzig/http/Server.zig b/src/jetzig/http/Server.zig index 1a12a2c..3e4c058 100644 --- a/src/jetzig/http/Server.zig +++ b/src/jetzig/http/Server.zig @@ -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});