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 . diff --git a/build.zig b/build.zig index 75eec1f..f8bbd4c 100755 --- a/build.zig +++ b/build.zig @@ -11,11 +11,17 @@ 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", "Use LLVM", - ) orelse true; + ); const test_filters = b.option( []const []const u8, @@ -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, @@ -293,24 +300,24 @@ 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); -// } - -fn findTemplates(b: *Build, templates_paths: []const []const u8) ![][]const u8 { +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, 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); diff --git a/build.zig.zon b/build.zig.zon index 11ece3c..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/ed326c74b98fae893acd89d2e84c0d894a200df3.tar.gz", - .hash = "jetcommon-0.1.0-jPY_DbNIAAA0utNExN3zMeqXA4fhZXQ6LDDP1eg8vodj", - }, .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 = .{ @@ -24,4 +24,3 @@ "README.md", }, } - 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()}); } 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 9291f28..20e3e23 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); @@ -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 ab235c8..48a0df2 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -1,21 +1,21 @@ const std = @import("std"); -const zmpl = @import("zmpl"); const allocator = std.testing.allocator; const ArenaAllocator = std.heap.ArenaAllocator; 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; 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(io, allocator); defer data.deinit(); var body = try data.object(); @@ -29,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( \\ @@ -55,10 +55,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(io, allocator); defer data.deinit(); var root = try data.root(.object); @@ -70,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( \\
<div>
\\ @partial foo("bar")
@@ -281,10 +254,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(io, allocator);
defer data.deinit();
var object = try data.object();
@@ -292,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
\\
@@ -303,10 +273,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(io, allocator);
defer data.deinit();
var object = try data.object();
@@ -315,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
@@ -325,14 +292,12 @@ 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(io, allocator);
defer data.deinit();
const template = zmpl.find("inheritance_child") orelse return expect(false);
const output = try template.render(
+ io,
&data,
Context,
.{},
@@ -354,10 +319,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(io, allocator);
defer data.deinit();
var root = try data.root(.object);
@@ -371,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(
\\
@@ -397,17 +359,14 @@ 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(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
@@ -416,10 +375,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(io, allocator);
defer data.deinit();
const TestEnum = enum { field_a, field_b };
@@ -450,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
@@ -464,10 +420,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(io, allocator);
defer data.deinit();
var root = try data.root(.object);
@@ -489,10 +442,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(io, allocator);
defer data.deinit();
var obj = try data.object();
@@ -506,10 +456,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(io, allocator);
defer data.deinit();
var root = try data.root(.object);
@@ -551,10 +498,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(io, allocator);
defer data.deinit();
var array = try data.array();
@@ -567,10 +511,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(io, allocator);
defer data.deinit();
var object = try data.object();
@@ -588,10 +529,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(io, allocator);
defer data.deinit();
var object = try data.object();
@@ -607,10 +545,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(io, allocator);
defer data.deinit();
var root = try data.root(.object);
@@ -633,10 +568,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(io, allocator);
defer data.deinit();
var root = try data.root(.object);
@@ -652,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
@@ -683,10 +615,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(io, allocator);
defer data.deinit();
var root = try data.root(.object);
@@ -697,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
@@ -708,10 +637,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(io, allocator);
defer data.deinit();
var root = try data.root(.object);
@@ -722,10 +648,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(io, allocator);
defer data.deinit();
var root = try data.root(.object);
@@ -734,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
@@ -747,10 +670,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(io, allocator);
defer data.deinit();
var root = try data.root(.object);
@@ -760,17 +680,14 @@ 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(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>
\\
@@ -779,10 +696,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(io, allocator);
defer data.deinit();
var root = try data.root(.object);
@@ -799,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
@@ -829,10 +743,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(io, allocator);
defer data.deinit();
var root = try data.root(.object);
@@ -843,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
@@ -857,14 +768,11 @@ 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(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(
\\
\\
@@ -874,10 +782,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(io, allocator);
defer data.deinit();
var root = try data.object();
@@ -887,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
\\
@@ -918,10 +823,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(io, allocator);
defer data.deinit();
var root = try data.object();
@@ -934,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
@@ -949,10 +851,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(io, allocator);
defer data.deinit();
var clip = try data.object();
@@ -962,16 +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 arena: ArenaAllocator = .init(allocator);
- defer arena.deinit();
-
- var data: zmpl.Data = .init(arena.allocator());
+ var data: Data = .init(io, allocator);
defer data.deinit();
var clip = try data.object();
@@ -981,17 +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 arena: ArenaAllocator = .init(allocator);
- defer arena.deinit();
-
- var data: zmpl.Data = .init(arena.allocator());
+ var data: Data = .init(io, allocator);
defer data.deinit();
var clip = try data.object();
@@ -1001,16 +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 arena: ArenaAllocator = .init(allocator);
- defer arena.deinit();
-
- var data: zmpl.Data = .init(arena.allocator());
+ var data: Data = .init(io, allocator);
defer data.deinit();
var root = try data.root(.object);
@@ -1020,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(
\\
\\
@@ -1038,10 +928,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(io, allocator);
defer data.deinit();
var root = try data.root(.object);
@@ -1050,7 +937,7 @@ test "if statement with indented HTML - else 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(
\\
\\
@@ -1062,14 +949,12 @@ 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(io, allocator);
defer data.deinit();
const template = zmpl.find("blocks") orelse return expect(false);
const output = try template.render(
+ std.testing.io,
&data,
Context,
.{},
@@ -1084,10 +969,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(io, allocator);
defer data.deinit();
var root = try data.root(.object);
@@ -1107,9 +989,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 +999,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.zig b/src/zmpl.zig
index 71ae18e..5192ba3 100644
--- a/src/zmpl.zig
+++ b/src/zmpl.zig
@@ -29,7 +29,7 @@ pub const find = Manifest.find;
pub const findPrefixed = Manifest.findPrefixed;
pub fn chomp(input: []const u8) []const u8 {
- return std.mem.trimRight(u8, input, "\r\n");
+ return std.mem.trimEnd(u8, input, "\r\n");
}
/// Sanitize input. Used internally for rendering data refs. Use `zmpl.fmt.sanitize` to manually
diff --git a/src/zmpl/Data.zig b/src/zmpl/Data.zig
index 32883fc..6717f9f 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,10 @@ 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,
+io: std.Io,
+parent_allocator: Allocator,
output_buf: *Writer.Allocating,
output_writer: *Writer,
value: ?*Value = null,
@@ -87,24 +91,39 @@ 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(io: std.Io, 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 };
+ data.io = io;
+ 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.
@@ -151,7 +170,7 @@ pub fn ref(self: Data, key: []const u8) ?*Value {
}
// We still support old-style refs without the preceding `$`.
- const trimmed_key = std.mem.trimLeft(
+ const trimmed_key = std.mem.trimStart(
u8,
if (std.mem.startsWith(u8, key, "$")) key[1..] else key,
".",
@@ -1016,7 +1035,7 @@ pub const Value = union(ValueType) {
.{@tagName(operator)},
),
.datetime => |capture| capture.value.compare(
- std.enums.nameCast(jetcommon.Operator, operator),
+ @field(jetcommon.Operator, @tagName(operator)),
other.datetime.value,
),
.null => true, // If both sides are `Null` then this can only be true.
@@ -1640,8 +1659,7 @@ 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.hashmap.clearAndFree();
}
@@ -1897,11 +1915,11 @@ 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.hashmap.swapRemove(key);
+ return true;
+ }
+ return false;
}
};
@@ -1915,7 +1933,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.
@@ -2293,56 +2311,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" {
@@ -2364,73 +2382,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"),
);
@@ -2438,17 +2456,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"),
);
@@ -2461,18 +2479,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"),
);
@@ -2480,7 +2498,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"),
);
@@ -2488,17 +2506,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.io, testing.allocator);
defer data.deinit();
var array1 = try data.root(.array);
@@ -2514,7 +2529,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"]}]
\\
,
@@ -2527,8 +2542,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" {
@@ -2538,19 +2553,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.io, testing.allocator);
defer data.deinit();
var array1 = try data.root(.array);
@@ -2558,65 +2570,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.io, 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.io, 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);
}
diff --git a/src/zmpl/Template.zig b/src/zmpl/Template.zig
index 5c1ada7..c748034 100644
--- a/src/zmpl/Template.zig
+++ b/src/zmpl/Template.zig
@@ -29,6 +29,7 @@ pub const RenderOptions = struct {
pub fn render(
self: Template,
+ io: std.Io,
data: *Data,
Context: ?type,
context: if (Context) |C| C else @TypeOf(null),
@@ -45,7 +46,7 @@ pub fn render(
const renderFn = @field(Manifest, template.name ++ "_renderWithLayout");
break :blk renderFn(layout, data, C, c, template.blocks) catch |err| {
if (@errorReturnTrace()) |stack_trace| {
- try debug.printSourceInfo(data.allocator, err, stack_trace);
+ try debug.printSourceInfo(io, data.allocator, err, stack_trace);
}
break :blk err;
};
@@ -58,7 +59,7 @@ pub fn render(
const renderFn = @field(Manifest, template.name ++ "_render");
break :blk renderFn(data, C, c, blocks) catch |err| {
if (@errorReturnTrace()) |stack_trace| {
- try debug.printSourceInfo(data.allocator, err, stack_trace);
+ try debug.printSourceInfo(io, data.allocator, err, stack_trace);
}
break :blk err;
};
diff --git a/src/zmpl/debug.zig b/src/zmpl/debug.zig
index bd45ce6..681e021 100644
--- a/src/zmpl/debug.zig
+++ b/src/zmpl/debug.zig
@@ -6,19 +6,21 @@ const colors = @import("colors.zig");
const util = @import("util.zig");
pub fn printSourceInfo(
+ io: std.Io,
allocator: Allocator,
err: anyerror,
stack_trace: *std.builtin.StackTrace,
) !void {
const debug_info = std.debug.getSelfDebugInfo() catch return err;
- const source_location = (zmplSourceLocation(debug_info, stack_trace, err) catch
+ const source_location = (zmplSourceLocation(io, allocator, debug_info, stack_trace, err) catch
return err) orelse
return err;
- defer debug_info.allocator.free(source_location.file_name);
- try debugSourceLocation(allocator, source_location);
+ try debugSourceLocation(io, allocator, source_location);
}
fn zmplSourceLocation(
+ io: std.Io,
+ allocator: Allocator,
debug_info: *std.debug.SelfInfo,
stack_trace: *std.builtin.StackTrace,
err: anyerror,
@@ -34,10 +36,8 @@ fn zmplSourceLocation(
}) {
const return_address = stack_trace.instruction_addresses[frame_index];
const address = return_address - 1;
- const module = debug_info.getModuleForAddress(address) catch return err;
- const symbol_info = module.getSymbolAtAddress(debug_info.allocator, address) catch
- return err;
+ const symbol_info = debug_info.getSymbol(allocator, io, address) catch return err;
if (symbol_info.source_location) |source_location| {
if (std.mem.endsWith(u8, source_location.file_name, "zmpl.manifest.zig")) {
@@ -50,10 +50,11 @@ fn zmplSourceLocation(
}
fn debugSourceLocation(
+ io: std.Io,
allocator: Allocator,
source_location: std.debug.SourceLocation,
) !void {
- const debug_line = try findDebugLine(allocator, source_location) orelse return;
+ const debug_line = try findDebugLine(io, allocator, source_location) orelse return;
var it = std.mem.tokenizeScalar(u8, debug_line, ':');
_ = it.next();
_ = it.next();
@@ -65,11 +66,13 @@ fn debugSourceLocation(
const from_position = try std.fmt.parseInt(usize, from.?, 10);
const to_position = try std.fmt.parseInt(usize, to.?, 10);
- const source_file = try std.fs.openFileAbsolute(filename, .{});
+ const source_file_handle = try std.Io.Dir.openFileAbsolute(io, filename, .{});
+ var read_buffer: [256]u8 = undefined;
const content = try allocator.alloc(u8, to_position - from_position + 1);
+ var source_file = source_file_handle.reader(io, &read_buffer);
try source_file.seekTo(from_position);
- _ = try source_file.readAll(content);
+ try source_file.interface.readSliceAll(content);
var cursor: usize = 0;
var buf: [std.heap.pageSize()]u8 = undefined;
@@ -77,7 +80,7 @@ fn debugSourceLocation(
const source_line_number = outer: {
while (cursor < from_position) {
var line_count: usize = 1;
- const bytes_read = try source_file.readAll(buf[0..]);
+ const bytes_read = try source_file.interface.readSliceShort(buf[0..]);
if (bytes_read == 0) return;
for (buf[0..bytes_read]) |char| {
if (cursor >= from_position) break :outer line_count;
@@ -104,11 +107,12 @@ fn debugSourceLocation(
}
fn findDebugLine(
+ io: std.Io,
allocator: Allocator,
source_location: std.debug.SourceLocation,
) !?[]const u8 {
- const file = try std.fs.openFileAbsolute(source_location.file_name, .{});
- const stat = try file.stat();
+ const file_handle = try std.Io.Dir.openFileAbsolute(io, source_location.file_name, .{});
+ const stat = try file_handle.stat(io);
const size = stat.size;
var cursor: usize = 0;
@@ -116,8 +120,11 @@ fn findDebugLine(
var buf: [std.heap.pageSize()]u8 = undefined;
var position: usize = 0;
+ var read_buffer: [256]u8 = undefined;
+ var file = file_handle.reader(io, &read_buffer);
+
while (cursor < size) outer: {
- const bytes_read = try file.readAll(buf[0..]);
+ const bytes_read = try file.interface.readSliceShort(buf[0..]);
if (bytes_read == 0) return null;
for (buf) |char| {
if (char == '\n') line += 1;
@@ -136,7 +143,7 @@ fn findDebugLine(
outer: {
while (cursor < size) {
- const bytes_read = try file.readAll(buf[0..]);
+ const bytes_read = try file.interface.readSliceShort(buf[0..]);
if (bytes_read == 0) return null;
cursor += bytes_read;
if (std.mem.indexOf(u8, buf[0..bytes_read], "//zmpl:debug")) |index| {
@@ -146,7 +153,7 @@ fn findDebugLine(
} else {
try debug_writer.writeAll(buf[0..bytes_read]);
while (cursor < size) {
- const line_bytes_read = try file.read(buf[0..]);
+ const line_bytes_read = try file.interface.readSliceShort(buf[0..]);
if (std.mem.indexOf(u8, buf[0..line_bytes_read], "\n")) |line_index| {
try debug_writer.writeAll(buf[0..line_index]);
break :outer;