From ed577381d7b5e1e397896b087c43cedb3da3272e Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Thu, 6 Feb 2025 04:35:52 -0800 Subject: [PATCH] upgrade to 0.13 and add lots of tests --- build.zig | 20 +- licenses.txt | 3 +- src/lib.zig | 26 +- src/main.zig | 79 ----- test.zig | 867 +++++++++++++++++++++++++++++++++++++++++++++++++++ zig.mod | 5 +- 6 files changed, 896 insertions(+), 104 deletions(-) delete mode 100644 src/main.zig create mode 100644 test.zig diff --git a/build.zig b/build.zig index 9b07cc6db28aa2eaacbc0711ed3bad0b259f00b6..6707e7e23a2b762fe64e6c6d2ba05b6a21def336 100644 --- a/build.zig +++ b/build.zig @@ -14,15 +14,15 @@ pub fn build(b: *std.Build) void { deps.addAllTo(exe); b.installArtifact(exe); - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - if (b.args) |args| { - run_cmd.addArgs(args); - } + const tests = b.addTest(.{ + .root_source_file = b.path("test.zig"), + .target = target, + .optimize = mode, + }); + deps.addAllTo(tests); - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); - - const test_step = b.step("test", "dummy test step to pass CI checks"); - _ = test_step; + const test_step = b.step("test", "Run all library tests"); + const tests_run = b.addRunArtifact(tests); + tests_run.has_side_effects = true; + test_step.dependOn(&tests_run.step); } diff --git a/licenses.txt b/licenses.txt index de393c6167e7b50a0c7c59a240ed29c1f0997f5a..a0b42e8fbd5d71fef3c7492a22023185e81b272b 100644 --- a/licenses.txt +++ b/licenses.txt @@ -4,8 +4,7 @@ MPL-2.0: MIT: = https://spdx.org/licenses/MIT +- git https://github.com/nektro/zig-expect - git https://github.com/nektro/zig-extras - git https://github.com/nektro/zig-tracer - -Unspecified: - git https://github.com/kivikakk/htmlentities.zig diff --git a/src/lib.zig b/src/lib.zig index b59e555da076f32743f5a10ccc9e156c4b51273a..bc56cd9a6864bbd1a5b6b955c634658e2d51641c 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -12,6 +12,7 @@ const string = []const u8; const htmlentities = @import("htmlentities"); const root = @import("root"); const tracer = @import("tracer"); +const extras = @import("extras"); const tokenize = @import("./tokenize.zig"); const astgen = @import("./astgen.zig"); @@ -65,7 +66,7 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d .string => try writer.print(" {s}=\"{}\"", .{ it.key, std.zig.fmtEscapes(it.value.string[1 .. it.value.string.len - 1]) }), .body => { try writer.print(" {s}=\"", .{it.key}); - try do(alloc, writer, astgen.Value{ .body = it.value.body }, data, ctx, opts); + inline for (it.value.body) |bval| try do(alloc, writer, bval, data, ctx, opts); try writer.print("\"", .{}); }, } @@ -103,7 +104,7 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d const TO = @TypeOf(x); const TI = @typeInfo(TO); - if (comptime std.meta.trait.isZigString(TO)) { + if (comptime extras.isZigString(TO)) { if (repl.raw) { try writer.writeAll(x); return; @@ -117,10 +118,10 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d try writer.print("{d}", .{x}); return; } - if (comptime std.meta.trait.hasFn("format")(TO)) { + if (comptime extras.hasFn("format")(TO)) { return std.fmt.format(writer, "{}", .{x}); } - if (comptime std.meta.trait.hasFn("toString")(TO)) { + if (comptime extras.hasFn("toString")(TO)) { try writer.writeAll(try x.toString(alloc)); return; } @@ -167,7 +168,7 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d return; } comptime assertEqual(v.args.len, 1); - if (comptime std.meta.trait.isIndexable(T)) { + if (comptime extras.isIndexable(T)) { try doif(alloc, writer, body, bottom, data, ctx, opts, x.len > 0); return; } @@ -197,7 +198,7 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d return; } comptime assertEqual(v.args.len, 1); - if (comptime std.meta.trait.isIndexable(T)) { + if (comptime extras.isIndexable(T)) { try doif(alloc, writer, body, bottom, data, ctx, opts, x.len == 0); return; } @@ -210,10 +211,10 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d .ifequal => { comptime assertEqual(v.args.len, 2); const y = try resolveArg(v.args[1], alloc, data, ctx, opts); - if (@typeInfo(@TypeOf(x)) == .Enum and comptime std.meta.trait.isZigString(@TypeOf(y))) { + if (@typeInfo(@TypeOf(x)) == .Enum and comptime extras.isZigString(@TypeOf(y))) { return try doif(alloc, writer, body, bottom, data, ctx, opts, std.mem.eql(u8, @tagName(x), y)); } - if (comptime std.meta.trait.isSlice(@TypeOf(x, y))) { + if (comptime extras.isSlice(@TypeOf(x, y))) { return try doif(alloc, writer, body, bottom, data, ctx, opts, std.mem.eql(u8, x, y)); } try doif(alloc, writer, body, bottom, data, ctx, opts, x == y); @@ -221,10 +222,10 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d .ifnotequal => { comptime assertEqual(v.args.len, 2); const y = try resolveArg(v.args[1], alloc, data, ctx, opts); - if (@typeInfo(@TypeOf(x)) == .Enum and comptime std.meta.trait.isZigString(@TypeOf(y))) { + if (@typeInfo(@TypeOf(x)) == .Enum and comptime extras.isZigString(@TypeOf(y))) { return try doif(alloc, writer, body, bottom, data, ctx, opts, !std.mem.eql(u8, @tagName(x), y)); } - if (comptime std.meta.trait.isSlice(@TypeOf(x, y))) { + if (comptime extras.isSlice(@TypeOf(x, y))) { return try doif(alloc, writer, body, bottom, data, ctx, opts, !std.mem.eql(u8, x, y)); } try doif(alloc, writer, body, bottom, data, ctx, opts, x != y); @@ -265,6 +266,7 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d const ATT = std.meta.fieldInfo(AT, .@"3").type; if (v.args.len != std.meta.fields(ATT).len) @compileError(std.fmt.comptimePrint("expected:{d} - actual:{d}", .{ std.meta.fields(ATT).len, v.args.len })); var tupargs = @as(ATT, undefined); + _ = &tupargs; var args = .{ alloc, list.writer(), @@ -342,7 +344,7 @@ fn FieldSearch(comptime T: type, comptime args: []const string) type { } fn Field(comptime T: type, comptime field_name: string) type { - if (std.meta.trait.isIndexable(T) and std.mem.eql(u8, field_name, "len")) { + if (extras.isIndexable(T) and std.mem.eql(u8, field_name, "len")) { return usize; } switch (@typeInfo(T)) { @@ -464,7 +466,7 @@ fn docap(alloc: std.mem.Allocator, writer: anytype, comptime top: astgen.Value, } } -fn isArrayOf(comptime T: type) std.meta.trait.TraitFn { +fn isArrayOf(comptime T: type) fn (type) bool { const Closure = struct { pub fn trait(comptime C: type) bool { return switch (@typeInfo(C)) { diff --git a/src/main.zig b/src/main.zig deleted file mode 100644 index b53aae337cf4bbf90331727e5ff974a6c48ea59f..0000000000000000000000000000000000000000 --- a/src/main.zig +++ /dev/null @@ -1,79 +0,0 @@ -const std = @import("std"); -const pek = @import("pek"); - -const example_document = - \\html[lang="en"]( - \\ head( - \\ meta[charset="utf-8"] - \\ title("Pek Example") - \\ meta[name="viewport" content="width=device-width,initial-scale=1"] - \\ meta[http-equiv="X-UA-Compatible" content="IE=edge"] - \\ ) - \\ body( - \\ h1("Pek Example") - \\ hr - \\ p("This is an example HTML document written in "a[href="https://github.com/nektro/zig-pek"]("Pek")".") - \\ p("Pek is written by "{author}".") - \\ p("Her favorite plant is the "{favorite.flower}) - \\ p("Hello, 世界") - \\ p("The most populous US cities are:") - \\ ul( - \\ {#each top_cities} - \\ li({this.name}", "{this.state.code}) - \\ /each/ - \\ ) - \\ p("Spooky text: "{spooky}) - \\ - \\ {#if am_i_a_girl} - \\ p("#1") - \\ /if/ - \\ - \\ {#ifnot is_it_my_birthday} - \\ p("#2") - \\ /ifnot/ - \\ - \\ {#ifequal top_cities.len best_rating} - \\ p("#3") - \\ /ifequal/ - \\ - \\ {#ifnotequal favorite.color sky} - \\ p("#4") - \\ /ifnotequal/ - \\ ) - \\) -; - -const S = struct { - name: []const u8, - state: struct { - code: []const u8, - }, -}; - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const alloc = gpa.allocator(); - - var name: []const u8 = "Meghan D"; - const doc = comptime pek.parse(example_document); - try pek.compile(@This(), alloc, std.io.getStdOut().writer(), doc, .{ - .author = name, - .favorite = .{ - .flower = "Sunflower", - .program_lang = "Zig", - .color = "Pink", - }, - .top_cities = &[_]S{ - .{ .name = "New York", .state = .{ .code = "NY" } }, - .{ .name = "Los Angeles", .state = .{ .code = "CA" } }, - .{ .name = "Chicago", .state = .{ .code = "IL" } }, - .{ .name = "Houston", .state = .{ .code = "TX" } }, - .{ .name = "Phoenix", .state = .{ .code = "AZ" } }, - }, - .spooky = "I better not be in bold.", - .am_i_a_girl = true, - .sky = "Blue", - .best_rating = @as(usize, 5), - .is_it_my_birthday = false, - }); -} diff --git a/test.zig b/test.zig new file mode 100644 index 0000000000000000000000000000000000000000..0d6bee6829340d27820ff471ae10d373c6c8dd5f --- /dev/null +++ b/test.zig @@ -0,0 +1,867 @@ +const std = @import("std"); +const pek = @import("pek"); +const expect = @import("expect").expect; + +test { + std.testing.refAllDeclsRecursive(pek); +} + +test "document" { + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\html[lang="en"]( + \\ head( + \\ title("Pek Example") + \\ meta[charset="UTF-8"] + \\ meta[name="viewport" content="width=device-width,initial-scale=1"] + \\ ) + \\ body( + \\ h1("Pek Example") + \\ hr + \\ p("This is an example HTML document written in "a[href="https://github.com/nektro/zig-pek"]("Pek")".") + \\ ) + \\) + ); + try pek.compile( + @This(), + alloc, + builder.writer(), + doc, + .{}, + ); + try expect(builder.items).toEqualString( + \\ + \\ + \\ + \\ Pek Example + \\ + \\ + \\ + \\ + \\

