From 0c6396eec126d097d5174cd95e2b6e8f473d7ac7 Mon Sep 17 00:00:00 2001 From: Ben Jordan Date: Sun, 14 Dec 2025 23:39:31 -0500 Subject: [PATCH 01/12] uncommented build fuction --- build.zig | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/build.zig b/build.zig index 75eec1f..10662e3 100755 --- a/build.zig +++ b/build.zig @@ -293,22 +293,22 @@ pub fn templatesPaths(allocator: Allocator, paths: []const TemplatesPath) ![]con return buf.toOwnedSlice(allocator); } -// pub fn addTemplateConstants(b: *Build, comptime constants: type) ![]const u8 { -// const fields = switch (@typeInfo(constants)) { -// .@"struct" => |info| info.fields, -// else => @panic("Expected struct, found: " ++ @typeName(constants)), -// }; -// var array: [fields.len][]const u8 = undefined; -// -// inline for (fields, 0..) |field, index| { -// array[index] = std.fmt.comptimePrint( -// "{s}#{s}", -// .{ field.name, @typeName(field.type) }, -// ); -// } -// -// return std.mem.join(b.allocator, "|", &array); -// } +pub fn addTemplateConstants(b: *Build, comptime constants: type) ![]const u8 { + const fields = switch (@typeInfo(constants)) { + .@"struct" => |info| info.fields, + else => @panic("Expected struct, found: " ++ @typeName(constants)), + }; + var array: [fields.len][]const u8 = undefined; + + inline for (fields, 0..) |field, index| { + array[index] = std.fmt.comptimePrint( + "{s}#{s}", + .{ field.name, @typeName(field.type) }, + ); + } + + return std.mem.join(b.allocator, "|", &array); +} fn findTemplates(b: *Build, templates_paths: []const []const u8) ![][]const u8 { var templates: ArrayList([]const u8) = .empty; From 114b6d5ef2e4983084df1a47b0e512f70a88e34b Mon Sep 17 00:00:00 2001 From: Ben Jordan Date: Sat, 20 Dec 2025 15:31:25 -0500 Subject: [PATCH 02/12] fixed benchmark --- src/main.zig | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main.zig b/src/main.zig index ef2022b..8ce6fd6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,6 +1,8 @@ const std = @import("std"); const ArenaAllocator = std.heap.ArenaAllocator; const GeneralPurposeAllocator = std.heap.GeneralPurposeAllocator; +const Writer = std.Io.Writer; +const Allocator = std.mem.Allocator; const zmpl = @import("zmpl"); @@ -10,20 +12,23 @@ pub fn main() !void { var arena: ArenaAllocator = .init(allocator); var data = zmpl.Data.init(arena.allocator()); + // https://github.com/json-iterator/test-data/blob/master/large-file.json const stat = try std.fs.cwd().statFile("large-file.json"); const json = try std.fs.cwd().readFileAlloc(allocator, "large-file.json", stat.size); // Time to beat: Duration: 1.28s - try benchmark(zmpl.Data.fromJson, .{ &data, json }); + try benchmark(allocator, zmpl.Data.fromJson, .{ &data, json }); // Time to beat: Duration: 946.734ms - _ = try benchmark(zmpl.Data.toJson, .{&data}); + _ = try benchmark(allocator, zmpl.Data.toJson, .{&data}); } -fn benchmark(func: anytype, args: anytype) @typeInfo(@TypeOf(func)).@"fn".return_type.? { - const start = std.time.nanoTimestamp(); - const result = try @call(.auto, func, args); - const end = std.time.nanoTimestamp(); - std.debug.print("Duration: {}\n", .{std.fmt.fmtDuration(@intCast(end - start))}); - return result; +fn benchmark(allocator: Allocator, func: anytype, args: anytype) !void { + const start = std.time.microTimestamp(); + _ = try @call(.auto, func, args); + const end = std.time.microTimestamp(); + var buf: Writer.Allocating = .init(allocator); + defer buf.deinit(); + try buf.writer.printDuration((end - start) * 1000, .{}); + std.debug.print("Duration: {s}\n", .{try buf.toOwnedSlice()}); } From 63330ddef0ea617492cd77583a8c1f31f663522a Mon Sep 17 00:00:00 2001 From: Ben Jordan Date: Mon, 22 Dec 2025 17:11:46 -0500 Subject: [PATCH 03/12] disabling workflows --- .github/workflows/ci.yml | 78 ++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0af9951..b775779 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,39 +1,39 @@ -name: CI - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - schedule: - - cron: "0 0 * * *" - workflow_dispatch: - -jobs: - test: - strategy: - matrix: - platform: [ubuntu-latest, windows-latest, macos-latest] - runs-on: ${{ matrix.platform }} - - steps: - - uses: actions/checkout@v4 - - - name: Setup Zig - uses: mlugg/setup-zig@v2 - with: - version: master - - - name: Print Zig version - run: zig version - - - name: Build - run: zig build --verbose - - - name: Run Tests - run: | - zig build test --summary all - - - name: Formatting check - if: matrix.platform != 'windows-latest' - run: zig fmt --check . +# name: CI +# +# on: +# push: +# branches: [ main ] +# pull_request: +# branches: [ main ] +# schedule: +# - cron: "0 0 * * *" +# workflow_dispatch: +# +# jobs: +# test: +# strategy: +# matrix: +# platform: [ubuntu-latest, windows-latest, macos-latest] +# runs-on: ${{ matrix.platform }} +# +# steps: +# - uses: actions/checkout@v4 +# +# - name: Setup Zig +# uses: mlugg/setup-zig@v2 +# with: +# version: master +# +# - name: Print Zig version +# run: zig version +# +# - name: Build +# run: zig build --verbose +# +# - name: Run Tests +# run: | +# zig build test --summary all +# +# - name: Formatting check +# if: matrix.platform != 'windows-latest' +# run: zig fmt --check . From 3d7d356e661292a74d8402bb6b83cda2a6c54593 Mon Sep 17 00:00:00 2001 From: Ben Jordan Date: Fri, 26 Dec 2025 23:35:52 -0500 Subject: [PATCH 04/12] added missing allocator --- src/zmpl/Data.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zmpl/Data.zig b/src/zmpl/Data.zig index 32883fc..8e980ab 100644 --- a/src/zmpl/Data.zig +++ b/src/zmpl/Data.zig @@ -1915,7 +1915,7 @@ pub const Array = struct { } pub fn deinit(self: *Array) void { - self.array.clearAndFree(); + self.array.clearAndFree(self.allocator); } // Compares equality of all items in an array. Order must be identical. From 0e20af280c26ac7f75574701c930a6c7caef01aa Mon Sep 17 00:00:00 2001 From: Ben Jordan Date: Fri, 26 Dec 2025 23:43:05 -0500 Subject: [PATCH 05/12] updated jetcommon --- build.zig.zon | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 11ece3c..ee97f83 100755 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,8 +5,8 @@ .minimum_zig_version = "0.15.1", .dependencies = .{ .jetcommon = .{ - .url = "https://github.com/jetzig-framework/jetcommon/archive/ed326c74b98fae893acd89d2e84c0d894a200df3.tar.gz", - .hash = "jetcommon-0.1.0-jPY_DbNIAAA0utNExN3zMeqXA4fhZXQ6LDDP1eg8vodj", + .url = "https://github.com/jetzig-framework/jetcommon/archive/6916e0e955da5016c64eab83c3cd80461db964bf.tar.gz", + .hash = "jetcommon-0.1.0-jPY_DWpFAACYWOJ1k9T-KcwU38iLqYIlqGuZh0aDSzIc", }, .zmd = .{ .url = "https://github.com/jetzig-framework/zmd/archive/87b1c46b517f47b3384eba7ce98d197c463a9d5e.tar.gz", @@ -24,4 +24,3 @@ "README.md", }, } - From 94a832f46c62abaff17d4f9cede0c3ebd6cb0625 Mon Sep 17 00:00:00 2001 From: Ben Jordan Date: Sat, 27 Dec 2025 00:35:55 -0500 Subject: [PATCH 06/12] fixed bug in deinit --- src/zmpl/Data.zig | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/zmpl/Data.zig b/src/zmpl/Data.zig index 8e980ab..2e1a97f 100644 --- a/src/zmpl/Data.zig +++ b/src/zmpl/Data.zig @@ -1640,8 +1640,9 @@ pub const Object = struct { pub fn deinit(self: *Object) void { var it = self.hashmap.iterator(); while (it.next()) |entry| { - self.allocator.destroy(entry.key_ptr); - self.allocator.destroy(entry.value_ptr); + entry.value_ptr.*.deinit(); + self.allocator.destroy(entry.value_ptr.*); + self.allocator.free(entry.key_ptr.*); } self.hashmap.clearAndFree(); } @@ -1897,11 +1898,13 @@ pub const Object = struct { /// Return `true` if value was removed and `false` otherwise. pub fn remove(self: *Object, key: []const u8) bool { if (self.hashmap.getEntry(key)) |entry| { - self.allocator.destroy(entry.value_ptr); - self.allocator.destroy(entry.key_ptr); - } else return false; - - return self.hashmap.swapRemove(key); + entry.value_ptr.*.deinit(); + self.allocator.destroy(entry.value_ptr.*); + self.allocator.free(entry.key_ptr.*); + _ = self.hashmap.swapRemove(key); + return true; + } + return false; } }; From 92fe1d7682361c292382a3c0035febeb854ce72f Mon Sep 17 00:00:00 2001 From: Ben Jordan Date: Sat, 27 Dec 2025 13:33:59 -0500 Subject: [PATCH 07/12] now solved? --- src/tests.zig | 215 +++++++------------------------- src/zmpl/Data.zig | 307 +++++++++++++++++++++++----------------------- 2 files changed, 200 insertions(+), 322 deletions(-) diff --git a/src/tests.zig b/src/tests.zig index ab235c8..951894a 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -1,5 +1,4 @@ const std = @import("std"); -const zmpl = @import("zmpl"); const allocator = std.testing.allocator; const ArenaAllocator = std.heap.ArenaAllocator; const ArrayList = std.ArrayList; @@ -7,15 +6,15 @@ const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const expectEqualStrings = std.testing.expectEqualStrings; +const zmpl = @import("zmpl"); +const Data = zmpl.Data; + const jetcommon = @import("jetcommon"); const Context = struct { foo: []const u8 = "default" }; test "readme example" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var body = try data.object(); @@ -55,10 +54,7 @@ test "readme example" { } test "object passing to partial" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.root(.object); @@ -81,10 +77,7 @@ test "object passing to partial" { } test "complex example" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var body = try data.object(); @@ -155,10 +148,7 @@ test "complex example" { } test "direct rendering of slots (render [][]const u8 as line-separated string)" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); const template = zmpl.find("slots") orelse return expect(false); @@ -175,10 +165,7 @@ test "direct rendering of slots (render [][]const u8 as line-separated string)" } test "javascript" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); const template = zmpl.find("javascript") orelse return expect(false); @@ -196,10 +183,7 @@ test "javascript" { } test "partials without blocks" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); const template = zmpl.find("partials_without_blocks") orelse return expect(false); @@ -212,10 +196,7 @@ test "partials without blocks" { } test "custom delimiters" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); const template = zmpl.find("custom_delimiters") orelse return expect(false); @@ -234,10 +215,7 @@ test "custom delimiters" { } test ".md.zmpl extension" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); const template = zmpl.find("markdown_extension") orelse return expect(false); @@ -249,10 +227,7 @@ test ".md.zmpl extension" { } test "default partial arguments" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); const template = zmpl.find("default_partial_arguments") orelse return expect(false); @@ -264,10 +239,7 @@ test "default partial arguments" { } test "escaping (HTML and backslash escaping" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); const template = zmpl.find("escaping") orelse return expect(false); @@ -281,10 +253,7 @@ test "escaping (HTML and backslash escaping" { } test "references combined with markdown" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var object = try data.object(); @@ -303,10 +272,7 @@ test "references combined with markdown" { } test "partial arg type coercion" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var object = try data.object(); @@ -325,10 +291,7 @@ test "partial arg type coercion" { } test "inheritance" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); const template = zmpl.find("inheritance_child") orelse return expect(false); @@ -354,10 +317,7 @@ test "inheritance" { } test "root init" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.root(.object); @@ -397,10 +357,7 @@ test "root init" { } test "reference stripping" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.root(.object); @@ -416,10 +373,7 @@ test "reference stripping" { } test "inferred type in put/append" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); const TestEnum = enum { field_a, field_b }; @@ -464,10 +418,7 @@ test "inferred type in put/append" { } test "getT(.array, ...) and getT(.object, ...)" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.root(.object); @@ -489,10 +440,7 @@ test "getT(.array, ...) and getT(.object, ...)" { } test "object.remove(...)" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var obj = try data.object(); @@ -506,10 +454,7 @@ test "object.remove(...)" { } test "getStruct from object" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.root(.object); @@ -551,10 +496,7 @@ test "getStruct from object" { } test "Array.items()" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var array = try data.array(); @@ -567,10 +509,7 @@ test "Array.items()" { } test "Object.items()" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var object = try data.object(); @@ -588,10 +527,7 @@ test "Object.items()" { } test "toJson()" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var object = try data.object(); @@ -607,10 +543,7 @@ test "toJson()" { } test "put slice" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.root(.object); @@ -633,10 +566,7 @@ test "put slice" { } test "iteration" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.root(.object); @@ -683,10 +613,7 @@ test "iteration" { } test "datetime format" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.root(.object); @@ -708,10 +635,7 @@ test "datetime format" { } test "datetime" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.root(.object); @@ -722,10 +646,7 @@ test "datetime" { } test "for with partial" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.root(.object); @@ -747,10 +668,7 @@ test "for with partial" { } test "error union" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.root(.object); @@ -760,10 +678,7 @@ test "error union" { } test "xss sanitization/raw formatter" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.root(.object); @@ -779,10 +694,7 @@ test "xss sanitization/raw formatter" { } test "if/else" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.root(.object); @@ -829,10 +741,7 @@ test "if/else" { } test "for with zmpl value" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.root(.object); @@ -857,10 +766,7 @@ test "for with zmpl value" { } test "comments" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); const template = zmpl.find("comments") orelse return expect(false); @@ -874,10 +780,7 @@ test "comments" { } test "for with if" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.object(); @@ -918,10 +821,7 @@ test "for with if" { } test "mix mardown and zig" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.object(); @@ -949,10 +849,7 @@ test "nullable if" { // Test with null value - should be falsey { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var clip = try data.object(); @@ -968,10 +865,7 @@ test "nullable if" { // Test with non-null, non-empty string - should be truthy { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var clip = try data.object(); @@ -988,10 +882,7 @@ test "nullable if" { // Test with empty string - should be falsey like null { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var clip = try data.object(); @@ -1007,10 +898,7 @@ test "nullable if" { } test "if statement with indented HTML - if branch" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.root(.object); @@ -1038,10 +926,7 @@ test "if statement with indented HTML - if branch" { } test "if statement with indented HTML - else branch" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.root(.object); @@ -1062,10 +947,7 @@ test "if statement with indented HTML - else branch" { } test "blocks" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); const template = zmpl.find("blocks") orelse return expect(false); @@ -1084,10 +966,7 @@ test "blocks" { } test "append struct with []const []const u8 field" { - var arena: ArenaAllocator = .init(allocator); - defer arena.deinit(); - - var data: zmpl.Data = .init(arena.allocator()); + var data: Data = .init(allocator); defer data.deinit(); var root = try data.root(.object); @@ -1107,9 +986,8 @@ test "append struct with []const []const u8 field" { const expected_baz: []const []const u8 = &.{ "baz", "qux" }; - for (baz_items, 0..) |item, index| { + for (baz_items, 0..) |item, index| try expectEqualStrings(expected_baz[index], item.string.value); - } const qux = foo.get("qux").?; const qux_items = qux.items(.array); @@ -1118,7 +996,6 @@ test "append struct with []const []const u8 field" { const expected_qux: []const usize = &.{ 1, 2, 3 }; - for (qux_items, 0..) |item, index| { + for (qux_items, 0..) |item, index| try expectEqual(expected_qux[index], item.integer.value); - } } diff --git a/src/zmpl/Data.zig b/src/zmpl/Data.zig index 2e1a97f..ffb3cb6 100644 --- a/src/zmpl/Data.zig +++ b/src/zmpl/Data.zig @@ -2,6 +2,7 @@ const Data = @This(); const std = @import("std"); +const testing = std.testing; const ArrayList = std.ArrayList; const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; @@ -75,7 +76,9 @@ const StackFallbackAllocator = std.heap.StackFallbackAllocator(buffer_size); /// `data.value` is a `Data.Value` generic which can be used with `switch` to walk through the /// data tree (for Zmpl templates, use the `{.nested_object.key}` syntax to do this /// automatically. +arena: *ArenaAllocator, allocator: Allocator, +parent_allocator: Allocator, output_buf: *Writer.Allocating, output_writer: *Writer, value: ?*Value = null, @@ -87,24 +90,38 @@ slots: ?[]const String = null, fmt: zmpl.Format, /// Creates a new `Data` instance which can then be used to store any tree of `Value`. -pub fn init(arena: Allocator) Data { - const output_buf = arena.create(Writer.Allocating) catch unreachable; - output_buf.* = .init(arena); - - return .{ - .allocator = arena, - .output_buf = output_buf, - .output_writer = &output_buf.writer, - .template_decls = .init(arena), - .fmt = .{ .writer = &output_buf.writer }, - }; +pub fn init(gpa: Allocator) Data { + const arena = gpa.create(ArenaAllocator) catch unreachable; + arena.* = .init(gpa); + + const arena_allocator = arena.allocator(); + + var output_buf = arena_allocator.create(Writer.Allocating) catch unreachable; + output_buf.* = .init(arena_allocator); + _ = &output_buf; + + var data: Data = undefined; + data.parent_allocator = gpa; + data.arena = arena; + data.allocator = arena_allocator; + data.output_buf = output_buf; + data.output_writer = &data.output_buf.writer; + data.value = null; + data.partial = false; + data.content = .{ .data = "" }; + data.partial_data = null; + data.template_decls = .init(data.allocator); + data.slots = null; + data.fmt = .{ .writer = &data.output_buf.writer }; + return data; } /// Frees all resources used by this `Data` instance. pub fn deinit(self: *Data) void { self.output_buf.clearRetainingCapacity(); self.output_buf.deinit(); - self.allocator.destroy(self.output_buf); + self.arena.deinit(); + self.parent_allocator.destroy(self.arena); } /// Chomps output buffer. @@ -1641,8 +1658,6 @@ pub const Object = struct { var it = self.hashmap.iterator(); while (it.next()) |entry| { entry.value_ptr.*.deinit(); - self.allocator.destroy(entry.value_ptr.*); - self.allocator.free(entry.key_ptr.*); } self.hashmap.clearAndFree(); } @@ -1899,8 +1914,6 @@ pub const Object = struct { pub fn remove(self: *Object, key: []const u8) bool { if (self.hashmap.getEntry(key)) |entry| { entry.value_ptr.*.deinit(); - self.allocator.destroy(entry.value_ptr.*); - self.allocator.free(entry.key_ptr.*); _ = self.hashmap.swapRemove(key); return true; } @@ -2296,56 +2309,56 @@ test "Value.compare integer" { const a = Value{ .integer = .{ .allocator = undefined, .value = 1 } }; const b = Value{ .integer = .{ .allocator = undefined, .value = 2 } }; const c = Value{ .integer = .{ .allocator = undefined, .value = 2 } }; - try std.testing.expect(!try a.compare(.equal, b)); - try std.testing.expect(try b.compare(.equal, c)); - try std.testing.expect(try a.compare(.less_than, b)); - try std.testing.expect(try a.compare(.less_or_equal, b)); - try std.testing.expect(try b.compare(.less_or_equal, c)); - try std.testing.expect(try c.compare(.greater_than, a)); - try std.testing.expect(try c.compare(.greater_or_equal, a)); - try std.testing.expect(try c.compare(.greater_or_equal, b)); + try testing.expect(!try a.compare(.equal, b)); + try testing.expect(try b.compare(.equal, c)); + try testing.expect(try a.compare(.less_than, b)); + try testing.expect(try a.compare(.less_or_equal, b)); + try testing.expect(try b.compare(.less_or_equal, c)); + try testing.expect(try c.compare(.greater_than, a)); + try testing.expect(try c.compare(.greater_or_equal, a)); + try testing.expect(try c.compare(.greater_or_equal, b)); } test "Value.compare float" { const a = Value{ .float = .{ .allocator = undefined, .value = 1.0 } }; const b = Value{ .float = .{ .allocator = undefined, .value = 1.2 } }; const c = Value{ .float = .{ .allocator = undefined, .value = 1.2 } }; - try std.testing.expect(!try a.compare(.equal, b)); - try std.testing.expect(try b.compare(.equal, c)); - try std.testing.expect(try a.compare(.less_than, b)); - try std.testing.expect(try a.compare(.less_or_equal, b)); - try std.testing.expect(try b.compare(.less_or_equal, c)); - try std.testing.expect(try c.compare(.greater_than, a)); - try std.testing.expect(try c.compare(.greater_or_equal, a)); - try std.testing.expect(try c.compare(.greater_or_equal, b)); + try testing.expect(!try a.compare(.equal, b)); + try testing.expect(try b.compare(.equal, c)); + try testing.expect(try a.compare(.less_than, b)); + try testing.expect(try a.compare(.less_or_equal, b)); + try testing.expect(try b.compare(.less_or_equal, c)); + try testing.expect(try c.compare(.greater_than, a)); + try testing.expect(try c.compare(.greater_or_equal, a)); + try testing.expect(try c.compare(.greater_or_equal, b)); } test "Value.compare boolean" { const a = Value{ .boolean = .{ .allocator = undefined, .value = false } }; const b = Value{ .boolean = .{ .allocator = undefined, .value = true } }; const c = Value{ .boolean = .{ .allocator = undefined, .value = true } }; - try std.testing.expect(!try a.compare(.equal, b)); - try std.testing.expect(try b.compare(.equal, c)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.less_than, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.less_or_equal, b)); - try std.testing.expectError(error.ZmplCompareError, b.compare(.less_or_equal, c)); - try std.testing.expectError(error.ZmplCompareError, c.compare(.greater_than, a)); - try std.testing.expectError(error.ZmplCompareError, c.compare(.greater_or_equal, a)); - try std.testing.expectError(error.ZmplCompareError, c.compare(.greater_or_equal, b)); + try testing.expect(!try a.compare(.equal, b)); + try testing.expect(try b.compare(.equal, c)); + try testing.expectError(error.ZmplCompareError, a.compare(.less_than, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.less_or_equal, b)); + try testing.expectError(error.ZmplCompareError, b.compare(.less_or_equal, c)); + try testing.expectError(error.ZmplCompareError, c.compare(.greater_than, a)); + try testing.expectError(error.ZmplCompareError, c.compare(.greater_or_equal, a)); + try testing.expectError(error.ZmplCompareError, c.compare(.greater_or_equal, b)); } test "Value.compare string" { const a = Value{ .string = .{ .allocator = undefined, .value = "foo" } }; const b = Value{ .string = .{ .allocator = undefined, .value = "bar" } }; const c = Value{ .string = .{ .allocator = undefined, .value = "bar" } }; - try std.testing.expect(!try a.compare(.equal, b)); - try std.testing.expect(try b.compare(.equal, c)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.less_than, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.less_or_equal, b)); - try std.testing.expectError(error.ZmplCompareError, b.compare(.less_or_equal, c)); - try std.testing.expectError(error.ZmplCompareError, c.compare(.greater_than, a)); - try std.testing.expectError(error.ZmplCompareError, c.compare(.greater_or_equal, a)); - try std.testing.expectError(error.ZmplCompareError, c.compare(.greater_or_equal, b)); + try testing.expect(!try a.compare(.equal, b)); + try testing.expect(try b.compare(.equal, c)); + try testing.expectError(error.ZmplCompareError, a.compare(.less_than, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.less_or_equal, b)); + try testing.expectError(error.ZmplCompareError, b.compare(.less_or_equal, c)); + try testing.expectError(error.ZmplCompareError, c.compare(.greater_than, a)); + try testing.expectError(error.ZmplCompareError, c.compare(.greater_or_equal, a)); + try testing.expectError(error.ZmplCompareError, c.compare(.greater_or_equal, b)); } test "Value.compare datetime" { @@ -2367,73 +2380,73 @@ test "Value.compare datetime" { .value = try jetcommon.types.DateTime.fromUnix(1731834128, .seconds), }, }; - try std.testing.expect(!try a.compare(.equal, b)); - try std.testing.expect(try b.compare(.equal, c)); - try std.testing.expect(try a.compare(.less_than, b)); - try std.testing.expect(try a.compare(.less_or_equal, b)); - try std.testing.expect(try b.compare(.less_or_equal, c)); - try std.testing.expect(try c.compare(.greater_than, a)); - try std.testing.expect(try c.compare(.greater_or_equal, a)); - try std.testing.expect(try c.compare(.greater_or_equal, b)); + try testing.expect(!try a.compare(.equal, b)); + try testing.expect(try b.compare(.equal, c)); + try testing.expect(try a.compare(.less_than, b)); + try testing.expect(try a.compare(.less_or_equal, b)); + try testing.expect(try b.compare(.less_or_equal, c)); + try testing.expect(try c.compare(.greater_than, a)); + try testing.expect(try c.compare(.greater_or_equal, a)); + try testing.expect(try c.compare(.greater_or_equal, b)); } test "Value.compare object" { const a = Value{ .object = undefined }; const b = Value{ .object = undefined }; - try std.testing.expectError(error.ZmplCompareError, a.compare(.equal, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.less_than, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.less_or_equal, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.less_or_equal, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.greater_than, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.greater_or_equal, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.greater_or_equal, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.equal, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.less_than, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.less_or_equal, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.less_or_equal, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.greater_than, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.greater_or_equal, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.greater_or_equal, b)); } test "Value.compare array" { const a = Value{ .array = undefined }; const b = Value{ .array = undefined }; - try std.testing.expectError(error.ZmplCompareError, a.compare(.equal, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.less_than, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.less_or_equal, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.less_or_equal, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.greater_than, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.greater_or_equal, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.greater_or_equal, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.equal, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.less_than, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.less_or_equal, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.less_or_equal, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.greater_than, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.greater_or_equal, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.greater_or_equal, b)); } test "Value.compare different types" { const a = Value{ .integer = undefined }; const b = Value{ .float = undefined }; - try std.testing.expectError(error.ZmplCompareError, a.compare(.equal, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.less_than, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.less_or_equal, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.less_or_equal, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.greater_than, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.greater_or_equal, b)); - try std.testing.expectError(error.ZmplCompareError, a.compare(.greater_or_equal, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.equal, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.less_than, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.less_or_equal, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.less_or_equal, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.greater_than, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.greater_or_equal, b)); + try testing.expectError(error.ZmplCompareError, a.compare(.greater_or_equal, b)); } test "Value.compareT string" { const a = Value{ .string = .{ .allocator = undefined, .value = "foo" } }; - try std.testing.expect(try a.compareT(.equal, []const u8, "foo")); - try std.testing.expect(try a.compareT(.equal, [:0]const u8, @as([:0]const u8, "foo"))); - try std.testing.expectError(error.ZmplCompareError, a.compareT(.less_than, []const u8, "foo")); - try std.testing.expectError(error.ZmplCoerceError, a.compareT(.equal, usize, 123)); + try testing.expect(try a.compareT(.equal, []const u8, "foo")); + try testing.expect(try a.compareT(.equal, [:0]const u8, @as([:0]const u8, "foo"))); + try testing.expectError(error.ZmplCompareError, a.compareT(.less_than, []const u8, "foo")); + try testing.expectError(error.ZmplCoerceError, a.compareT(.equal, usize, 123)); } test "Value.compareT integer" { const a = Value{ .integer = .{ .allocator = undefined, .value = 1 } }; - try std.testing.expect(try a.compareT(.equal, usize, 1)); - try std.testing.expect(try a.compareT(.equal, u16, 1)); - try std.testing.expect(try a.compareT(.equal, u8, 1)); - try std.testing.expect(!try a.compareT(.equal, u8, 2)); - try std.testing.expect(try a.compareT(.less_than, usize, 2)); - try std.testing.expect(try a.compareT(.less_or_equal, usize, 2)); - try std.testing.expect(try a.compareT(.less_or_equal, usize, 1)); - try std.testing.expect(try a.compareT(.greater_than, usize, 0)); - try std.testing.expect(try a.compareT(.greater_or_equal, usize, 0)); - try std.testing.expect(try a.compareT(.greater_or_equal, usize, 1)); - try std.testing.expectError( + try testing.expect(try a.compareT(.equal, usize, 1)); + try testing.expect(try a.compareT(.equal, u16, 1)); + try testing.expect(try a.compareT(.equal, u8, 1)); + try testing.expect(!try a.compareT(.equal, u8, 2)); + try testing.expect(try a.compareT(.less_than, usize, 2)); + try testing.expect(try a.compareT(.less_or_equal, usize, 2)); + try testing.expect(try a.compareT(.less_or_equal, usize, 1)); + try testing.expect(try a.compareT(.greater_than, usize, 0)); + try testing.expect(try a.compareT(.greater_or_equal, usize, 0)); + try testing.expect(try a.compareT(.greater_or_equal, usize, 1)); + try testing.expectError( error.ZmplCompareError, a.compareT(.equal, []const u8, "1"), ); @@ -2441,17 +2454,17 @@ test "Value.compareT integer" { test "Value.compareT float" { const a = Value{ .float = .{ .allocator = undefined, .value = 1.0 } }; - try std.testing.expect(try a.compareT(.equal, f128, 1.0)); - try std.testing.expect(try a.compareT(.equal, f64, 1.0)); - try std.testing.expect(try a.compareT(.equal, f32, 1.0)); - try std.testing.expect(!try a.compareT(.equal, f64, 1.1)); - try std.testing.expect(try a.compareT(.less_than, f64, 1.1)); - try std.testing.expect(try a.compareT(.less_or_equal, f64, 1.1)); - try std.testing.expect(try a.compareT(.less_or_equal, f64, 1.0)); - try std.testing.expect(try a.compareT(.greater_than, f64, 0.9)); - try std.testing.expect(try a.compareT(.greater_or_equal, f64, 0.9)); - try std.testing.expect(try a.compareT(.greater_or_equal, f64, 1.0)); - try std.testing.expectError( + try testing.expect(try a.compareT(.equal, f128, 1.0)); + try testing.expect(try a.compareT(.equal, f64, 1.0)); + try testing.expect(try a.compareT(.equal, f32, 1.0)); + try testing.expect(!try a.compareT(.equal, f64, 1.1)); + try testing.expect(try a.compareT(.less_than, f64, 1.1)); + try testing.expect(try a.compareT(.less_or_equal, f64, 1.1)); + try testing.expect(try a.compareT(.less_or_equal, f64, 1.0)); + try testing.expect(try a.compareT(.greater_than, f64, 0.9)); + try testing.expect(try a.compareT(.greater_or_equal, f64, 0.9)); + try testing.expect(try a.compareT(.greater_or_equal, f64, 1.0)); + try testing.expectError( error.ZmplCompareError, a.compareT(.equal, []const u8, "1.0"), ); @@ -2464,18 +2477,18 @@ test "Value.compareT datetime" { .value = try jetcommon.types.DateTime.fromUnix(1731834128, .seconds), }, }; - try std.testing.expect(try a.compareT(.equal, u64, 1731834128 * 1_000_000)); - try std.testing.expect(try a.compareT(.equal, u128, 1731834128 * 1_000_000)); - try std.testing.expect(!try a.compareT(.equal, u64, 1731834127 * 1_000_000)); - try std.testing.expect(!try a.compareT(.equal, i64, 1731834127 * 1_000_000)); - try std.testing.expect(!try a.compareT(.equal, i128, 1731834127 * 1_000_000)); - try std.testing.expect(try a.compareT(.less_than, u64, 1731834129 * 1_000_000)); - try std.testing.expect(try a.compareT(.less_or_equal, u64, 1731834129 * 1_000_000)); - try std.testing.expect(try a.compareT(.less_or_equal, u64, 1731834128 * 1_000_000)); - try std.testing.expect(try a.compareT(.greater_than, u64, 1731834127 * 1_000_000)); - try std.testing.expect(try a.compareT(.greater_or_equal, u64, 1731834127 * 1_000_000)); - try std.testing.expect(try a.compareT(.greater_or_equal, u64, 1731834128 * 1_000_000)); - try std.testing.expectError( + try testing.expect(try a.compareT(.equal, u64, 1731834128 * 1_000_000)); + try testing.expect(try a.compareT(.equal, u128, 1731834128 * 1_000_000)); + try testing.expect(!try a.compareT(.equal, u64, 1731834127 * 1_000_000)); + try testing.expect(!try a.compareT(.equal, i64, 1731834127 * 1_000_000)); + try testing.expect(!try a.compareT(.equal, i128, 1731834127 * 1_000_000)); + try testing.expect(try a.compareT(.less_than, u64, 1731834129 * 1_000_000)); + try testing.expect(try a.compareT(.less_or_equal, u64, 1731834129 * 1_000_000)); + try testing.expect(try a.compareT(.less_or_equal, u64, 1731834128 * 1_000_000)); + try testing.expect(try a.compareT(.greater_than, u64, 1731834127 * 1_000_000)); + try testing.expect(try a.compareT(.greater_or_equal, u64, 1731834127 * 1_000_000)); + try testing.expect(try a.compareT(.greater_or_equal, u64, 1731834128 * 1_000_000)); + try testing.expectError( error.ZmplCompareError, a.compareT(.equal, []const u8, "1731834128"), ); @@ -2483,7 +2496,7 @@ test "Value.compareT datetime" { test "Value.compareT object" { const a = Value{ .object = undefined }; - try std.testing.expectError( + try testing.expectError( error.ZmplCompareError, a.compareT(.equal, []const u8, "foo"), ); @@ -2491,17 +2504,14 @@ test "Value.compareT object" { test "Value.compareT array" { const a = Value{ .array = undefined }; - try std.testing.expectError( + try testing.expectError( error.ZmplCompareError, a.compareT(.equal, []const u8, "foo"), ); } test "append/put array/object" { - var arena: ArenaAllocator = .init(std.testing.allocator); - defer arena.deinit(); - - var data = Data.init(arena.allocator()); + var data: Data = .init(testing.allocator); defer data.deinit(); var array1 = try data.root(.array); @@ -2517,7 +2527,7 @@ test "append/put array/object" { try object2.put("garply", "waldo"); try array4.append("fred"); - try std.testing.expectEqualStrings( + try testing.expectEqualStrings( \\[[["foo"],"bar"],"baz",{"qux":"quux","corge":{"garply":"waldo"},"grault":["fred"]}] \\ , @@ -2530,8 +2540,8 @@ test "coerce enum" { const E = enum { foo, bar }; const e1: E = .foo; const e2: E = .bar; - try std.testing.expect(e1 == try value.coerce(E)); - try std.testing.expect(e2 != try value.coerce(E)); + try testing.expect(e1 == try value.coerce(E)); + try testing.expect(e2 != try value.coerce(E)); } test "coerce boolean" { @@ -2541,19 +2551,16 @@ test "coerce boolean" { const value4 = Value{ .string = .{ .allocator = undefined, .value = "random" } }; const value5 = Value{ .boolean = .{ .allocator = undefined, .value = true } }; const value6 = Value{ .boolean = .{ .allocator = undefined, .value = false } }; - try std.testing.expect(try value1.coerce(bool)); - try std.testing.expect(!try value2.coerce(bool)); - try std.testing.expect(try value3.coerce(bool)); - try std.testing.expect(!try value4.coerce(bool)); - try std.testing.expect(try value5.coerce(bool)); - try std.testing.expect(!try value6.coerce(bool)); + try testing.expect(try value1.coerce(bool)); + try testing.expect(!try value2.coerce(bool)); + try testing.expect(try value3.coerce(bool)); + try testing.expect(!try value4.coerce(bool)); + try testing.expect(try value5.coerce(bool)); + try testing.expect(!try value6.coerce(bool)); } test "array pop" { - var arena: ArenaAllocator = .init(std.testing.allocator); - defer arena.deinit(); - - var data = Data.init(arena.allocator()); + var data: Data = .init(testing.allocator); defer data.deinit(); var array1 = try data.root(.array); @@ -2561,65 +2568,59 @@ test "array pop" { try array1.append(2); try array1.append(3); - try std.testing.expect(array1.count() == 3); + try testing.expect(array1.count() == 3); const vals: [3]u8 = .{ 3, 2, 1 }; for (vals) |val| { const popped = array1.pop().?; - try std.testing.expect(try popped.compareT(.equal, u8, val)); + try testing.expect(try popped.compareT(.equal, u8, val)); } - try std.testing.expect(array1.count() == 0); + try testing.expect(array1.count() == 0); } test "getT(.null, ...)" { - var arena: ArenaAllocator = .init(std.testing.allocator); - defer arena.deinit(); - - var data = Data.init(arena.allocator()); + var data: Data = .init(testing.allocator); defer data.deinit(); var obj = try data.root(.object); try obj.put("foo", null); - try std.testing.expectEqual(obj.getT(.null, "foo"), true); - try std.testing.expectEqual(obj.getT(.null, "bar"), false); + try testing.expectEqual(obj.getT(.null, "foo"), true); + try testing.expectEqual(obj.getT(.null, "bar"), false); } test "parseJsonSlice" { - var arena: ArenaAllocator = .init(std.testing.allocator); - defer arena.deinit(); - - var data = Data.init(arena.allocator()); + var data: Data = .init(testing.allocator); defer data.deinit(); const string_value = try data.parseJsonSlice( \\"foo" ); - try std.testing.expectEqualStrings("foo", string_value.*.string.value); + try testing.expectEqualStrings("foo", string_value.*.string.value); const boolean_value = try data.parseJsonSlice( \\true ); - try std.testing.expectEqual(true, boolean_value.*.boolean.value); + try testing.expectEqual(true, boolean_value.*.boolean.value); const integer_value = try data.parseJsonSlice( \\100 ); - try std.testing.expectEqual(100, integer_value.*.integer.value); + try testing.expectEqual(100, integer_value.*.integer.value); const float_value = try data.parseJsonSlice( \\100.1 ); - try std.testing.expectEqual(100.1, float_value.*.float.value); + try testing.expectEqual(100.1, float_value.*.float.value); const object_value = try data.parseJsonSlice( \\{"foo": "bar"} ); - try std.testing.expectEqualStrings("bar", object_value.get("foo").?.string.value); + try testing.expectEqualStrings("bar", object_value.get("foo").?.string.value); const array_value = try data.parseJsonSlice( \\["foo", "bar"] ); - try std.testing.expectEqualStrings("bar", array_value.items(.array)[1].string.value); + try testing.expectEqualStrings("bar", array_value.items(.array)[1].string.value); } From 6b2c50117d75e59b8efc952bd2ebcc53bc6b2c96 Mon Sep 17 00:00:00 2001 From: Emily Flion Date: Wed, 31 Dec 2025 15:06:16 +0100 Subject: [PATCH 08/12] Leave use_llvm default chosen by the compiler By default the non-llvm backend is used for x86 debug builds by default by the Zig compiler, no need to set a default here. Signed-off-by: Emily Flion --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 10662e3..ade76ef 100755 --- a/build.zig +++ b/build.zig @@ -15,7 +15,7 @@ pub fn build(b: *std.Build) !void { bool, "use_llvm", "Use LLVM", - ) orelse true; + ); const test_filters = b.option( []const []const u8, From 5cbef6584ac9e0992a97c802b1a66b9eebf514cb Mon Sep 17 00:00:00 2001 From: Emily Flion Date: Wed, 31 Dec 2025 15:20:44 +0100 Subject: [PATCH 09/12] Update build.zig with new Io requirements Currently there isn't a simple blocking Io impl in the standard library, so for now we have to use Threaded version (here it's just statically allocated). We could also technically make a simple blocking impl ourselves, but it would too much work for just the build system, and it *should* come to std at some point in the future... Signed-off-by: Emily Flion --- build.zig | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/build.zig b/build.zig index ade76ef..f8bbd4c 100755 --- a/build.zig +++ b/build.zig @@ -11,6 +11,12 @@ pub fn build(b: *std.Build) !void { const optimize = b.standardOptimizeOption(.{}); const options_files = b.addWriteFiles(); + var threaded: std.Io.Threaded = .init_single_threaded; + // This deinit is not necessary since it's stack allocated but it's ok to + // keep, in case we want to change the threaded initialization at some point + defer threaded.deinit(); + const io = threaded.io(); + const use_llvm = b.option( bool, "use_llvm", @@ -58,6 +64,7 @@ pub fn build(b: *std.Build) !void { "zmpl_templates_paths", "Directories to search for .zmpl templates. Format: `prefix=...,path=...", ) orelse try templatesPaths( + io, b.allocator, &.{.{ .prefix = "templates", @@ -181,14 +188,14 @@ pub fn build(b: *std.Build) !void { const manifest_lazy_path = manifest_exe_run.addOutputFileArg("zmpl.manifest.zig"); manifest_exe_run.setCwd(.{ - .cwd_relative = try std.fs.cwd().realpathAlloc(b.allocator, "."), + .cwd_relative = try std.Io.Dir.cwd().realPathFileAlloc(io, ".", b.allocator), }); manifest_exe_run.expectExitCode(0); manifest_exe_run.addArg(try std.mem.join(b.allocator, ";", templates_paths)); lib.step.dependOn(&manifest_exe_run.step); - for (try findTemplates(b, templates_paths)) |path| + for (try findTemplates(b, io, templates_paths)) |path| manifest_exe_run.addFileArg(.{ .cwd_relative = path }); const compile_step = b.step("compile", "Compile Zmpl templates"); @@ -264,7 +271,7 @@ const TemplatesPath = struct { path: []const []const u8, }; -pub fn templatesPaths(allocator: Allocator, paths: []const TemplatesPath) ![]const []const u8 { +pub fn templatesPaths(io: std.Io, allocator: Allocator, paths: []const TemplatesPath) ![]const []const u8 { var buf: ArrayList([]const u8) = .empty; defer buf.deinit(allocator); for (paths) |path| { @@ -274,7 +281,7 @@ pub fn templatesPaths(allocator: Allocator, paths: []const TemplatesPath) ![]con const absolute_path = if (std.fs.path.isAbsolute(joined)) try allocator.dupe(u8, joined) else - std.fs.cwd().realpathAlloc(allocator, joined) catch |err| + std.Io.Dir.cwd().realPathFileAlloc(io, joined, allocator) catch |err| switch (err) { error.FileNotFound => "_", else => return err, @@ -310,7 +317,7 @@ pub fn addTemplateConstants(b: *Build, comptime constants: type) ![]const u8 { return std.mem.join(b.allocator, "|", &array); } -fn findTemplates(b: *Build, templates_paths: []const []const u8) ![][]const u8 { +fn findTemplates(b: *Build, io: std.Io, templates_paths: []const []const u8) ![][]const u8 { var templates: ArrayList([]const u8) = .empty; defer templates.deinit(b.allocator); @@ -326,7 +333,8 @@ fn findTemplates(b: *Build, templates_paths: []const []const u8) ![][]const u8 { for (templates_paths_buf.items) |templates_path| { if (std.mem.eql(u8, templates_path, "_")) continue; - var dir = std.fs.cwd().openDir( + var dir = std.Io.Dir.cwd().openDir( + io, templates_path, .{ .iterate = true }, ) catch |err| { @@ -345,11 +353,11 @@ fn findTemplates(b: *Build, templates_paths: []const []const u8) ![][]const u8 { var walker = try dir.walk(b.allocator); defer walker.deinit(); - while (try walker.next()) |entry| { + while (try walker.next(io)) |entry| { if (entry.kind != .file) continue; const extension = std.fs.path.extension(entry.path); if (!std.mem.eql(u8, extension, ".zmpl")) continue; - try templates.append(b.allocator, try dir.realpathAlloc(b.allocator, entry.path)); + try templates.append(b.allocator, try dir.realPathFileAlloc(io, entry.path, b.allocator)); } } return templates.toOwnedSlice(b.allocator); From b81d7ddeb7bafc4815309f8608057add6d8326b6 Mon Sep 17 00:00:00 2001 From: Emily Flion Date: Thu, 1 Jan 2026 18:26:06 +0100 Subject: [PATCH 10/12] Update jetcommon to new version Currently pointing to a fork while waiting for it to be merged Signed-off-by: Emily Flion --- build.zig.zon | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index ee97f83..003e7a2 100755 --- a/build.zig.zon +++ b/build.zig.zon @@ -4,14 +4,14 @@ .fingerprint = 0xef2931206468149, .minimum_zig_version = "0.15.1", .dependencies = .{ - .jetcommon = .{ - .url = "https://github.com/jetzig-framework/jetcommon/archive/6916e0e955da5016c64eab83c3cd80461db964bf.tar.gz", - .hash = "jetcommon-0.1.0-jPY_DWpFAACYWOJ1k9T-KcwU38iLqYIlqGuZh0aDSzIc", - }, .zmd = .{ .url = "https://github.com/jetzig-framework/zmd/archive/87b1c46b517f47b3384eba7ce98d197c463a9d5e.tar.gz", .hash = "zmd-0.1.0-H8YV7bvdAABAVFR2lherqOup7valUMg9UTEiiMFxlTjZ", }, + .jetcommon = .{ + .url = "https://github.com/emneo-dev/jetcommon/archive/c4da8d68813ab6cd384323475753c07d18627656.tar.gz", + .hash = "jetcommon-0.1.0-jPY_DetFAAA8wBYM9TKN5HJ1TAH5qSioHzmZuRTg4H_Z", + }, }, .paths = .{ From 7e31785857db781c876419b60b71d9732436f293 Mon Sep 17 00:00:00 2001 From: Emily Flion Date: Thu, 1 Jan 2026 18:26:37 +0100 Subject: [PATCH 11/12] Fix typo in debug print Signed-off-by: Emily Flion --- src/manifest/main.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/manifest/main.zig b/src/manifest/main.zig index 9291f28..aec0761 100644 --- a/src/manifest/main.zig +++ b/src/manifest/main.zig @@ -22,7 +22,7 @@ pub fn main() !void { if (std.mem.eql(u8, permitted_field, field.name)) break; } else { std.debug.print( - "[zmpl] Unrecgonized option: `{s}: {s}`\n", + "[zmpl] Unrecognized option: `{s}: {s}`\n", .{ field.name, @typeName(field.type) }, ); std.process.exit(1); From 061eec6bf91ee4ab7ddb5c749312f8b032ec76b8 Mon Sep 17 00:00:00 2001 From: Emily Flion Date: Thu, 1 Jan 2026 19:14:15 +0100 Subject: [PATCH 12/12] Update for new zig Io changes Signed-off-by: Emily Flion --- src/manifest/Manifest.zig | 13 ++-- src/manifest/Node.zig | 34 ++++----- src/manifest/Template.zig | 12 ++-- src/manifest/main.zig | 12 ++-- src/manifest/util.zig | 15 ++-- src/tests.zig | 143 +++++++++++++++++++------------------- src/zmpl.zig | 2 +- src/zmpl/Data.zig | 16 +++-- src/zmpl/Template.zig | 5 +- src/zmpl/debug.zig | 37 ++++++---- 10 files changed, 156 insertions(+), 133 deletions(-) diff --git a/src/manifest/Manifest.zig b/src/manifest/Manifest.zig index 8432c2d..e0a572d 100644 --- a/src/manifest/Manifest.zig +++ b/src/manifest/Manifest.zig @@ -28,6 +28,7 @@ pub fn init( pub fn compile( self: *Manifest, + io: std.Io, allocator: Allocator, writer: *Writer, comptime options: type, @@ -107,6 +108,7 @@ pub fn compile( // var file = try std.fs.openFileAbsolute(inner_path.path, .{}); // } try self.compileTemplates( + io, allocator, &template_defs, outer_path, @@ -190,6 +192,7 @@ pub fn compile( fn compileTemplates( self: *Manifest, + io: std.Io, allocator: Allocator, array: *ArrayList(TemplateDef), templates_path: TemplatePath, @@ -197,6 +200,7 @@ fn compileTemplates( template_map: *StringHashMap(Template.TemplateMap), comptime options: type, ) !void { + var read_buffer: [512]u8 = undefined; for (self.template_paths) |template_path| { if (!template_path.present) continue; if (!std.mem.eql(u8, template_path.prefix, templates_path.prefix)) continue; @@ -204,9 +208,10 @@ fn compileTemplates( const key = try util.templatePathStore(allocator, templates_paths_map.get(template_path.prefix).?, template_path.path); const generated_name = template_map.get(template_path.prefix).?.get(key).?; - var file = try std.fs.openFileAbsolute(template_path.path, .{}); - const size = (try file.stat()).size; - const content = try file.readToEndAlloc(allocator, @intCast(size)); + var file = try std.Io.Dir.openFileAbsolute(io, template_path.path, .{}); + const size = (try file.stat(io)).size; + var file_reader = file.reader(io, &read_buffer); + const content = try file_reader.interface.allocRemaining(allocator, .limited(size + 1)); var template: Template = .init( allocator, generated_name, @@ -217,7 +222,7 @@ fn compileTemplates( content, template_map.*, ); - const output = try template.compile(options); + const output = try template.compile(io, options); const template_def: TemplateDef = .{ .key = key, diff --git a/src/manifest/Node.zig b/src/manifest/Node.zig index c8a72b2..b2dccec 100644 --- a/src/manifest/Node.zig +++ b/src/manifest/Node.zig @@ -90,7 +90,7 @@ pub const Writer = struct { } }; -pub fn compile(self: Node, input: []const u8, writer: Writer, options: type) !void { +pub fn compile(self: Node, io: std.Io, input: []const u8, writer: Writer, options: type) !void { var compile_writer = writer; compile_writer.token = self.token; @@ -109,22 +109,22 @@ pub fn compile(self: Node, input: []const u8, writer: Writer, options: type) !vo for (self.children.items) |child_node| { if (start < child_node.token.start) { const content = input[start .. child_node.token.start - 1]; - try self.render(if (initial) .initial else .secondary, content, options, compile_writer); + try self.render(io, if (initial) .initial else .secondary, content, options, compile_writer); initial = false; } start = child_node.token.end + 1; - try child_node.compile(input, compile_writer, options); + try child_node.compile(io, input, compile_writer, options); } if (self.children.items.len == 0) { const content = input[self.token.startOfContent()..self.token.endOfContent()]; - try self.render(.initial, content, options, compile_writer); + try self.render(io, .initial, content, options, compile_writer); } else { const last_child = self.children.items[self.children.items.len - 1]; if (last_child.token.end + 1 < self.token.endOfContent()) { const content = input[last_child.token.end + 1 .. self.token.endOfContent()]; - try self.render(.secondary, content, options, compile_writer); + try self.render(io, .secondary, content, options, compile_writer); } } try self.renderClose(compile_writer); @@ -143,6 +143,7 @@ fn divFormatter(allocator: Allocator, node: ZmdNode) ![]const u8 { fn render( self: Node, + io: std.Io, context: Context, content: []const u8, options: type, @@ -155,6 +156,7 @@ fn render( const stripped_content = try self.stripComments(content); try self.renderMode( + io, self.token.mode, context, stripped_content, @@ -163,7 +165,7 @@ fn render( ); } -fn renderMode(self: Node, mode: Mode, context: Context, content: []const u8, formatters: Formatters, writer: anytype) !void { +fn renderMode(self: Node, io: std.Io, mode: Mode, context: Context, content: []const u8, formatters: Formatters, writer: anytype) !void { switch (mode) { .zig => try self.renderZig(content, writer), .html => try self.renderHtml(content, .{}, writer), @@ -172,12 +174,12 @@ fn renderMode(self: Node, mode: Mode, context: Context, content: []const u8, for .{}, writer, ), - .partial => try self.renderPartial(content, writer), + .partial => try self.renderPartial(io, content, writer), .args => try self.renderArgs(writer), .extend => try self.renderExtend(writer), .@"for" => try self.renderFor(context, content, writer, formatters), .@"if" => try self.renderIf(context, content, writer, formatters), - .block => try self.writeBlock(context, content, formatters), + .block => try self.writeBlock(io, context, content, formatters), .blocks => try self.writeBlocks(writer), } } @@ -375,7 +377,7 @@ fn renderHtml( } } -fn renderPartial(self: Node, content: []const u8, writer: anytype) !void { +fn renderPartial(self: Node, io: std.Io, content: []const u8, writer: anytype) !void { if (self.token.args == null) { std.log.err( "Expected `@partial` with name, no name was given [{}->{}]: '{s}'", @@ -418,7 +420,7 @@ fn renderPartial(self: Node, content: []const u8, writer: anytype) !void { return error.ZmplSyntaxError; } - const expected_partial_args = try self.getPartialArgsSignature(prefix, partial_name); + const expected_partial_args = try self.getPartialArgsSignature(io, prefix, partial_name); var reordered_args: ArrayList(Arg) = .empty; defer reordered_args.deinit(self.allocator); @@ -542,7 +544,7 @@ fn renderPartial(self: Node, content: []const u8, writer: anytype) !void { \\ const __slots = [_]__zmpl.Data.Slot{{ \\{[items]s} \\ }}; - \\ var __partial_data: __zmpl.Data = .init(allocator); + \\ var __partial_data: __zmpl.Data = .init(zmpl.io, allocator); \\ __partial_data.template_decls = zmpl.template_decls; \\ defer __partial_data.deinit(); \\ @@ -877,7 +879,7 @@ fn ifStatement(self: Node, input: []const u8) !IfStatement { // Write a `@block` definition - note that we write to a different output buffer here - each // block is compiled into a separate function which is written after the main manifest body. -fn writeBlock(self: Node, context: Context, content: []const u8, formatters: Formatters) !void { +fn writeBlock(self: Node, io: std.Io, context: Context, content: []const u8, formatters: Formatters) !void { if (context == .initial) { const args = self.token.args orelse { std.log.err("Missing argument to `@block` mode: `{s}`", .{self.token.mode_line}); @@ -915,12 +917,12 @@ fn writeBlock(self: Node, context: Context, content: []const u8, formatters: For .{}, writer, ), - .partial => try self.renderPartial(content, writer), + .partial => try self.renderPartial(io, content, writer), .args => try self.renderArgs(writer), .extend => try self.renderExtend(writer), .@"for" => try self.renderFor(context, content, writer, formatters), .@"if" => try self.renderIf(context, content, writer, formatters), - .block => try self.writeBlock(context, content, formatters), + .block => try self.writeBlock(io, context, content, formatters), .blocks => try self.writeBlocks(writer), } } else { @@ -1145,7 +1147,7 @@ fn renderZigLiteral( // Parse a target partial's `@args` pragma in order to re-order keyword args if needed. // We need to read direct from the file here because we can't guarantee that the target partial // has been parsed yet. -fn getPartialArgsSignature(self: Node, prefix: []const u8, partial_name: []const u8) ![]Arg { +fn getPartialArgsSignature(self: Node, io: std.Io, prefix: []const u8, partial_name: []const u8) ![]Arg { const fetch_name = try util.templatePathFetch(self.allocator, partial_name, true); std.mem.replaceScalar(u8, fetch_name, '/', std.fs.path.sep); const with_extension = try std.mem.concat(self.allocator, u8, &[_][]const u8{ fetch_name, ".zmpl" }); @@ -1159,7 +1161,7 @@ fn getPartialArgsSignature(self: Node, prefix: []const u8, partial_name: []const }; const path = try std.fs.path.join(self.allocator, &[_][]const u8{ templates_path, with_extension }); defer self.allocator.free(path); - const content = util.readFile(self.allocator, std.fs.cwd(), path) catch return &.{}; + const content = try util.readFile(io, self.allocator, std.Io.Dir.cwd(), path); defer self.allocator.free(content); var it = std.mem.splitScalar(u8, content, '\n'); diff --git a/src/manifest/Template.zig b/src/manifest/Template.zig index b14d11c..b869cd5 100644 --- a/src/manifest/Template.zig +++ b/src/manifest/Template.zig @@ -104,7 +104,7 @@ pub fn deinit(self: *Template) void { } /// Compile a template into a Zig code which can then be written out and compiled by Zig. -pub fn compile(self: *Template, comptime options: type) ![]const u8 { +pub fn compile(self: *Template, io: std.Io, comptime options: type) ![]const u8 { if (self.state != .initial) unreachable; try self.tokenize(); @@ -115,7 +115,7 @@ pub fn compile(self: *Template, comptime options: type) ![]const u8 { const writer = Node.Writer{ .allocator = self.allocator, .buf = &buf, .token = self.tokens.items[0] }; try self.renderHeader(writer, options); - try self.root_node.compile(self.input, writer, options); + try self.root_node.compile(io, self.input, writer, options); try self.renderFooter(writer); @@ -259,8 +259,8 @@ fn appendToken(self: *Template, context: Context, end: usize, depth: usize) !voi var args = std.mem.trim(u8, mode_line, &std.ascii.whitespace); args = switch (context.delimiter) { .none, .eof => args, - .string => |delimiter_string| std.mem.trimRight(u8, args, delimiter_string), - .brace => std.mem.trimRight(u8, args, "}"), + .string => |delimiter_string| std.mem.trimEnd(u8, args, delimiter_string), + .brace => std.mem.trimEnd(u8, args, "}"), }; const args_start = @tagName(context.mode).len + 1; args = if (args_start <= args.len) @@ -647,7 +647,7 @@ fn renderFooter(self: Template, writer: anytype) !void { \\ const __inner_content = try allocator.dupe(u8, try zmpl.output_buf.toOwnedSlice()); \\ zmpl.content = .{{ .data = zmpl.strip(__inner_content) }}; \\ zmpl.output_buf.clearRetainingCapacity(); - \\ const __content = try __capture.render(zmpl, Context, context, {s}{s}, .{{}}); + \\ const __content = try __capture.render(zmpl.io, zmpl, Context, context, {s}{s}, .{{}}); \\ return __content; \\ }} else {{ \\ const output = try zmpl.output_buf.toOwnedSlice(); @@ -698,7 +698,7 @@ fn renderFooter(self: Template, writer: anytype) !void { \\ ); \\ zmpl.content = .{{ .data = zmpl.strip(inner_content) }}; \\ zmpl.output_buf.clearRetainingCapacity(); - \\ const content = try layout.render(zmpl, Context, context, blocks, .{{}}); + \\ const content = try layout.render(zmpl.io, zmpl, Context, context, blocks, .{{}}); \\ return zmpl.strip(content); \\}} \\ diff --git a/src/manifest/main.zig b/src/manifest/main.zig index aec0761..20e3e23 100644 --- a/src/manifest/main.zig +++ b/src/manifest/main.zig @@ -38,6 +38,9 @@ pub fn main() !void { defer arena.deinit(); const allocator = arena.allocator(); + var threaded = std.Io.Threaded.init_single_threaded; + const io = threaded.io(); + const args = try std.process.argsAlloc(allocator); const manifest_path = args[1]; @@ -55,7 +58,7 @@ pub fn main() !void { const present = !std.mem.eql(u8, path, "_"); try templates_paths.append(allocator, .{ .prefix = prefix, - .path = if (present) try std.fs.realpathAlloc(allocator, path) else "_", + .path = if (present) try std.Io.Dir.cwd().realPathFileAlloc(io, path, allocator) else "_", .present = present, }); } @@ -79,15 +82,16 @@ pub fn main() !void { var manifest: Manifest = .init(templates_paths.items, template_paths_buf.items); - const file = try std.fs.cwd().createFile(manifest_path, .{ .truncate = true }); + const file = try std.Io.Dir.cwd().createFile(io, manifest_path, .{ .truncate = true }); var buffer: [1024]u8 = undefined; - var writer = file.writerStreaming(&buffer); + var writer = file.writerStreaming(io, &buffer); try manifest.compile( + io, allocator, &writer.interface, zmpl_options, ); - file.close(); + file.close(io); } test { diff --git a/src/manifest/util.zig b/src/manifest/util.zig index 1817482..ebc8839 100644 --- a/src/manifest/util.zig +++ b/src/manifest/util.zig @@ -5,7 +5,7 @@ const Writer = std.Io.Writer; /// The first non-whitespace character of a given input (line). pub fn firstMeaningfulChar(input: []const u8) ?u8 { - const stripped = std.mem.trimLeft(u8, input, &std.ascii.whitespace); + const stripped = std.mem.trimStart(u8, input, &std.ascii.whitespace); if (stripped.len == 0) return null; @@ -14,7 +14,7 @@ pub fn firstMeaningfulChar(input: []const u8) ?u8 { /// Detect if a given input string begins with a given value, ignoring leading whitespace. pub fn startsWithIgnoringWhitespace(haystack: []const u8, needle: []const u8) bool { - const stripped = std.mem.trimLeft(u8, haystack, &std.ascii.whitespace); + const stripped = std.mem.trimStart(u8, haystack, &std.ascii.whitespace); return std.mem.startsWith(u8, stripped, needle); } @@ -22,7 +22,7 @@ pub fn startsWithIgnoringWhitespace(haystack: []const u8, needle: []const u8) bo /// Detect if a given input string begins with a given value, ignoring leading whitespace. pub fn indexOfIgnoringWhitespace(haystack: []const u8, needle: []const u8) ?usize { // FIXME: This function makes no sense. - const trimmed = std.mem.trimLeft(u8, haystack, &std.ascii.whitespace); + const trimmed = std.mem.trimStart(u8, haystack, &std.ascii.whitespace); if (std.mem.indexOf(u8, trimmed, needle)) |index| { return (haystack.len - trimmed.len) + index; } else { @@ -160,7 +160,7 @@ pub inline fn strip(input: []const u8) []const u8 { /// Strip surrounding parentheses from a []const u8: `(foobar)` becomes `foobar`. pub inline fn trimParentheses(input: []const u8) []const u8 { - return std.mem.trimRight(u8, std.mem.trimLeft(u8, input, "("), ")"); + return std.mem.trimEnd(u8, std.mem.trimStart(u8, input, "("), ")"); } /// Strip all leading and trailing `\n` except one. @@ -219,8 +219,8 @@ pub fn normalizePathPosix(allocator: Allocator, path: []const u8) ![]const u8 { } /// Try to read a file and return content, output a helpful error on failure. -pub fn readFile(allocator: Allocator, dir: std.fs.Dir, path: []const u8) ![]const u8 { - const stat = dir.statFile(path) catch |err| { +pub fn readFile(io: std.Io, allocator: Allocator, dir: std.Io.Dir, path: []const u8) ![]const u8 { + const stat = dir.statFile(io, path, .{}) catch |err| { switch (err) { error.FileNotFound => { std.debug.print("[zmpl] File not found: {s}\n", .{path}); @@ -229,8 +229,7 @@ pub fn readFile(allocator: Allocator, dir: std.fs.Dir, path: []const u8) ![]cons else => return err, } }; - const content = std.fs.cwd().readFileAlloc(allocator, path, @intCast(stat.size)); - return content; + return std.Io.Dir.cwd().readFileAlloc(io, path, allocator, .limited(stat.size + 1)); } /// Output an escaped string suitable for use in generated Zig code. diff --git a/src/tests.zig b/src/tests.zig index 951894a..48a0df2 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -5,6 +5,7 @@ const ArrayList = std.ArrayList; const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const expectEqualStrings = std.testing.expectEqualStrings; +const io = std.testing.io; const zmpl = @import("zmpl"); const Data = zmpl.Data; @@ -14,7 +15,7 @@ const jetcommon = @import("jetcommon"); const Context = struct { foo: []const u8 = "default" }; test "readme example" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var body = try data.object(); @@ -28,7 +29,7 @@ test "readme example" { try body.put("auth", auth); const template = zmpl.find("example") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\ @@ -54,7 +55,7 @@ test "readme example" { } test "object passing to partial" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var root = try data.root(.object); @@ -66,7 +67,7 @@ test "object passing to partial" { try root.put("user", user); const template = zmpl.find("object_root_layout") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\

