authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2025-06-21 18:53:16 -07:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2025-06-21 18:53:16 -07:00
log656e58d4447aec777ce61d1b971b16d00200ebc6
tree13733a3b6fc2f64fbe360253129cc8b105e99586
parent645a7dede162499ce58e9278d2070f69b7045d84

add two element multi-{#each


3 files changed, 79 insertions(+), 18 deletions(-)

src/lib.zig+38-18
......@@ -94,19 +94,12 @@ inline fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.V
9494 },
9595 .replacement => |repl| {
9696 const v = repl.arms;
97 const x = if (comptime std.mem.eql(u8, v[0], "this")) search(v[1..], data) else search(v, ctx);
97 const x = search(v, ctx);
9898 const TO = @TypeOf(x);
9999 const TI = @typeInfo(TO);
100100
101101 if (comptime extras.isZigString(TO)) {
102 if (repl.raw) {
103 try writer.writeAll(x);
104 return;
105 }
106 const s = std.mem.trim(u8, x, "\n");
107 if (opts.escaped) try writeEscaped(s, writer);
108 if (!opts.escaped) try writer.writeAll(s);
109 return;
102 return writeReplacementString(writer, repl.raw, opts.escaped, x);
110103 }
111104 if (TI == .int or TI == .float or TI == .comptime_int or TI == .comptime_float) {
112105 try writer.print("{d}", .{x});
......@@ -145,8 +138,25 @@ inline fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.V
145138 const TI = @typeInfo(T);
146139 switch (v.name) {
147140 .each => {
148 comptime assertEqual(v.args.len, 1);
149 for (x) |item| try do(alloc, writer, body, item, ctx, opts);
141 switch (v.args.len) {
142 1 => {
143 for (x) |item| {
144 if (@hasField(@TypeOf(ctx), "this")) {
145 // handle nested loops, should be temporary
146 try do(alloc, writer, body, null, extras.join(.{ extras.omit(ctx, "this"), .{ .this = item } }), opts);
147 } else {
148 try do(alloc, writer, body, null, extras.join(.{ ctx, .{ .this = item } }), opts);
149 }
150 }
151 },
152 2 => {
153 const y = try resolveArg(v.args[1], alloc, data, ctx, opts);
154 for (x, y) |item, jtem| {
155 try do(alloc, writer, body, null, extras.join(.{ ctx, .{ .this = item, .that = jtem } }), opts);
156 }
157 },
158 else => @compileError(std.fmt.comptimePrint("#each block cannot have {d} iterators", .{v.args.len})),
159 }
150160 },
151161 .@"if" => {
152162 if (v.func) |n| {
......@@ -255,9 +265,8 @@ inline fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.V
255265 const field_name = comptime std.fmt.comptimePrint("{d}", .{i + 2});
256266 @field(args, field_name) = try resolveArg(arg, alloc, data, ctx, opts);
257267 }
258 const repvalue = astgen.Value{ .replacement = .{ .arms = &.{"this"}, .raw = v.raw } };
259268 try @call(.auto, func, args);
260 try do(alloc, writer, repvalue, try list.toOwnedSlice(), ctx, opts);
269 try writeReplacementString(writer, v.raw, opts.escaped, try list.toOwnedSlice());
261270 return;
262271 }
263272 if (v.raw and @hasDecl(opts.Ctx, "pek__" ++ v.name)) {
......@@ -279,9 +288,8 @@ inline fn do(alloc: std.mem.Allocator, writer: anytype, comptime value: astgen.V
279288 const field_name = comptime std.fmt.comptimePrint("{d}", .{i});
280289 @field(args[3], field_name) = try resolveArg(arg, alloc, data, ctx, opts);
281290 }
282 const repvalue = astgen.Value{ .replacement = .{ .arms = &.{"this"}, .raw = v.raw } };
283291 try @call(.auto, func, args);
284 try do(alloc, writer, repvalue, try list.toOwnedSlice(), ctx, opts);
292 try writeReplacementString(writer, v.raw, opts.escaped, list.items);
285293 return;
286294 }
287295 if (v.raw and @hasDecl(opts.Ctx, "pek_" ++ v.name)) {
......@@ -306,7 +314,7 @@ pub const DoOptions = struct {
306314fn resolveArg(comptime arg: astgen.Arg, alloc: std.mem.Allocator, data: anytype, ctx: anytype, comptime opts: DoOptions) !ResolveArg(arg, @TypeOf(data), @TypeOf(ctx)) {
307315 return switch (arg) {
308316 .plain => |av| av[1 .. av.len - 1],
309 .lookup => |av| if (comptime std.mem.eql(u8, av[0], "this")) search(av[1..], data) else search(av, ctx),
317 .lookup => |av| search(av, ctx),
310318 .int => |av| av,
311319 .value => |av| {
312320 var list = std.ArrayList(u8).init(alloc);
......@@ -319,9 +327,10 @@ fn resolveArg(comptime arg: astgen.Arg, alloc: std.mem.Allocator, data: anytype,
319327}
320328
321329fn ResolveArg(comptime arg: astgen.Arg, comptime This: type, comptime Ctx: type) type {
330 _ = This;
322331 return switch (arg) {
323332 .plain => string,
324 .lookup => |av| if (comptime std.mem.eql(u8, av[0], "this")) FieldSearch(This, av[1..]) else FieldSearch(Ctx, av),
333 .lookup => |av| FieldSearch(Ctx, av),
325334 .int => u64,
326335 .value => string,
327336 };
......@@ -358,7 +367,8 @@ fn Field(comptime T: type, comptime field_name: string) type {
358367 return fld.type;
359368 }
360369 }
361 @compileError(std.fmt.comptimePrint("pek: unknown field {s} on type {s}", .{ field_name, @typeName(T) }));
370 @compileLog(std.meta.fieldNames(T));
371 _ = @field(@as(T, undefined), field_name);
362372}
363373
364374pub fn writeEscaped(s: string, writer: anytype) !void {
......@@ -374,6 +384,16 @@ pub fn writeEscaped(s: string, writer: anytype) !void {
374384 }
375385}
376386
387fn writeReplacementString(writer: anytype, raw: bool, escaped: bool, bytes: []const u8) !void {
388 if (raw) {
389 try writer.writeAll(bytes);
390 return;
391 }
392 const s = std.mem.trim(u8, bytes, "\n");
393 if (escaped) try writeEscaped(s, writer);
394 if (!escaped) try writer.writeAll(s);
395}
396
377397fn nextCodepointSliceLossy(it: *std.unicode.Utf8Iterator) ?[]const u8 {
378398 if (it.i >= it.bytes.len) return null;
379399 const cp_len = std.unicode.utf8ByteSequenceLength(it.bytes[it.i]) catch {
test.zig+40
......@@ -1,6 +1,7 @@
11const std = @import("std");
22const pek = @import("pek");
33const expect = @import("expect").expect;
4const extras = @import("extras");
45
56test {
67 std.testing.refAllDeclsRecursive(pek);
......@@ -904,3 +905,42 @@ test {
904905 \\
905906 );
906907}
908
909// multi-each
910test {
911 const alloc = std.testing.allocator;
912 var builder = std.ArrayList(u8).init(alloc);
913 defer builder.deinit();
914 const S = struct {
915 abbr: []const u8,
916 name: []const u8,
917 index: u8,
918 };
919 const states = extras.StaticMultiList(S).initComptime(&[_]S{
920 .{ .abbr = "MA", .name = "Massachusetts", .index = 0 },
921 .{ .abbr = "OR", .name = "Oregon", .index = 0 },
922 .{ .abbr = "CA", .name = "California", .index = 0 },
923 });
924 const doc = comptime pek.parse(
925 \\body(
926 \\ {#each abbrs names}
927 \\ p("The US state '"{that}"' can be shortened to '"{this}"'.")
928 \\ /each/
929 \\)
930 );
931 try pek.compileInner(
932 alloc,
933 builder.writer(),
934 doc,
935 .{ .Ctx = @This(), .indent = 0, .flag1 = false },
936 .{ .abbrs = states.items.abbr, .names = states.items.name },
937 );
938 try expect(builder.items).toEqualString(
939 \\<body>
940 \\ <p>The US state 'Massachusetts' can be shortened to 'MA'.</p>
941 \\ <p>The US state 'Oregon' can be shortened to 'OR'.</p>
942 \\ <p>The US state 'California' can be shortened to 'CA'.</p>
943 \\</body>
944 \\
945 );
946}
zig.mod+1
......@@ -10,3 +10,4 @@ dependencies:
1010
1111root_dependencies:
1212 - src: git https://github.com/nektro/zig-expect
13 - src: git https://github.com/nektro/zig-extras