Pek Example

+ \\
+ \\

This is an example HTML document written in Pek.

+ \\ + \\ + \\ + ); +} + +test "fragment" { + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ h1("Pek Example") + \\ hr + \\ p("This is an example HTML document written in "a[href="https://github.com/nektro/zig-pek"]("Pek")".") + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{}, + ); + try expect(builder.items).toEqualString( + \\ + \\

Pek Example

+ \\
+ \\

This is an example HTML document written in Pek.

+ \\ + \\ + ); +} + +test "if: basic" { + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ h1("Pek Example") + \\ hr + \\ {#if foo} + \\ p("This is an example HTML document written in "a[href="https://github.com/nektro/zig-pek"]("Pek")".") + \\ /if/ + \\ {#if bar} + \\ p("This will show up because "code("bar")" is true instead.") + \\ /if/ + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .foo = false, .bar = true }, + ); + try expect(builder.items).toEqualString( + \\ + \\

Pek Example

+ \\
+ \\

This will show up because bar is true instead.

+ \\ + \\ + ); +} + +// if else +test "if else + field access" { + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ {#if bar.qux} + \\ p("This will show up because "code("bar")" is true.") + \\ + \\ p("This will show up because "code("bar")" is false.") + \\ /if/ + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .bar = .{ .qux = false } }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This will show up because bar is false.

