diff --git a/src/template/lib.zig b/src/template/lib.zig index a77d3f1..8fbcbe1 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -3,7 +3,7 @@ const std = @import("std"); pub fn main() !void { try execute( std.io.getStdOut().writer(), - .{ .test_tmpl = "{.x} {@context_foo}" }, + .{ .test_tmpl = "{.x} {%context_foo}" }, @embedFile("./test.tmp.html"), .{ .community = .{ .name = "abcd" }, @@ -15,6 +15,9 @@ pub fn main() !void { .bar = .{ .x = "x" }, .qux = false, .quxx = true, + .maybe_foo = @as(?[]const u8, "foo"), + .maybe_bar = @as(?[]const u8, null), + .x = "y", }, .{ .context_foo = "foo", @@ -83,9 +86,33 @@ fn executeStatement( .@"if" => |if_stmt| { const condition = evaluateExpression(if_stmt.header.condition, args, captures, context); const subtemplate = if_stmt.subtemplate; - if (condition) { - try executeTemplate(writer, templates, subtemplate, args, captures, context); + var was_true: bool = false; + if (if_stmt.header.capture) |capture| { + if (condition) |val| { + was_true = true; + try executeTemplate( + writer, + templates, + subtemplate, + args, + addCapture(captures, capture, val), + context, + ); + } } else { + if (condition) { + was_true = true; + try executeTemplate( + writer, + templates, + subtemplate, + args, + captures, + context, + ); + } + } + if (!was_true) { if (if_stmt.else_branch) |branch| switch (branch) { .@"else" => |subtmpl| try executeTemplate(writer, templates, subtmpl, args, captures, context), .elif => |elif| try executeStatement(writer, templates, .{ .@"if" = elif.* }, args, captures, context), @@ -158,6 +185,7 @@ fn evaluateExpression( } fn AddCapture(comptime Root: type, comptime name: []const u8, comptime Val: type) type { + if (std.mem.eql(u8, name, "_")) return Root; var fields = std.meta.fields(Root) ++ [_]std.builtin.Type.StructField{.{ .name = name, .field_type = Val, @@ -175,6 +203,7 @@ fn AddCapture(comptime Root: type, comptime name: []const u8, comptime Val: type } fn addCapture(root: anytype, comptime name: []const u8, val: anytype) AddCapture(@TypeOf(root), name, @TypeOf(val)) { + if (comptime std.mem.eql(u8, name, "_")) return root; var result = std.mem.zeroInit(AddCapture(@TypeOf(root), name, @TypeOf(val)), root); @field(result, name) = val; return result; @@ -359,6 +388,8 @@ fn parseTemplateTokens(comptime tokens: ControlTokenIter) []const TemplateToken .slash => items = items ++ [_]TemplateToken{.{ .text = "/" }}, .equals => items = items ++ [_]TemplateToken{.{ .text = "=" }}, .at => items = items ++ [_]TemplateToken{.{ .text = "@" }}, + .comma => items = items ++ [_]TemplateToken{.{ .text = "," }}, + .percent => items = items ++ [_]TemplateToken{.{ .text = "%" }}, }; return items; @@ -381,7 +412,7 @@ fn parseExpression(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIt iter = names.new_iter; break .{ .capture_deref = names.item }; }, - .at => { + .percent => { const names = parseDeref(iter); iter = names.new_iter; break .{ .context_deref = names.item }; @@ -455,7 +486,7 @@ fn parseControlBlock(comptime tokens: ControlTokenIter) ParseResult(ControlToken .@"if" => break .{ .end_if = {} }, } }, - .period, .dollar, .at => { + .period, .dollar, .percent => { iter.putBack(token); const expr = parseExpression(iter); iter = expr.new_iter; @@ -551,15 +582,61 @@ fn parseForHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIte } } +fn tryParseCapture(comptime tokens: ControlTokenIter) ?ParseResult(ControlTokenIter, []const []const u8) { + comptime { + var iter = tokens; + + iter = skipWhitespace(iter); + if ((iter.next() orelse return null) != .pipe) return null; + var captures: []const []const u8 = &.{}; + while (true) { + iter = skipWhitespace(iter); + if ((iter.next() orelse return null) != .dollar) return null; + iter = skipWhitespace(iter); + const name = switch (iter.next() orelse return null) { + .text => |text| text, + else => return null, + }; + iter = skipWhitespace(iter); + captures = captures ++ &[_][]const u8{name}; + + switch (iter.next() orelse return null) { + .pipe => break, + .comma => {}, + else => return null, + } + } + + return .{ + .new_iter = iter, + .item = captures, + }; + } +} + fn parseIfHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, IfHeader) { comptime { const condition = parseExpression(tokens); var iter = condition.new_iter; + const captures = tryParseCapture(iter); + if (captures) |cap| { + if (cap.item.len == 1) { + return .{ + .new_iter = cap.new_iter, + .item = IfHeader{ + .condition = condition.item, + .capture = cap.item[0], + }, + }; + } else @compileError("Only one capture allowed for if statements"); + } + return .{ .new_iter = iter, .item = .{ .condition = condition.item, + .capture = null, }, }; } @@ -582,7 +659,7 @@ fn parseDeref(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, [ if (wants != .period) @compileError("Unexpected token \".\""); wants = .text; }, - else => if (wants == .period) return .{ + else => if (wants == .period or fields.len == 0) return .{ .new_iter = iter, .item = fields, } else @compileError("Unexpected token"), @@ -657,6 +734,7 @@ const CallTemplate = struct { const IfHeader = struct { condition: Expression, + capture: ?[]const u8, }; const Statement = union(enum) { @@ -707,6 +785,8 @@ const ControlToken = union(enum) { slash: void, equals: void, at: void, + comma: void, + percent: void, }; const ControlTokenIter = struct { @@ -737,6 +817,8 @@ const ControlTokenIter = struct { '/' => return .{ .slash = {} }, '=' => return .{ .equals = {} }, '@' => return .{ .at = {} }, + ',' => return .{ .comma = {} }, + '%' => return .{ .percent = {} }, ' ', '\t', '\n', '\r' => { var idx: usize = 0; while (idx < remaining.len and std.mem.indexOfScalar(u8, " \t\n\r", remaining[idx]) != null) : (idx += 1) {} diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index 0922607..29930ed 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -22,9 +22,14 @@ neither {=/if} - + {#if .maybe_foo |$v|}{$v}{#else}null{/if} + {#if .maybe_bar |$v|}{$v}{#else}null{/if} + {#if .maybe_foo |$_|}abcd{#else}null{/if} - {@context_foo} + + + + {%context_foo}