User

@@ -77,7 +78,7 @@ test "object passing to partial" { } test "complex example" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var body = try data.object(); @@ -92,7 +93,7 @@ test "complex example" { try body.put("auth", auth); const template = zmpl.find("complex_example") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\
hello
\\

Slots:

@@ -165,11 +166,11 @@ test "direct rendering of slots (render [][]const u8 as line-separated string)" } test "javascript" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); const template = zmpl.find("javascript") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\ \\ { is my favorite character @@ -183,11 +184,11 @@ test "javascript" { } test "partials without blocks" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); const template = zmpl.find("partials_without_blocks") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\ Blah partial content \\
bar
Blah partial content @@ -196,11 +197,11 @@ test "partials without blocks" { } test "custom delimiters" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); const template = zmpl.find("custom_delimiters") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\

Built-in markdown support

\\
@@ -215,11 +216,11 @@ test "custom delimiters" { } test ".md.zmpl extension" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); const template = zmpl.find("markdown_extension") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\

Hello

\\
@@ -227,11 +228,11 @@ test ".md.zmpl extension" { } test "default partial arguments" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); const template = zmpl.find("default_partial_arguments") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\bar, default value \\ @@ -239,11 +240,11 @@ test "default partial arguments" { } test "escaping (HTML and backslash escaping" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); const template = zmpl.find("escaping") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\
<div>
         \\  @partial foo("bar")