+ \\ + \\ + ); +} + +// ifnot +test { + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ {#ifnot bar} + \\ p("This will show up because "code("bar")" is false.") + \\ + \\ p("This will show up because "code("bar")" is true.") + \\ /ifnot/ + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .bar = true }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This will show up because bar is true.

+ \\ + \\ + ); +} +test { + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ {#ifnot bar} + \\ p("This will show up because "code("bar")" is false.") + \\ + \\ p("This will show up because "code("bar")" is true.") + \\ /ifnot/ + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .bar = false }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This will show up because bar is false.

+ \\ + \\ + ); +} + +// ifequal +test { // bool + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ {#ifequal bar true} + \\ p("This will show up because "code("bar")" is true.") + \\ + \\ p("This will show up because "code("bar")" is false.") + \\ /ifequal/ + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .bar = true }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This will show up because bar is true.

+ \\ + \\ + ); +} +test { // bool false + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ {#ifequal bar true} + \\ p("This will show up because "code("bar")" is true.") + \\ + \\ p("This will show up because "code("bar")" is false.") + \\ /ifequal/ + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .bar = false }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This will show up because bar is false.

+ \\ + \\ + ); +} +test { // string + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ {#ifequal bar "foo"} + \\ p("This will show up because "code("bar")" is 'foo'.") + \\ + \\ p("This will show up because "code("bar")" is 'qux'.") + \\ /ifequal/ + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .bar = @as([]const u8, "foo") }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This will show up because bar is 'foo'.

+ \\ + \\ + ); +} +test { // string false + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ {#ifequal bar "foo"} + \\ p("This will show up because "code("bar")" is 'foo'.") + \\ + \\ p("This will show up because "code("bar")" is 'qux'.") + \\ /ifequal/ + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .bar = @as([]const u8, "qux") }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This will show up because bar is 'qux'.

+ \\ + \\ + ); +} +test { // enum + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ {#ifequal bar "foo"} + \\ p("This will show up because "code("bar")" is .foo.") + \\ + \\ p("This will show up because "code("bar")" is .qux.") + \\ /ifequal/ + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .bar = @as(enum { foo, bar, qux }, .foo) }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This will show up because bar is .foo.

+ \\ + \\ + ); +} +test { // enum false + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ {#ifequal bar "foo"} + \\ p("This will show up because "code("bar")" is .foo.") + \\ + \\ p("This will show up because "code("bar")" is .qux.") + \\ /ifequal/ + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .bar = @as(enum { foo, bar, qux }, .qux) }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This will show up because bar is .qux.

+ \\ + \\ + ); +} + +// ifnotequal +test { // bool + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ {#ifnotequal bar true} + \\ p("This will show up because "code("bar")" is false.") + \\ + \\ p("This will show up because "code("bar")" is true.") + \\ /ifnotequal/ + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .bar = false }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This will show up because bar is false.

