authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2025-02-06 04:35:52 -08:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2025-02-13 12:32:04 -08:00
loged577381d7b5e1e397896b087c43cedb3da3272e
treefe02cbc9ff3b529db076a2f613736fa3242d4cfa
parent6f98a0f349e71b1c54a2e1e8c495a37243b3b722

upgrade to 0.13 and add lots of tests


6 files changed, 896 insertions(+), 104 deletions(-)

build.zig+10-10
...@@ -14,15 +14,15 @@ pub fn build(b: *std.Build) void {...@@ -14,15 +14,15 @@ pub fn build(b: *std.Build) void {
14 deps.addAllTo(exe);14 deps.addAllTo(exe);
15 b.installArtifact(exe);15 b.installArtifact(exe);
1616
17 const run_cmd = b.addRunArtifact(exe);17 const tests = b.addTest(.{
18 run_cmd.step.dependOn(b.getInstallStep());18 .root_source_file = b.path("test.zig"),
19 if (b.args) |args| {19 .target = target,
20 run_cmd.addArgs(args);20 .optimize = mode,
21 }21 });
2222 deps.addAllTo(tests);
23 const run_step = b.step("run", "Run the app");
24 run_step.dependOn(&run_cmd.step);
2523
26 const test_step = b.step("test", "dummy test step to pass CI checks");24 const test_step = b.step("test", "Run all library tests");
27 _ = test_step;25 const tests_run = b.addRunArtifact(tests);
26 tests_run.has_side_effects = true;
27 test_step.dependOn(&tests_run.step);
28}28}
licenses.txt+1-2
...@@ -4,8 +4,7 @@ MPL-2.0:...@@ -4,8 +4,7 @@ MPL-2.0:
44
5MIT:5MIT:
6= https://spdx.org/licenses/MIT6= https://spdx.org/licenses/MIT
7- git https://github.com/nektro/zig-expect
7- git https://github.com/nektro/zig-extras8- git https://github.com/nektro/zig-extras
8- git https://github.com/nektro/zig-tracer9- git https://github.com/nektro/zig-tracer
9
10Unspecified:
11- git https://github.com/kivikakk/htmlentities.zig10- git https://github.com/kivikakk/htmlentities.zig
src/lib.zig+14-12
...@@ -12,6 +12,7 @@ const string = []const u8;...@@ -12,6 +12,7 @@ const string = []const u8;
12const htmlentities = @import("htmlentities");12const htmlentities = @import("htmlentities");
13const root = @import("root");13const root = @import("root");
14const tracer = @import("tracer");14const tracer = @import("tracer");
15const extras = @import("extras");
1516
16const tokenize = @import("./tokenize.zig");17const tokenize = @import("./tokenize.zig");
17const astgen = @import("./astgen.zig");18const astgen = @import("./astgen.zig");
...@@ -65,7 +66,7 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d...@@ -65,7 +66,7 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d
65 .string => try writer.print(" {s}=\"{}\"", .{ it.key, std.zig.fmtEscapes(it.value.string[1 .. it.value.string.len - 1]) }),66 .string => try writer.print(" {s}=\"{}\"", .{ it.key, std.zig.fmtEscapes(it.value.string[1 .. it.value.string.len - 1]) }),
66 .body => {67 .body => {
67 try writer.print(" {s}=\"", .{it.key});68 try writer.print(" {s}=\"", .{it.key});
68 try do(alloc, writer, astgen.Value{ .body = it.value.body }, data, ctx, opts);69 inline for (it.value.body) |bval| try do(alloc, writer, bval, data, ctx, opts);
69 try writer.print("\"", .{});70 try writer.print("\"", .{});
70 },71 },
71 }72 }
...@@ -103,7 +104,7 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d...@@ -103,7 +104,7 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d
103 const TO = @TypeOf(x);104 const TO = @TypeOf(x);
104 const TI = @typeInfo(TO);105 const TI = @typeInfo(TO);
105106
106 if (comptime std.meta.trait.isZigString(TO)) {107 if (comptime extras.isZigString(TO)) {
107 if (repl.raw) {108 if (repl.raw) {
108 try writer.writeAll(x);109 try writer.writeAll(x);
109 return;110 return;
...@@ -117,10 +118,10 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d...@@ -117,10 +118,10 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d
117 try writer.print("{d}", .{x});118 try writer.print("{d}", .{x});
118 return;119 return;
119 }120 }
120 if (comptime std.meta.trait.hasFn("format")(TO)) {121 if (comptime extras.hasFn("format")(TO)) {
121 return std.fmt.format(writer, "{}", .{x});122 return std.fmt.format(writer, "{}", .{x});
122 }123 }
123 if (comptime std.meta.trait.hasFn("toString")(TO)) {124 if (comptime extras.hasFn("toString")(TO)) {
124 try writer.writeAll(try x.toString(alloc));125 try writer.writeAll(try x.toString(alloc));
125 return;126 return;
126 }127 }
...@@ -167,7 +168,7 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d...@@ -167,7 +168,7 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d
167 return;168 return;
168 }169 }
169 comptime assertEqual(v.args.len, 1);170 comptime assertEqual(v.args.len, 1);
170 if (comptime std.meta.trait.isIndexable(T)) {171 if (comptime extras.isIndexable(T)) {
171 try doif(alloc, writer, body, bottom, data, ctx, opts, x.len > 0);172 try doif(alloc, writer, body, bottom, data, ctx, opts, x.len > 0);
172 return;173 return;
173 }174 }
...@@ -197,7 +198,7 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d...@@ -197,7 +198,7 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d
197 return;198 return;
198 }199 }
199 comptime assertEqual(v.args.len, 1);200 comptime assertEqual(v.args.len, 1);
200 if (comptime std.meta.trait.isIndexable(T)) {201 if (comptime extras.isIndexable(T)) {
201 try doif(alloc, writer, body, bottom, data, ctx, opts, x.len == 0);202 try doif(alloc, writer, body, bottom, data, ctx, opts, x.len == 0);
202 return;203 return;
203 }204 }
...@@ -210,10 +211,10 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d...@@ -210,10 +211,10 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d
210 .ifequal => {211 .ifequal => {
211 comptime assertEqual(v.args.len, 2);212 comptime assertEqual(v.args.len, 2);
212 const y = try resolveArg(v.args[1], alloc, data, ctx, opts);213 const y = try resolveArg(v.args[1], alloc, data, ctx, opts);
213 if (@typeInfo(@TypeOf(x)) == .Enum and comptime std.meta.trait.isZigString(@TypeOf(y))) {214 if (@typeInfo(@TypeOf(x)) == .Enum and comptime extras.isZigString(@TypeOf(y))) {
214 return try doif(alloc, writer, body, bottom, data, ctx, opts, std.mem.eql(u8, @tagName(x), y));215 return try doif(alloc, writer, body, bottom, data, ctx, opts, std.mem.eql(u8, @tagName(x), y));
215 }216 }
216 if (comptime std.meta.trait.isSlice(@TypeOf(x, y))) {217 if (comptime extras.isSlice(@TypeOf(x, y))) {
217 return try doif(alloc, writer, body, bottom, data, ctx, opts, std.mem.eql(u8, x, y));218 return try doif(alloc, writer, body, bottom, data, ctx, opts, std.mem.eql(u8, x, y));
218 }219 }
219 try doif(alloc, writer, body, bottom, data, ctx, opts, x == y);220 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...@@ -221,10 +222,10 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d
221 .ifnotequal => {222 .ifnotequal => {
222 comptime assertEqual(v.args.len, 2);223 comptime assertEqual(v.args.len, 2);
223 const y = try resolveArg(v.args[1], alloc, data, ctx, opts);224 const y = try resolveArg(v.args[1], alloc, data, ctx, opts);
224 if (@typeInfo(@TypeOf(x)) == .Enum and comptime std.meta.trait.isZigString(@TypeOf(y))) {225 if (@typeInfo(@TypeOf(x)) == .Enum and comptime extras.isZigString(@TypeOf(y))) {
225 return try doif(alloc, writer, body, bottom, data, ctx, opts, !std.mem.eql(u8, @tagName(x), y));226 return try doif(alloc, writer, body, bottom, data, ctx, opts, !std.mem.eql(u8, @tagName(x), y));
226 }227 }
227 if (comptime std.meta.trait.isSlice(@TypeOf(x, y))) {228 if (comptime extras.isSlice(@TypeOf(x, y))) {
228 return try doif(alloc, writer, body, bottom, data, ctx, opts, !std.mem.eql(u8, x, y));229 return try doif(alloc, writer, body, bottom, data, ctx, opts, !std.mem.eql(u8, x, y));
229 }230 }
230 try doif(alloc, writer, body, bottom, data, ctx, opts, x != y);231 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...@@ -265,6 +266,7 @@ fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.Value, d
265 const ATT = std.meta.fieldInfo(AT, .@"3").type;266 const ATT = std.meta.fieldInfo(AT, .@"3").type;
266 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 }));267 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 }));
267 var tupargs = @as(ATT, undefined);268 var tupargs = @as(ATT, undefined);
269 _ = &tupargs;
268 var args = .{270 var args = .{
269 alloc,271 alloc,
270 list.writer(),272 list.writer(),
...@@ -342,7 +344,7 @@ fn FieldSearch(comptime T: type, comptime args: []const string) type {...@@ -342,7 +344,7 @@ fn FieldSearch(comptime T: type, comptime args: []const string) type {
342}344}
343345
344fn Field(comptime T: type, comptime field_name: string) type {346fn Field(comptime T: type, comptime field_name: string) type {
345 if (std.meta.trait.isIndexable(T) and std.mem.eql(u8, field_name, "len")) {347 if (extras.isIndexable(T) and std.mem.eql(u8, field_name, "len")) {
346 return usize;348 return usize;
347 }349 }
348 switch (@typeInfo(T)) {350 switch (@typeInfo(T)) {
...@@ -464,7 +466,7 @@ fn docap(alloc: std.mem.Allocator, writer: anytype, comptime top: astgen.Value,...@@ -464,7 +466,7 @@ fn docap(alloc: std.mem.Allocator, writer: anytype, comptime top: astgen.Value,
464 }466 }
465}467}
466468
467fn isArrayOf(comptime T: type) std.meta.trait.TraitFn {469fn isArrayOf(comptime T: type) fn (type) bool {
468 const Closure = struct {470 const Closure = struct {
469 pub fn trait(comptime C: type) bool {471 pub fn trait(comptime C: type) bool {
470 return switch (@typeInfo(C)) {472 return switch (@typeInfo(C)) {
src/main.zig deleted-79
...@@ -1,79 +0,0 @@
1const std = @import("std");
2const pek = @import("pek");
3
4const example_document =
5 \\html[lang="en"](
6 \\ head(
7 \\ meta[charset="utf-8"]
8 \\ title("Pek Example")
9 \\ meta[name="viewport" content="width=device-width,initial-scale=1"]
10 \\ meta[http-equiv="X-UA-Compatible" content="IE=edge"]
11 \\ )
12 \\ body(
13 \\ h1("Pek Example")
14 \\ hr
15 \\ p("This is an example HTML document written in "a[href="https://github.com/nektro/zig-pek"]("Pek")".")
16 \\ p("Pek is written by "{author}".")
17 \\ p("Her favorite plant is the "{favorite.flower})
18 \\ p("Hello, 世界")
19 \\ p("The most populous US cities are:")
20 \\ ul(
21 \\ {#each top_cities}
22 \\ li({this.name}", "{this.state.code})
23 \\ /each/
24 \\ )
25 \\ p("Spooky text: "{spooky})
26 \\
27 \\ {#if am_i_a_girl}
28 \\ p("#1")
29 \\ /if/
30 \\
31 \\ {#ifnot is_it_my_birthday}
32 \\ p("#2")
33 \\ /ifnot/
34 \\
35 \\ {#ifequal top_cities.len best_rating}
36 \\ p("#3")
37 \\ /ifequal/
38 \\
39 \\ {#ifnotequal favorite.color sky}
40 \\ p("#4")
41 \\ /ifnotequal/
42 \\ )
43 \\)
44;
45
46const S = struct {
47 name: []const u8,
48 state: struct {
49 code: []const u8,
50 },
51};
52
53pub fn main() !void {
54 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
55 const alloc = gpa.allocator();
56
57 var name: []const u8 = "Meghan D";
58 const doc = comptime pek.parse(example_document);
59 try pek.compile(@This(), alloc, std.io.getStdOut().writer(), doc, .{
60 .author = name,
61 .favorite = .{
62 .flower = "Sunflower",
63 .program_lang = "Zig",
64 .color = "Pink",
65 },
66 .top_cities = &[_]S{
67 .{ .name = "New York", .state = .{ .code = "NY" } },
68 .{ .name = "Los Angeles", .state = .{ .code = "CA" } },
69 .{ .name = "Chicago", .state = .{ .code = "IL" } },
70 .{ .name = "Houston", .state = .{ .code = "TX" } },
71 .{ .name = "Phoenix", .state = .{ .code = "AZ" } },
72 },
73 .spooky = "<strong>I better not be in bold.</strong>",
74 .am_i_a_girl = true,
75 .sky = "Blue",
76 .best_rating = @as(usize, 5),
77 .is_it_my_birthday = false,
78 });
79}
test.zig created+867
...@@ -0,0 +1,867 @@
1const std = @import("std");
2const pek = @import("pek");
3const expect = @import("expect").expect;
4
5test {
6 std.testing.refAllDeclsRecursive(pek);
7}
8
9test "document" {
10 const alloc = std.testing.allocator;
11 var builder = std.ArrayList(u8).init(alloc);
12 defer builder.deinit();
13 const doc = comptime pek.parse(
14 \\html[lang="en"](
15 \\ head(
16 \\ title("Pek Example")
17 \\ meta[charset="UTF-8"]
18 \\ meta[name="viewport" content="width=device-width,initial-scale=1"]
19 \\ )
20 \\ body(
21 \\ h1("Pek Example")
22 \\ hr
23 \\ p("This is an example HTML document written in "a[href="https://github.com/nektro/zig-pek"]("Pek")".")
24 \\ )
25 \\)
26 );
27 try pek.compile(
28 @This(),
29 alloc,
30 builder.writer(),
31 doc,
32 .{},
33 );
34 try expect(builder.items).toEqualString(
35 \\<!DOCTYPE html>
36 \\<html lang="en">
37 \\ <head>
38 \\ <title>Pek Example</title>
39 \\ <meta charset="UTF-8" />
40 \\ <meta name="viewport" content="width=device-width,initial-scale=1" />
41 \\ </head>
42 \\ <body>
43 \\ <h1>Pek Example</h1>
44 \\ <hr />
45 \\ <p>This is an example HTML document written in <a href="https://github.com/nektro/zig-pek">Pek</a>.</p>
46 \\ </body>
47 \\</html>
48 \\
49 );
50}
51
52test "fragment" {
53 const alloc = std.testing.allocator;
54 var builder = std.ArrayList(u8).init(alloc);
55 defer builder.deinit();
56 const doc = comptime pek.parse(
57 \\body(
58 \\ h1("Pek Example")
59 \\ hr
60 \\ p("This is an example HTML document written in "a[href="https://github.com/nektro/zig-pek"]("Pek")".")
61 \\)
62 );
63 try pek.compileInner(
64 alloc,
65 builder.writer(),
66 doc,
67 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
68 .{},
69 );
70 try expect(builder.items).toEqualString(
71 \\<body>
72 \\ <h1>Pek Example</h1>
73 \\ <hr />
74 \\ <p>This is an example HTML document written in <a href="https://github.com/nektro/zig-pek">Pek</a>.</p>
75 \\</body>
76 \\
77 );
78}
79
80test "if: basic" {
81 const alloc = std.testing.allocator;
82 var builder = std.ArrayList(u8).init(alloc);
83 defer builder.deinit();
84 const doc = comptime pek.parse(
85 \\body(
86 \\ h1("Pek Example")
87 \\ hr
88 \\ {#if foo}
89 \\ p("This is an example HTML document written in "a[href="https://github.com/nektro/zig-pek"]("Pek")".")
90 \\ /if/
91 \\ {#if bar}
92 \\ p("This will show up because "code("bar")" is true instead.")
93 \\ /if/
94 \\)
95 );
96 try pek.compileInner(
97 alloc,
98 builder.writer(),
99 doc,
100 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
101 .{ .foo = false, .bar = true },
102 );
103 try expect(builder.items).toEqualString(
104 \\<body>
105 \\ <h1>Pek Example</h1>
106 \\ <hr />
107 \\ <p>This will show up because <code>bar</code> is true instead.</p>
108 \\</body>
109 \\
110 );
111}
112
113// if else
114test "if else + field access" {
115 const alloc = std.testing.allocator;
116 var builder = std.ArrayList(u8).init(alloc);
117 defer builder.deinit();
118 const doc = comptime pek.parse(
119 \\body(
120 \\ {#if bar.qux}
121 \\ p("This will show up because "code("bar")" is true.")
122 \\ <else>
123 \\ p("This will show up because "code("bar")" is false.")
124 \\ /if/
125 \\)
126 );
127 try pek.compileInner(
128 alloc,
129 builder.writer(),
130 doc,
131 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
132 .{ .bar = .{ .qux = false } },
133 );
134 try expect(builder.items).toEqualString(
135 \\<body>
136 \\ <p>This will show up because <code>bar</code> is false.</p>
137 \\</body>
138 \\
139 );
140}
141
142// ifnot
143test {
144 const alloc = std.testing.allocator;
145 var builder = std.ArrayList(u8).init(alloc);
146 defer builder.deinit();
147 const doc = comptime pek.parse(
148 \\body(
149 \\ {#ifnot bar}
150 \\ p("This will show up because "code("bar")" is false.")
151 \\ <else>
152 \\ p("This will show up because "code("bar")" is true.")
153 \\ /ifnot/
154 \\)
155 );
156 try pek.compileInner(
157 alloc,
158 builder.writer(),
159 doc,
160 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
161 .{ .bar = true },
162 );
163 try expect(builder.items).toEqualString(
164 \\<body>
165 \\ <p>This will show up because <code>bar</code> is true.</p>
166 \\</body>
167 \\
168 );
169}
170test {
171 const alloc = std.testing.allocator;
172 var builder = std.ArrayList(u8).init(alloc);
173 defer builder.deinit();
174 const doc = comptime pek.parse(
175 \\body(
176 \\ {#ifnot bar}
177 \\ p("This will show up because "code("bar")" is false.")
178 \\ <else>
179 \\ p("This will show up because "code("bar")" is true.")
180 \\ /ifnot/
181 \\)
182 );
183 try pek.compileInner(
184 alloc,
185 builder.writer(),
186 doc,
187 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
188 .{ .bar = false },
189 );
190 try expect(builder.items).toEqualString(
191 \\<body>
192 \\ <p>This will show up because <code>bar</code> is false.</p>
193 \\</body>
194 \\
195 );
196}
197
198// ifequal
199test { // bool
200 const alloc = std.testing.allocator;
201 var builder = std.ArrayList(u8).init(alloc);
202 defer builder.deinit();
203 const doc = comptime pek.parse(
204 \\body(
205 \\ {#ifequal bar true}
206 \\ p("This will show up because "code("bar")" is true.")
207 \\ <else>
208 \\ p("This will show up because "code("bar")" is false.")
209 \\ /ifequal/
210 \\)
211 );
212 try pek.compileInner(
213 alloc,
214 builder.writer(),
215 doc,
216 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
217 .{ .bar = true },
218 );
219 try expect(builder.items).toEqualString(
220 \\<body>
221 \\ <p>This will show up because <code>bar</code> is true.</p>
222 \\</body>
223 \\
224 );
225}
226test { // bool false
227 const alloc = std.testing.allocator;
228 var builder = std.ArrayList(u8).init(alloc);
229 defer builder.deinit();
230 const doc = comptime pek.parse(
231 \\body(
232 \\ {#ifequal bar true}
233 \\ p("This will show up because "code("bar")" is true.")
234 \\ <else>
235 \\ p("This will show up because "code("bar")" is false.")
236 \\ /ifequal/
237 \\)
238 );
239 try pek.compileInner(
240 alloc,
241 builder.writer(),
242 doc,
243 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
244 .{ .bar = false },
245 );
246 try expect(builder.items).toEqualString(
247 \\<body>
248 \\ <p>This will show up because <code>bar</code> is false.</p>
249 \\</body>
250 \\
251 );
252}
253test { // string
254 const alloc = std.testing.allocator;
255 var builder = std.ArrayList(u8).init(alloc);
256 defer builder.deinit();
257 const doc = comptime pek.parse(
258 \\body(
259 \\ {#ifequal bar "foo"}
260 \\ p("This will show up because "code("bar")" is 'foo'.")
261 \\ <else>
262 \\ p("This will show up because "code("bar")" is 'qux'.")
263 \\ /ifequal/
264 \\)
265 );
266 try pek.compileInner(
267 alloc,
268 builder.writer(),
269 doc,
270 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
271 .{ .bar = @as([]const u8, "foo") },
272 );
273 try expect(builder.items).toEqualString(
274 \\<body>
275 \\ <p>This will show up because <code>bar</code> is &apos;foo&apos;.</p>
276 \\</body>
277 \\
278 );
279}
280test { // string false
281 const alloc = std.testing.allocator;
282 var builder = std.ArrayList(u8).init(alloc);
283 defer builder.deinit();
284 const doc = comptime pek.parse(
285 \\body(
286 \\ {#ifequal bar "foo"}
287 \\ p("This will show up because "code("bar")" is 'foo'.")
288 \\ <else>
289 \\ p("This will show up because "code("bar")" is 'qux'.")
290 \\ /ifequal/
291 \\)
292 );
293 try pek.compileInner(
294 alloc,
295 builder.writer(),
296 doc,
297 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
298 .{ .bar = @as([]const u8, "qux") },
299 );
300 try expect(builder.items).toEqualString(
301 \\<body>
302 \\ <p>This will show up because <code>bar</code> is &apos;qux&apos;.</p>
303 \\</body>
304 \\
305 );
306}
307test { // enum
308 const alloc = std.testing.allocator;
309 var builder = std.ArrayList(u8).init(alloc);
310 defer builder.deinit();
311 const doc = comptime pek.parse(
312 \\body(
313 \\ {#ifequal bar "foo"}
314 \\ p("This will show up because "code("bar")" is .foo.")
315 \\ <else>
316 \\ p("This will show up because "code("bar")" is .qux.")
317 \\ /ifequal/
318 \\)
319 );
320 try pek.compileInner(
321 alloc,
322 builder.writer(),
323 doc,
324 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
325 .{ .bar = @as(enum { foo, bar, qux }, .foo) },
326 );
327 try expect(builder.items).toEqualString(
328 \\<body>
329 \\ <p>This will show up because <code>bar</code> is .foo.</p>
330 \\</body>
331 \\
332 );
333}
334test { // enum false
335 const alloc = std.testing.allocator;
336 var builder = std.ArrayList(u8).init(alloc);
337 defer builder.deinit();
338 const doc = comptime pek.parse(
339 \\body(
340 \\ {#ifequal bar "foo"}
341 \\ p("This will show up because "code("bar")" is .foo.")
342 \\ <else>
343 \\ p("This will show up because "code("bar")" is .qux.")
344 \\ /ifequal/
345 \\)
346 );
347 try pek.compileInner(
348 alloc,
349 builder.writer(),
350 doc,
351 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
352 .{ .bar = @as(enum { foo, bar, qux }, .qux) },
353 );
354 try expect(builder.items).toEqualString(
355 \\<body>
356 \\ <p>This will show up because <code>bar</code> is .qux.</p>
357 \\</body>
358 \\
359 );
360}
361
362// ifnotequal
363test { // bool
364 const alloc = std.testing.allocator;
365 var builder = std.ArrayList(u8).init(alloc);
366 defer builder.deinit();
367 const doc = comptime pek.parse(
368 \\body(
369 \\ {#ifnotequal bar true}
370 \\ p("This will show up because "code("bar")" is false.")
371 \\ <else>
372 \\ p("This will show up because "code("bar")" is true.")
373 \\ /ifnotequal/
374 \\)
375 );
376 try pek.compileInner(
377 alloc,
378 builder.writer(),
379 doc,
380 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
381 .{ .bar = false },
382 );
383 try expect(builder.items).toEqualString(
384 \\<body>
385 \\ <p>This will show up because <code>bar</code> is false.</p>
386 \\</body>
387 \\
388 );
389}
390test { // bool false
391 const alloc = std.testing.allocator;
392 var builder = std.ArrayList(u8).init(alloc);
393 defer builder.deinit();
394 const doc = comptime pek.parse(
395 \\body(
396 \\ {#ifnotequal bar true}
397 \\ p("This will show up because "code("bar")" is false.")
398 \\ <else>
399 \\ p("This will show up because "code("bar")" is true.")
400 \\ /ifnotequal/
401 \\)
402 );
403 try pek.compileInner(
404 alloc,
405 builder.writer(),
406 doc,
407 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
408 .{ .bar = true },
409 );
410 try expect(builder.items).toEqualString(
411 \\<body>
412 \\ <p>This will show up because <code>bar</code> is true.</p>
413 \\</body>
414 \\
415 );
416}
417test { // string
418 const alloc = std.testing.allocator;
419 var builder = std.ArrayList(u8).init(alloc);
420 defer builder.deinit();
421 const doc = comptime pek.parse(
422 \\body(
423 \\ {#ifnotequal bar "foo"}
424 \\ p("This will show up because "code("bar")" is 'qux'.")
425 \\ <else>
426 \\ p("This will show up because "code("bar")" is 'foo'.")
427 \\ /ifnotequal/
428 \\)
429 );
430 try pek.compileInner(
431 alloc,
432 builder.writer(),
433 doc,
434 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
435 .{ .bar = @as([]const u8, "qux") },
436 );
437 try expect(builder.items).toEqualString(
438 \\<body>
439 \\ <p>This will show up because <code>bar</code> is &apos;qux&apos;.</p>
440 \\</body>
441 \\
442 );
443}
444test { // string false
445 const alloc = std.testing.allocator;
446 var builder = std.ArrayList(u8).init(alloc);
447 defer builder.deinit();
448 const doc = comptime pek.parse(
449 \\body(
450 \\ {#ifnotequal bar "foo"}
451 \\ p("This will show up because "code("bar")" is 'qux'.")
452 \\ <else>
453 \\ p("This will show up because "code("bar")" is 'foo'.")
454 \\ /ifnotequal/
455 \\)
456 );
457 try pek.compileInner(
458 alloc,
459 builder.writer(),
460 doc,
461 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
462 .{ .bar = @as([]const u8, "foo") },
463 );
464 try expect(builder.items).toEqualString(
465 \\<body>
466 \\ <p>This will show up because <code>bar</code> is &apos;foo&apos;.</p>
467 \\</body>
468 \\
469 );
470}
471test { // enum
472 const alloc = std.testing.allocator;
473 var builder = std.ArrayList(u8).init(alloc);
474 defer builder.deinit();
475 const doc = comptime pek.parse(
476 \\body(
477 \\ {#ifnotequal bar "foo"}
478 \\ p("This will show up because "code("bar")" is .qux.")
479 \\ <else>
480 \\ p("This will show up because "code("bar")" is .foo.")
481 \\ /ifnotequal/
482 \\)
483 );
484 try pek.compileInner(
485 alloc,
486 builder.writer(),
487 doc,
488 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
489 .{ .bar = @as(enum { foo, bar, qux }, .qux) },
490 );
491 try expect(builder.items).toEqualString(
492 \\<body>
493 \\ <p>This will show up because <code>bar</code> is .qux.</p>
494 \\</body>
495 \\
496 );
497}
498test { // enum false
499 const alloc = std.testing.allocator;
500 var builder = std.ArrayList(u8).init(alloc);
501 defer builder.deinit();
502 const doc = comptime pek.parse(
503 \\body(
504 \\ {#ifnotequal bar "foo"}
505 \\ p("This will show up because "code("bar")" is .qux.")
506 \\ <else>
507 \\ p("This will show up because "code("bar")" is .foo.")
508 \\ /ifnotequal/
509 \\)
510 );
511 try pek.compileInner(
512 alloc,
513 builder.writer(),
514 doc,
515 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
516 .{ .bar = @as(enum { foo, bar, qux }, .foo) },
517 );
518 try expect(builder.items).toEqualString(
519 \\<body>
520 \\ <p>This will show up because <code>bar</code> is .foo.</p>
521 \\</body>
522 \\
523 );
524}
525
526// replacement
527test {
528 const alloc = std.testing.allocator;
529 var builder = std.ArrayList(u8).init(alloc);
530 defer builder.deinit();
531 const doc = comptime pek.parse(
532 \\body(
533 \\ p("This text for this link is "a[href="https://github.com/nektro/zig-pek"]({text})".")
534 \\)
535 );
536 try pek.compileInner(
537 alloc,
538 builder.writer(),
539 doc,
540 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
541 .{ .text = @as([]const u8, "dynamic") },
542 );
543 try expect(builder.items).toEqualString(
544 \\<body>
545 \\ <p>This text for this link is <a href="https://github.com/nektro/zig-pek">dynamic</a>.</p>
546 \\</body>
547 \\
548 );
549}
550test {
551 const alloc = std.testing.allocator;
552 var builder = std.ArrayList(u8).init(alloc);
553 defer builder.deinit();
554 const doc = comptime pek.parse(
555 \\body(
556 \\ p("This url for this link is "a[href=({url})]("dynamic")".")
557 \\)
558 );
559 try pek.compileInner(
560 alloc,
561 builder.writer(),
562 doc,
563 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
564 .{ .url = @as([]const u8, "https://github.com/nektro/zig-pek") },
565 );
566 try expect(builder.items).toEqualString(
567 \\<body>
568 \\ <p>This url for this link is <a href="https:&sol;&sol;github.com&sol;nektro&sol;zig-pek">dynamic</a>.</p>
569 \\</body>
570 \\
571 );
572}
573test {
574 const alloc = std.testing.allocator;
575 var builder = std.ArrayList(u8).init(alloc);
576 defer builder.deinit();
577 const doc = comptime pek.parse(
578 \\body(
579 \\ p("This host for this link is "a[href=("https://"{host}"/nektro/zig-pek")]("dynamic")".")
580 \\)
581 );
582 try pek.compileInner(
583 alloc,
584 builder.writer(),
585 doc,
586 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
587 .{ .host = @as([]const u8, "github.com") },
588 );
589 try expect(builder.items).toEqualString(
590 \\<body>
591 \\ <p>This host for this link is <a href="https:&sol;&sol;github.com&sol;nektro&sol;zig-pek">dynamic</a>.</p>
592 \\</body>
593 \\
594 );
595}
596test {
597 const alloc = std.testing.allocator;
598 var builder = std.ArrayList(u8).init(alloc);
599 defer builder.deinit();
600 const doc = comptime pek.parse(
601 \\body(
602 \\ p("This host for this link is "a[href=("https://"{host}"/nektro/zig-pek")]("dynamic")".")
603 \\)
604 );
605 try pek.compileInner(
606 alloc,
607 builder.writer(),
608 doc,
609 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
610 .{ .host = "github.com".* },
611 );
612 try expect(builder.items).toEqualString(
613 \\<body>
614 \\ <p>This host for this link is <a href="https:&sol;&sol;github.com&sol;nektro&sol;zig-pek">dynamic</a>.</p>
615 \\</body>
616 \\
617 );
618}
619
620// replacement with custom serializer
621test {
622 const S = struct {
623 name: []const u8,
624
625 pub fn format(s: @This(), comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
626 _ = fmt;
627 _ = options;
628 try writer.writeAll(s.name);
629 try writer.writeAll(".com");
630 }
631 };
632 const alloc = std.testing.allocator;
633 var builder = std.ArrayList(u8).init(alloc);
634 defer builder.deinit();
635 const doc = comptime pek.parse(
636 \\body(
637 \\ p("This host for this link is "a[href=("https://"{host}"/nektro/zig-pek")]("dynamic")".")
638 \\)
639 );
640 try pek.compileInner(
641 alloc,
642 builder.writer(),
643 doc,
644 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
645 .{ .host = S{ .name = "github" } },
646 );
647 try expect(builder.items).toEqualString(
648 \\<body>
649 \\ <p>This host for this link is <a href="https:&sol;&sol;github.com&sol;nektro&sol;zig-pek">dynamic</a>.</p>
650 \\</body>
651 \\
652 );
653}
654test {
655 const S = struct {
656 name: []const u8,
657
658 pub fn toString(s: @This(), alloc: std.mem.Allocator) ![]const u8 {
659 return std.fmt.allocPrint(alloc, "{s}.com", .{s.name});
660 }
661 };
662 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
663 defer arena.deinit();
664 const alloc = arena.allocator();
665 var builder = std.ArrayList(u8).init(alloc);
666 defer builder.deinit();
667 const doc = comptime pek.parse(
668 \\body(
669 \\ p("This host for this link is "a[href=("https://"{host}"/nektro/zig-pek")]("dynamic")".")
670 \\)
671 );
672 try pek.compileInner(
673 alloc,
674 builder.writer(),
675 doc,
676 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
677 .{ .host = S{ .name = "github" } },
678 );
679 try expect(builder.items).toEqualString(
680 \\<body>
681 \\ <p>This host for this link is <a href="https:&sol;&sol;github.com&sol;nektro&sol;zig-pek">dynamic</a>.</p>
682 \\</body>
683 \\
684 );
685}
686
687// raw replacement
688test {
689 const alloc = std.testing.allocator;
690 var builder = std.ArrayList(u8).init(alloc);
691 defer builder.deinit();
692 const doc = comptime pek.parse(
693 \\body(
694 \\ p("This url for this link is "a[href=({{url}})]("dynamic but not escaped")".")
695 \\)
696 );
697 try pek.compileInner(
698 alloc,
699 builder.writer(),
700 doc,
701 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
702 .{ .url = "https://github.com/nektro/zig-pek".* },
703 );
704 try expect(builder.items).toEqualString(
705 \\<body>
706 \\ <p>This url for this link is <a href="https://github.com/nektro/zig-pek">dynamic but not escaped</a>.</p>
707 \\</body>
708 \\
709 );
710}
711test {
712 const alloc = std.testing.allocator;
713 var builder = std.ArrayList(u8).init(alloc);
714 defer builder.deinit();
715 const doc = comptime pek.parse(
716 \\body(
717 \\ p("Raw replacements can also "{{foo}}".")
718 \\)
719 );
720 try pek.compileInner(
721 alloc,
722 builder.writer(),
723 doc,
724 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
725 .{ .foo = "contain HTML such as <button>this</button> as an escape hatch when you know content is safe".* },
726 );
727 try expect(builder.items).toEqualString(
728 \\<body>
729 \\ <p>Raw replacements can also contain HTML such as <button>this</button> as an escape hatch when you know content is safe.</p>
730 \\</body>
731 \\
732 );
733}
734
735// custom function
736test {
737 const C = struct {
738 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 {
739 const tmpl = comptime pek.parse(
740 \\label(
741 \\ div({label})
742 \\ textarea[type="text" required="" name=({name}) placeholder=({placeholder})]({value})
743 \\)
744 );
745 try pek.compileInner(alloc, writer, tmpl, opts, .{
746 .name = args[0],
747 .label = args[1],
748 .value = args[2],
749 .placeholder = args[3],
750 });
751 }
752 };
753 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
754 defer arena.deinit();
755 const alloc = arena.allocator();
756 var builder = std.ArrayList(u8).init(alloc);
757 defer builder.deinit();
758 var person: struct { name: []const u8 } = .{ .name = "meghan" };
759 _ = &person;
760 const doc = comptime pek.parse(
761 \\body(
762 \\ form(
763 \\ {##input_text "name" "Name" prev.name ""}
764 \\ button("Submit ▶")
765 \\ )
766 \\)
767 );
768 try pek.compileInner(
769 alloc,
770 builder.writer(),
771 doc,
772 .{ .Ctx = C, .indent = 0, .flag1 = false },
773 .{ .prev = person },
774 );
775 try expect(builder.items).toEqualString(
776 \\<body>
777 \\ <form><label>
778 \\ <div>Name</div>
779 \\ <textarea type="text" required="" name="name" placeholder="">meghan</textarea>
780 \\ </label>
781 \\<button>Submit ▶</button></form>
782 \\</body>
783 \\
784 );
785}
786
787// custom bool function
788test {
789 const S = struct {
790 id: u64,
791 };
792 const C = struct {
793 pub fn pek_is_admin(alloc: std.mem.Allocator, user: S) !bool {
794 _ = alloc;
795 return user.id < 10;
796 }
797 };
798 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
799 defer arena.deinit();
800 const alloc = arena.allocator();
801 var builder = std.ArrayList(u8).init(alloc);
802 defer builder.deinit();
803 var person: S = .{ .id = 0 };
804 _ = &person;
805 const doc = comptime pek.parse(
806 \\body(
807 \\ {#if#is_admin user}
808 \\ p("only an admin user can see this")
809 \\ <else>
810 \\ p("unauthorized users will see this")
811 \\ /if/
812 \\)
813 );
814 try pek.compileInner(
815 alloc,
816 builder.writer(),
817 doc,
818 .{ .Ctx = C, .indent = 0, .flag1 = false },
819 .{ .user = person },
820 );
821 try expect(builder.items).toEqualString(
822 \\<body>
823 \\ <p>only an admin user can see this</p>
824 \\</body>
825 \\
826 );
827}
828test {
829 const S = struct {
830 id: u64,
831 };
832 const C = struct {
833 pub fn pek_is_admin(alloc: std.mem.Allocator, user: S) !bool {
834 _ = alloc;
835 return user.id < 10;
836 }
837 };
838 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
839 defer arena.deinit();
840 const alloc = arena.allocator();
841 var builder = std.ArrayList(u8).init(alloc);
842 defer builder.deinit();
843 var person: S = .{ .id = 25 };
844 _ = &person;
845 const doc = comptime pek.parse(
846 \\body(
847 \\ {#if#is_admin user}
848 \\ p("only an admin user can see this")
849 \\ <else>
850 \\ p("unauthorized users will see this")
851 \\ /if/
852 \\)
853 );
854 try pek.compileInner(
855 alloc,
856 builder.writer(),
857 doc,
858 .{ .Ctx = C, .indent = 0, .flag1 = false },
859 .{ .user = person },
860 );
861 try expect(builder.items).toEqualString(
862 \\<body>
863 \\ <p>unauthorized users will see this</p>
864 \\</body>
865 \\
866 );
867}
zig.mod+4-1
...@@ -4,6 +4,9 @@ main: src/lib.zig...@@ -4,6 +4,9 @@ main: src/lib.zig
4license: MPL-2.04license: MPL-2.0
5description: An HTML preprocessor with a builtin template engine.5description: An HTML preprocessor with a builtin template engine.
6dependencies:6dependencies:
7 - src: git https://github.com/kivikakk/htmlentities.zig7 - src: git https://github.com/kivikakk/htmlentities.zig commit-bd5d569a245c7c8e83812eadcb5761b7ba76ef04
8 - src: git https://github.com/nektro/zig-extras8 - src: git https://github.com/nektro/zig-extras
9 - src: git https://github.com/nektro/zig-tracer9 - src: git https://github.com/nektro/zig-tracer
10
11root_dependencies:
12 - src: git https://github.com/nektro/zig-expect