@@ -253,7 +254,7 @@ test "escaping (HTML and backslash escaping" {
 }
 
 test "references combined with markdown" {
-    var data: Data = .init(allocator);
+    var data: Data = .init(io, allocator);
     defer data.deinit();
 
     var object = try data.object();
@@ -261,7 +262,7 @@ test "references combined with markdown" {
     try object.put("title", data.string("jetzig.dev"));
 
     const template = zmpl.find("references_markdown") orelse return expect(false);
-    const output = try template.render(&data, Context, .{}, &.{}, .{});
+    const output = try template.render(io, &data, Context, .{}, &.{}, .{});
     try expectEqualStrings(
         \\

Test

\\ @@ -272,7 +273,7 @@ test "references combined with markdown" { } test "partial arg type coercion" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var object = try data.object(); @@ -281,7 +282,7 @@ test "partial arg type coercion" { try object.put("baz", data.string("qux")); const template = zmpl.find("partial_arg_type_coercion") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\100 \\123.456 @@ -291,11 +292,12 @@ test "partial arg type coercion" { } test "inheritance" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); const template = zmpl.find("inheritance_child") orelse return expect(false); const output = try template.render( + io, &data, Context, .{}, @@ -317,7 +319,7 @@ test "inheritance" { } test "root init" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var root = try data.root(.object); @@ -331,7 +333,7 @@ test "root init" { try root.put("auth", auth); const template = zmpl.find("example") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\ @@ -357,14 +359,14 @@ test "root init" { } test "reference stripping" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var root = try data.root(.object); try root.put("message", data.string("hello")); const template = zmpl.find("reference_with_spaces") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\
hello
@@ -373,7 +375,7 @@ test "reference stripping" { } test "inferred type in put/append" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); const TestEnum = enum { field_a, field_b }; @@ -404,7 +406,7 @@ test "inferred type in put/append" { try root.put("optional", optional); const template = zmpl.find("basic") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\hello @@ -418,7 +420,7 @@ test "inferred type in put/append" { } test "getT(.array, ...) and getT(.object, ...)" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var root = try data.root(.object); @@ -440,7 +442,7 @@ test "getT(.array, ...) and getT(.object, ...)" { } test "object.remove(...)" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var obj = try data.object(); @@ -454,7 +456,7 @@ test "object.remove(...)" { } test "getStruct from object" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var root = try data.root(.object); @@ -496,7 +498,7 @@ test "getStruct from object" { } test "Array.items()" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var array = try data.array(); @@ -509,7 +511,7 @@ test "Array.items()" { } test "Object.items()" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var object = try data.object(); @@ -527,7 +529,7 @@ test "Object.items()" { } test "toJson()" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var object = try data.object(); @@ -543,7 +545,7 @@ test "toJson()" { } test "put slice" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var root = try data.root(.object); @@ -566,7 +568,7 @@ test "put slice" { } test "iteration" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var root = try data.root(.object); @@ -582,7 +584,7 @@ test "iteration" { try root.put("objects", objects); const template = zmpl.find("iteration") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\ \\
baz
@@ -613,7 +615,7 @@ test "iteration" { } test "datetime format" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var root = try data.root(.object); @@ -624,7 +626,7 @@ test "datetime format" { try root.put("bar", bar); const template = zmpl.find("datetime_format") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\
Tue Sep 24 19:30:35 2024
\\
2024-09-24
@@ -635,7 +637,7 @@ test "datetime format" { } test "datetime" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var root = try data.root(.object); @@ -646,7 +648,7 @@ test "datetime" { } test "for with partial" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var root = try data.root(.object); @@ -655,7 +657,7 @@ test "for with partial" { try array.append(.{ .foo = "foo2", .bar = "bar2" }); const template = zmpl.find("for_with_partial") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\foo1: bar1 \\
foo1
@@ -668,7 +670,7 @@ test "for with partial" { } test "error union" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var root = try data.root(.object); @@ -678,14 +680,14 @@ test "error union" { } test "xss sanitization/raw formatter" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var root = try data.root(.object); try root.put("foo", ""); const template = zmpl.find("xss") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\<script>alert(':)');</script> \\ @@ -694,7 +696,7 @@ test "xss sanitization/raw formatter" { } test "if/else" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var root = try data.root(.object); @@ -711,7 +713,7 @@ test "if/else" { try foo.put("falsey", false); const template = zmpl.find("if_else") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\ \\ expected here @@ -741,7 +743,7 @@ test "if/else" { } test "for with zmpl value" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var root = try data.root(.object); @@ -752,7 +754,7 @@ test "for with zmpl value" { try foo.append("qux"); const template = zmpl.find("for_with_zmpl_value_main") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\ \\ bar @@ -766,11 +768,11 @@ test "for with zmpl value" { } test "comments" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); const template = zmpl.find("comments") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\ \\ @@ -780,7 +782,7 @@ test "comments" { } test "for with if" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var root = try data.object(); @@ -790,7 +792,7 @@ test "for with if" { try things.append(.{ .foo = "quux", .bar = "corge", .time = "2024-11-24T18:51:23Z" }); const template = zmpl.find("for_with_if") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\
foo: bar \\ @@ -821,7 +823,7 @@ test "for with if" { } test "mix mardown and zig" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var root = try data.object(); @@ -834,7 +836,7 @@ test "mix mardown and zig" { // markdown (i.e. the parent's mode) but the list gets broken into three parts intsead of a // single list. const template = zmpl.find("mix_markdown_and_zig") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\

Header

\\
  • list item 1
  • list item 2
  • qux
  • corge
  • last item
  • qux
@@ -849,7 +851,7 @@ test "nullable if" { // Test with null value - should be falsey { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var clip = try data.object(); @@ -859,13 +861,13 @@ test "nullable if" { try root.put("clip", clip); const template = zmpl.find("nullable_if") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings("\nThe value is null\n", output); } // Test with non-null, non-empty string - should be truthy { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var clip = try data.object(); @@ -875,14 +877,14 @@ test "nullable if" { try root.put("clip", clip); const template = zmpl.find("nullable_if") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); // Non-empty string should correctly evaluate as truthy try expectEqualStrings("\nThe value is not null\n", output); } // Test with empty string - should be falsey like null { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var clip = try data.object(); @@ -892,13 +894,13 @@ test "nullable if" { try root.put("clip", clip); const template = zmpl.find("nullable_if") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings("\nThe value is null\n", output); } } test "if statement with indented HTML - if branch" { - var data: Data = .init(allocator); + var data: Data = .init(io, allocator); defer data.deinit(); var root = try data.root(.object); @@ -908,7 +910,7 @@ test "if statement with indented HTML - if branch" { try root.put("user", user); const template = zmpl.find("if_indented_html") orelse return expect(false); - const output = try template.render(&data, Context, .{}, &.{}, .{}); + const output = try template.render(io, &data, Context, .{}, &.{}, .{}); try expectEqualStrings( \\ \\