+ \\ + \\ + ); +} +test { // bool false + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ {#ifnotequal bar true} + \\ p("This will show up because "code("bar")" is false.") + \\ + \\ p("This will show up because "code("bar")" is true.") + \\ /ifnotequal/ + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .bar = true }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This will show up because bar is true.

+ \\ + \\ + ); +} +test { // string + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ {#ifnotequal bar "foo"} + \\ p("This will show up because "code("bar")" is 'qux'.") + \\ + \\ p("This will show up because "code("bar")" is 'foo'.") + \\ /ifnotequal/ + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .bar = @as([]const u8, "qux") }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This will show up because bar is 'qux'.

+ \\ + \\ + ); +} +test { // string false + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ {#ifnotequal bar "foo"} + \\ p("This will show up because "code("bar")" is 'qux'.") + \\ + \\ p("This will show up because "code("bar")" is 'foo'.") + \\ /ifnotequal/ + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .bar = @as([]const u8, "foo") }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This will show up because bar is 'foo'.

+ \\ + \\ + ); +} +test { // enum + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ {#ifnotequal bar "foo"} + \\ p("This will show up because "code("bar")" is .qux.") + \\ + \\ p("This will show up because "code("bar")" is .foo.") + \\ /ifnotequal/ + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .bar = @as(enum { foo, bar, qux }, .qux) }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This will show up because bar is .qux.

+ \\ + \\ + ); +} +test { // enum false + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ {#ifnotequal bar "foo"} + \\ p("This will show up because "code("bar")" is .qux.") + \\ + \\ p("This will show up because "code("bar")" is .foo.") + \\ /ifnotequal/ + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .bar = @as(enum { foo, bar, qux }, .foo) }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This will show up because bar is .foo.

+ \\ + \\ + ); +} + +// replacement +test { + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ p("This text for this link is "a[href="https://github.com/nektro/zig-pek"]({text})".") + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .text = @as([]const u8, "dynamic") }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This text for this link is dynamic.

+ \\ + \\ + ); +} +test { + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ p("This url for this link is "a[href=({url})]("dynamic")".") + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .url = @as([]const u8, "https://github.com/nektro/zig-pek") }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This url for this link is dynamic.

+ \\ + \\ + ); +} +test { + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ p("This host for this link is "a[href=("https://"{host}"/nektro/zig-pek")]("dynamic")".") + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .host = @as([]const u8, "github.com") }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This host for this link is dynamic.

+ \\ + \\ + ); +} +test { + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ p("This host for this link is "a[href=("https://"{host}"/nektro/zig-pek")]("dynamic")".") + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .host = "github.com".* }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This host for this link is dynamic.

+ \\ + \\ + ); +} + +// replacement with custom serializer +test { + const S = struct { + name: []const u8, + + pub fn format(s: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + _ = fmt; + _ = options; + try writer.writeAll(s.name); + try writer.writeAll(".com"); + } + }; + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ p("This host for this link is "a[href=("https://"{host}"/nektro/zig-pek")]("dynamic")".") + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .host = S{ .name = "github" } }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This host for this link is dynamic.

+ \\ + \\ + ); +} +test { + const S = struct { + name: []const u8, + + pub fn toString(s: @This(), alloc: std.mem.Allocator) ![]const u8 { + return std.fmt.allocPrint(alloc, "{s}.com", .{s.name}); + } + }; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ p("This host for this link is "a[href=("https://"{host}"/nektro/zig-pek")]("dynamic")".") + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .host = S{ .name = "github" } }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This host for this link is dynamic.

+ \\ + \\ + ); +} + +// raw replacement +test { + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ p("This url for this link is "a[href=({{url}})]("dynamic but not escaped")".") + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .url = "https://github.com/nektro/zig-pek".* }, + ); + try expect(builder.items).toEqualString( + \\ + \\

This url for this link is dynamic but not escaped.

+ \\ + \\ + ); +} +test { + const alloc = std.testing.allocator; + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + const doc = comptime pek.parse( + \\body( + \\ p("Raw replacements can also "{{foo}}".") + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = @This(), .indent = 0, .flag1 = false }, + .{ .foo = "contain HTML such as as an escape hatch when you know content is safe".* }, + ); + try expect(builder.items).toEqualString( + \\ + \\

Raw replacements can also contain HTML such as as an escape hatch when you know content is safe.

+ \\ + \\ + ); +} + +// custom function +test { + const C = struct { + pub fn pek__input_text(alloc: std.mem.Allocator, writer: pek.Writer, comptime opts: pek.DoOptions, args: struct { []const u8, []const u8, []const u8, []const u8 }) !void { + const tmpl = comptime pek.parse( + \\label( + \\ div({label}) + \\ textarea[type="text" required="" name=({name}) placeholder=({placeholder})]({value}) + \\) + ); + try pek.compileInner(alloc, writer, tmpl, opts, .{ + .name = args[0], + .label = args[1], + .value = args[2], + .placeholder = args[3], + }); + } + }; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + var person: struct { name: []const u8 } = .{ .name = "meghan" }; + _ = &person; + const doc = comptime pek.parse( + \\body( + \\ form( + \\ {##input_text "name" "Name" prev.name ""} + \\ button("Submit ▶") + \\ ) + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = C, .indent = 0, .flag1 = false }, + .{ .prev = person }, + ); + try expect(builder.items).toEqualString( + \\ + \\
+ \\
+ \\ + \\ + ); +} + +// custom bool function +test { + const S = struct { + id: u64, + }; + const C = struct { + pub fn pek_is_admin(alloc: std.mem.Allocator, user: S) !bool { + _ = alloc; + return user.id < 10; + } + }; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + var person: S = .{ .id = 0 }; + _ = &person; + const doc = comptime pek.parse( + \\body( + \\ {#if#is_admin user} + \\ p("only an admin user can see this") + \\ + \\ p("unauthorized users will see this") + \\ /if/ + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = C, .indent = 0, .flag1 = false }, + .{ .user = person }, + ); + try expect(builder.items).toEqualString( + \\ + \\

only an admin user can see this

+ \\ + \\ + ); +} +test { + const S = struct { + id: u64, + }; + const C = struct { + pub fn pek_is_admin(alloc: std.mem.Allocator, user: S) !bool { + _ = alloc; + return user.id < 10; + } + }; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + var builder = std.ArrayList(u8).init(alloc); + defer builder.deinit(); + var person: S = .{ .id = 25 }; + _ = &person; + const doc = comptime pek.parse( + \\body( + \\ {#if#is_admin user} + \\ p("only an admin user can see this") + \\ + \\ p("unauthorized users will see this") + \\ /if/ + \\) + ); + try pek.compileInner( + alloc, + builder.writer(), + doc, + .{ .Ctx = C, .indent = 0, .flag1 = false }, + .{ .user = person }, + ); + try expect(builder.items).toEqualString( + \\ + \\

unauthorized users will see this

+ \\ + \\ + ); +} diff --git a/zig.mod b/zig.mod index 138741e5c1ee9f5b3f5ffc932a861361bdf6f2d0..adcae393819b15f145a56a731882268503e198cd 100644 --- a/zig.mod +++ b/zig.mod @@ -4,6 +4,9 @@ main: src/lib.zig license: MPL-2.0 description: An HTML preprocessor with a builtin template engine. dependencies: - - src: git https://github.com/kivikakk/htmlentities.zig + - src: git https://github.com/kivikakk/htmlentities.zig commit-bd5d569a245c7c8e83812eadcb5761b7ba76ef04 - src: git https://github.com/nektro/zig-extras - src: git https://github.com/nektro/zig-tracer + +root_dependencies: + - src: git https://github.com/nektro/zig-expect -- 2.54.0