From 54748b4c07c56d2ff2f6d4f9541d27042a0ef72f Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Wed, 14 Dec 2022 01:29:00 -0800 Subject: [PATCH 1/7] Expression refactor --- src/template/lib.zig | 227 ++++++++++++++++--------------------- src/template/test.tmp.html | 2 +- 2 files changed, 100 insertions(+), 129 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index 77460bd..26783c8 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -211,34 +211,7 @@ fn print(writer: anytype, arg: anytype) !void { try std.fmt.format(writer, "{}", .{arg}); } -const DerefError = error{NullOptional}; -fn Deref(comptime T: type, comptime names: []const DerefDecl) type { - if (names.len == 0) return T; - - // Compiler segfaults when I use std.meta to get this info so we search it manually - const F = switch (names[0]) { - .field => |name| blk: { - const field = for (@typeInfo(T).Struct.fields) |f| { - if (std.mem.eql(u8, f.name, name)) break f; - } else @compileError("Unknown field " ++ name ++ " in type " ++ @typeName(T)); - break :blk field.field_type; - }, - .optional_unwrap => std.meta.Child(T), - }; - - return Deref(F, names[1..]); -} - -fn deref(arg: anytype, comptime names: []const DerefDecl) DerefError!Deref(@TypeOf(arg), names) { - if (names.len == 0) return arg; - - switch (names[0]) { - .field => |name| return deref(@field(arg, name), names[1..]), - .optional_unwrap => return arg orelse error.NullOptional, - } -} - -const ExpressionError = error{IndexOutOfBounds} || DerefError; +const ExpressionError = error{ IndexOutOfBounds, NullOptional }; fn EvaluateExpression( comptime expression: Expression, @@ -247,14 +220,21 @@ fn EvaluateExpression( comptime Context: type, ) type { return switch (expression) { - .arg_deref => |names| Deref(Args, names), - .capture_deref => |names| Deref(Captures, names), - .context_deref => |names| Deref(Context, names), + .args => Args, + .captures => Captures, + .context => Context, + .deref => |expr| { + const T = EvaluateExpression(expr.container, Args, Captures, Context); + for (@typeInfo(T).Struct.fields) |f| { + if (std.mem.eql(u8, expr.field, f.name)) return f.field_type; + } + }, .equals => bool, .builtin => |call| switch (call.*) { .isTag => bool, .slice => |sl| []const std.meta.Elem(EvaluateExpression(sl.iterable, Args, Captures, Context)), }, + .optional_unwrap => |expr| std.meta.Child(EvaluateExpression(expr, Args, Captures, Context)), }; } @@ -265,9 +245,15 @@ fn evaluateExpression( context: anytype, ) ExpressionError!EvaluateExpression(expression, @TypeOf(args), @TypeOf(captures), @TypeOf(context)) { return switch (expression) { - .arg_deref => |names| try deref(args, names), - .capture_deref => |names| try deref(captures, names), - .context_deref => |names| try deref(context, names), + .args => args, + .captures => captures, + .context => context, + .deref => |expr| { + return @field( + try evaluateExpression(expr.container, args, captures, context), + expr.field, + ); + }, .equals => |eql| { const lhs = try evaluateExpression(eql.lhs, args, captures, context); const rhs = try evaluateExpression(eql.rhs, args, captures, context); @@ -293,6 +279,7 @@ fn evaluateExpression( return iterable[start..end]; }, }, + .optional_unwrap => unreachable, }; } @@ -566,54 +553,66 @@ fn parseExpression(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIt var last_valid_iter: ?ControlTokenIter = null; var expr: ?Expression = null; - while (iter.next()) |token| switch (token) { - .whitespace => {}, - .period => { - const names = parseDeref(iter); - iter = names.new_iter; - if (expr != null) break; - expr = .{ .arg_deref = names.item }; - last_valid_iter = iter; - }, - .dollar => { - const names = parseDeref(iter); - iter = names.new_iter; - if (expr != null) break; - expr = .{ .capture_deref = names.item }; - last_valid_iter = iter; - }, - .percent => { - const names = parseDeref(iter); - iter = names.new_iter; - if (expr != null) break; - expr = .{ .context_deref = names.item }; - last_valid_iter = iter; - }, - .equals => { - const next = iter.next() orelse break; - if (next == .equals) { - const lhs = expr orelse break; - const rhs = parseExpression(iter); - iter = rhs.new_iter; - - expr = .{ - .equals = &.{ - .lhs = lhs, - .rhs = rhs.item, - }, - }; + while (iter.next()) |token| { + switch (token) { + .whitespace => {}, + .period => { + iter = skipWhitespace(iter); + if (expr == null) { + expr = .{ .args = {} }; + if (iter.peek()) |n| if (n == .text) iter.putBack(.{ .period = {} }); + } else { + const field = iter.next(); + expectToken(field, .text); + expr = .{ + .deref = &.{ + .container = expr.?, + .field = field.?.text, + }, + }; + } last_valid_iter = iter; - } else break; - }, - .at => { - if (expr != null) break; - const builtin = parseBuiltin(iter); - iter = builtin.new_iter; - expr = .{ .builtin = &builtin.item }; - last_valid_iter = iter; - }, - else => break, - }; + }, + .dollar => { + if (expr != null) break; + iter = skipWhitespace(iter); + expr = .{ .captures = {} }; + if (iter.peek()) |n| if (n == .text) iter.putBack(.{ .period = {} }); + last_valid_iter = iter; + }, + .percent => { + if (expr != null) break; + iter = skipWhitespace(iter); + expr = .{ .context = {} }; + if (iter.peek()) |n| if (n == .text) iter.putBack(.{ .period = {} }); + last_valid_iter = iter; + }, + .equals => { + const next = iter.next() orelse break; + if (next == .equals) { + const lhs = expr orelse break; + const rhs = parseExpression(iter); + iter = rhs.new_iter; + + expr = .{ + .equals = &.{ + .lhs = lhs, + .rhs = rhs.item, + }, + }; + last_valid_iter = iter; + } else break; + }, + .at => { + if (expr != null) break; + const builtin = parseBuiltin(iter); + iter = builtin.new_iter; + expr = .{ .builtin = &builtin.item }; + last_valid_iter = iter; + }, + else => break, + } + } return .{ .new_iter = last_valid_iter orelse @compileError("Invalid Expression"), @@ -972,40 +971,6 @@ fn parseSwitchHeader(comptime tokens: ControlTokenIter) ParseResult(ControlToken } } -fn parseDeref(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, []const DerefDecl) { - comptime { - var iter = tokens; - var fields: []const DerefDecl = &.{}; - var wants = .text; - while (iter.peek()) |token| { - switch (token) { - .whitespace => {}, - .text => |text| { - if (wants == .period) break; - fields = fields ++ [1]DerefDecl{.{ .field = text }}; - wants = .period; - }, - .period => { - if (wants != .period) @compileError("Unexpected token \".\""); - wants = .text; - }, - .question_mark => { - if (wants == .period) break; - fields = fields ++ [1]DerefDecl{.{ .optional_unwrap = {} }}; - wants = .period; - }, - else => if (wants == .period or fields.len == 0) break else @compileError("Unexpected token"), - } - _ = iter.next(); - } - - return .{ - .new_iter = iter, - .item = fields, - }; - } -} - fn parseCallTemplate(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, CallTemplate) { comptime { var iter = tokens; @@ -1076,9 +1041,9 @@ const TemplateItem = union(enum) { statement: Statement, }; -const DerefDecl = union(enum) { +const DerefExpr = struct { + container: Expression, field: []const u8, - optional_unwrap: void, }; const EqualsExpr = struct { @@ -1087,11 +1052,13 @@ const EqualsExpr = struct { }; const Expression = union(enum) { - arg_deref: []const DerefDecl, - capture_deref: []const DerefDecl, - context_deref: []const DerefDecl, + args: void, + captures: void, + context: void, + deref: *const DerefExpr, equals: *const EqualsExpr, builtin: *const BuiltinCall, + optional_unwrap: *const Expression, }; const For = struct { @@ -1235,14 +1202,17 @@ const ControlToken = union(enum) { const ControlTokenIter = struct { start: usize = 0, text: []const u8, - peeked_token: ?ControlToken = null, + peeked_tokens: [2]?ControlToken = [2]?ControlToken{ null, null }, + peeked_token_count: usize = 0, row: usize = 0, fn next(self: *ControlTokenIter) ?ControlToken { - if (self.peeked_token) |token| { - self.peeked_token = null; - return token; + if (self.peeked_token_count != 0) { + const t = self.peeked_tokens[self.peeked_token_count - 1].?; + self.peeked_tokens[self.peeked_token_count - 1] = null; + self.peeked_token_count -= 1; + return t; } const remaining = self.text[self.start..]; @@ -1287,13 +1257,14 @@ const ControlTokenIter = struct { fn peek(self: *ControlTokenIter) ?ControlToken { const token = self.next(); - self.peeked_token = token; + if (token) |t| self.putBack(t); return token; } fn putBack(self: *ControlTokenIter, token: ControlToken) void { - std.debug.assert(self.peeked_token == null); - self.peeked_token = token; + std.debug.assert(self.peeked_token_count < self.peeked_tokens.len); + self.peeked_tokens[self.peeked_token_count] = token; + self.peeked_token_count += 1; } }; @@ -1310,8 +1281,8 @@ test "template" { try testCase("abcd", .{}, "abcd"); try testCase("{.val}", .{ .val = 3 }, "3"); try testCase("{#if .val}1{/if}", .{ .val = true }, "1"); - try testCase("{#for .vals |$v|}{$v}{/for}", .{ .vals = [_]u8{ 1, 2, 3 } }, "123"); try testCase("{#for .vals |$v|=} {$v} {=/for}", .{ .vals = [_]u8{ 1, 2, 3 } }, "123"); + try testCase("{#for .vals |$val|}{$val}{/for}", .{ .vals = [_]u8{ 1, 2, 3 } }, "123"); try testCase("{#if .val}1{#else}0{/if}", .{ .val = true }, "1"); try testCase("{#if .val}1{#else}0{/if}", .{ .val = false }, "0"); try testCase("{#if .val}1{#elif .foo}2{/if}", .{ .val = false, .foo = true }, "2"); diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index 1b484b9..04507de 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -49,7 +49,7 @@ {#if .maybe_foo |$v|}{$v}{#else}null{/if} {#if .maybe_bar |$v|}{$v}{#else}null{/if} {#if .maybe_foo |$_|}abcd{#else}null{/if} - {.maybe_foo.?} + {{.maybe_foo.?}} This causes an error: {{.maybe_bar.?}} From 25d6ee024579224f3a5dfd8aaf03d535a6c8047e Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Wed, 14 Dec 2022 01:36:58 -0800 Subject: [PATCH 2/7] Support optional deref post-refactor --- src/template/lib.zig | 28 ++++++++++++++++++++-------- src/template/test.tmp.html | 2 +- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index 26783c8..6e5944d 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -234,7 +234,7 @@ fn EvaluateExpression( .isTag => bool, .slice => |sl| []const std.meta.Elem(EvaluateExpression(sl.iterable, Args, Captures, Context)), }, - .optional_unwrap => |expr| std.meta.Child(EvaluateExpression(expr, Args, Captures, Context)), + .optional_unwrap => |expr| std.meta.Child(EvaluateExpression(expr.*, Args, Captures, Context)), }; } @@ -279,7 +279,10 @@ fn evaluateExpression( return iterable[start..end]; }, }, - .optional_unwrap => unreachable, + .optional_unwrap => |expr| { + const val = try evaluateExpression(expr.*, args, captures, context); + return val orelse error.NullOptional; + }, }; } @@ -561,15 +564,24 @@ fn parseExpression(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIt if (expr == null) { expr = .{ .args = {} }; if (iter.peek()) |n| if (n == .text) iter.putBack(.{ .period = {} }); - } else { - const field = iter.next(); - expectToken(field, .text); - expr = .{ + } else switch (iter.next() orelse break) { + .text => |text| expr = .{ .deref = &.{ .container = expr.?, - .field = field.?.text, + .field = text, }, - }; + }, + .question_mark => expr = .{ + .optional_unwrap = blk: { + const e = expr.?; + break :blk &e; + }, + }, + else => |t2| { + iter.pushBack(t2); + iter.pushBack(token); + break; + }, } last_valid_iter = iter; }, diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index 04507de..1b484b9 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -49,7 +49,7 @@ {#if .maybe_foo |$v|}{$v}{#else}null{/if} {#if .maybe_bar |$v|}{$v}{#else}null{/if} {#if .maybe_foo |$_|}abcd{#else}null{/if} - {{.maybe_foo.?}} + {.maybe_foo.?} This causes an error: {{.maybe_bar.?}} From f2a23db588a076bd3f352a33490ec3a28fc581bc Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Wed, 14 Dec 2022 02:08:07 -0800 Subject: [PATCH 3/7] Support integer literals --- src/template/lib.zig | 91 ++++++++++++++++++++++++++++++-------- src/template/test.tmp.html | 1 + 2 files changed, 74 insertions(+), 18 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index 6e5944d..ac9110e 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -235,6 +235,7 @@ fn EvaluateExpression( .slice => |sl| []const std.meta.Elem(EvaluateExpression(sl.iterable, Args, Captures, Context)), }, .optional_unwrap => |expr| std.meta.Child(EvaluateExpression(expr.*, Args, Captures, Context)), + .int => isize, }; } @@ -271,8 +272,8 @@ fn evaluateExpression( }, .slice => |sl| { const iterable = try evaluateExpression(sl.iterable, args, captures, context); - const start = try evaluateExpression(sl.start, args, captures, context); - const end = try evaluateExpression(sl.end, args, captures, context); + const start = std.math.cast(usize, try evaluateExpression(sl.start, args, captures, context)) orelse return error.IndexOutOfBounds; + const end = std.math.cast(usize, try evaluateExpression(sl.end, args, captures, context)) orelse return error.IndexOutOfBounds; if (comptime std.meta.trait.is(.Array)(@TypeOf(iterable))) @compileError("Cannot slice an array, pass a slice or pointer to array instead"); if (start > iterable.len or end > iterable.len) return error.IndexOutOfBounds; @@ -283,6 +284,7 @@ fn evaluateExpression( const val = try evaluateExpression(expr.*, args, captures, context); return val orelse error.NullOptional; }, + .int => |i| return i, }; } @@ -512,7 +514,7 @@ fn parseTemplateTokens(comptime tokens: ControlTokenIter) []const TemplateToken while (iter.next()) |token| switch (token) { .whitespace => |wsp| items = items ++ [_]TemplateToken{.{ .whitespace = wsp }}, - .text => |text| items = items ++ [_]TemplateToken{.{ .text = text }}, + .number, .text => |text| items = items ++ [_]TemplateToken{.{ .text = text }}, .open_bracket => { const next = iter.next() orelse @compileError("Unexpected end of template"); if (next == .open_bracket) { @@ -550,6 +552,33 @@ fn parseTemplateTokens(comptime tokens: ControlTokenIter) []const TemplateToken } } +fn tryParseIdentifier(comptime tokens: ControlTokenIter) ?ParseResult(ControlTokenIter, []const u8) { + comptime { + var iter = skipWhitespace(tokens); + + var ident: []const u8 = ""; + var first: bool = true; + while (iter.next()) |token| switch (token) { + .number, .text => |text| { + if (first and token == .number) return null; + ident = ident ++ text; + first = false; + }, + else => { + iter.putBack(token); + break; + }, + }; + + if (first) return null; + + return ParseResult(ControlTokenIter, []const u8){ + .new_iter = iter, + .item = ident, + }; + } +} + fn parseExpression(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, Expression) { comptime { var iter = tokens; @@ -564,25 +593,24 @@ fn parseExpression(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIt if (expr == null) { expr = .{ .args = {} }; if (iter.peek()) |n| if (n == .text) iter.putBack(.{ .period = {} }); - } else switch (iter.next() orelse break) { - .text => |text| expr = .{ + } else if (tryParseIdentifier(iter)) |ident| { + iter = ident.new_iter; + + expr = .{ .deref = &.{ .container = expr.?, - .field = text, + .field = ident.item, }, - }, - .question_mark => expr = .{ + }; + } else if (iter.peek()) |next| if (next == .question_mark) { + _ = iter.next(); + expr = .{ .optional_unwrap = blk: { const e = expr.?; break :blk &e; }, - }, - else => |t2| { - iter.pushBack(t2); - iter.pushBack(token); - break; - }, - } + }; + }; last_valid_iter = iter; }, .dollar => { @@ -622,6 +650,12 @@ fn parseExpression(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIt expr = .{ .builtin = &builtin.item }; last_valid_iter = iter; }, + .number => |n| { + if (expr != null) break; + const num = std.fmt.parseInt(isize, n, 10) catch @compileError("Error parsing integer"); + expr = .{ .int = num }; + last_valid_iter = iter; + }, else => break, } } @@ -808,7 +842,7 @@ fn parseControlBlock(comptime tokens: ControlTokenIter) ParseResult(ControlToken }, else => { @compileLog(iter.row); - @compileError("TODO " ++ @tagName(token) ++ " " ++ token.text); + @compileError("TODO " ++ @tagName(token)); }, }; @@ -1011,7 +1045,7 @@ fn parseFormat(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, expectToken(iter.next(), .double_quote); var fmt_str: []const u8 = ""; while (true) switch (iter.next() orelse @compileError("Unexpected end of template")) { - .text, .whitespace => |t| fmt_str = fmt_str ++ t, + .text, .number, .whitespace => |t| fmt_str = fmt_str ++ t, .open_bracket => fmt_str = fmt_str ++ "{", .close_bracket => fmt_str = fmt_str ++ "}", .period => fmt_str = fmt_str ++ ".", @@ -1071,6 +1105,7 @@ const Expression = union(enum) { equals: *const EqualsExpr, builtin: *const BuiltinCall, optional_unwrap: *const Expression, + int: isize, }; const For = struct { @@ -1193,6 +1228,7 @@ const BuiltinCall = union(Builtin) { const ControlToken = union(enum) { text: []const u8, + number: []const u8, open_bracket: void, close_bracket: void, period: void, @@ -1219,6 +1255,18 @@ const ControlTokenIter = struct { row: usize = 0, + fn isControlChar(ch: u8) bool { + return switch (ch) { + '{', '}', '.', '#', '|', '$', '/', '=', '@', ',', '%', '(', ')', '"', '?' => true, + + else => false, + }; + } + + fn isTextChar(ch: u8) bool { + return !std.ascii.isWhitespace(ch) and !std.ascii.isDigit(ch) and !isControlChar(ch); + } + fn next(self: *ControlTokenIter) ?ControlToken { if (self.peeked_token_count != 0) { const t = self.peeked_tokens[self.peeked_token_count - 1].?; @@ -1257,9 +1305,16 @@ const ControlTokenIter = struct { self.start += idx - 1; return .{ .whitespace = remaining[0..idx] }; }, + '0'...'9' => { + var idx: usize = 0; + while (idx < remaining.len and std.ascii.isDigit(remaining[idx])) : (idx += 1) {} + + self.start += idx - 1; + return .{ .number = remaining[0..idx] }; + }, else => { var idx: usize = 0; - while (idx < remaining.len and std.mem.indexOfScalar(u8, "{}.#|$/=@,%()\"? \t\n\r", remaining[idx]) == null) : (idx += 1) {} + while (idx < remaining.len and isTextChar(remaining[idx])) : (idx += 1) {} self.start += idx - 1; return .{ .text = remaining[0..idx] }; diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index 1b484b9..1879bd0 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -27,6 +27,7 @@ {=/if} sliced: {#for @slice(.foo, .start, .end) |$s|}{$s}, {/for} + sliced: {#for @slice(.foo, 1, 3) |$s|}{$s}, {/for} format: {#format "s" .x} From 73ba42d2d59ebfb1c5db0efd108b3005fa5ab239 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Wed, 14 Dec 2022 02:35:23 -0800 Subject: [PATCH 4/7] Add loop index capture support --- src/template/lib.zig | 43 +++++++++++++++----------------------- src/template/test.tmp.html | 1 + 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index ac9110e..c5fb6b6 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -82,13 +82,15 @@ fn executeStatement( const iterable = try evaluateExpression(loop.header.iterable, args, captures, context); const subtemplate = loop.subtemplate; //std.log.debug("{any}", .{subtemplate}); - for (iterable) |v| { + for (iterable) |v, i| { + const with_item_capture = addCapture(captures, loop.header.item_capture, v); + const with_idx_capture = if (comptime loop.header.idx_capture) |name| addCapture(with_item_capture, name, i) else with_item_capture; try executeTemplate( writer, templates, subtemplate, args, - addCapture(captures, loop.header.capture, v), + with_idx_capture, context, ); } @@ -227,7 +229,7 @@ fn EvaluateExpression( const T = EvaluateExpression(expr.container, Args, Captures, Context); for (@typeInfo(T).Struct.fields) |f| { if (std.mem.eql(u8, expr.field, f.name)) return f.field_type; - } + } else @compileError("Field " ++ expr.field ++ " does not exist on type " ++ @typeName(T)); }, .equals => bool, .builtin => |call| switch (call.*) { @@ -875,32 +877,21 @@ fn endControlBlock(comptime tokens: ControlTokenIter) ControlTokenIter { fn parseForHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, ForHeader) { comptime { const iterable = parseExpression(tokens); - var iter = iterable.new_iter; + var iter = skipWhitespace(iterable.new_iter); - iter = skipWhitespace(iter); - { - const token = iter.next() orelse @compileError("Unexpected end of template"); - if (token != .pipe) @compileError("Unexpected token"); - } - { - const token = iter.next() orelse @compileError("Unexpected end of template"); - if (token != .dollar) @compileError("Unexpected token"); - } - const capture = blk: { - const token = iter.next() orelse @compileError("Unexpected end of template"); - if (token != .text) @compileError("Unexpected token"); - break :blk token.text; + const captures = tryParseCapture(iter) orelse { + @compileLog(iter.row); + @compileError("Expected capture"); }; - { - const token = iter.next() orelse @compileError("Unexpected end of template"); - if (token != .pipe) @compileError("Unexpected token"); - } + + if (captures.item.len == 0 or captures.item.len > 2) @compileError("Expected 1 or 2 captures"); return .{ - .new_iter = iter, + .new_iter = captures.new_iter, .item = .{ .iterable = iterable.item, - .capture = capture, + .item_capture = captures.item[0], + .idx_capture = if (captures.item.len == 2) captures.item[1] else null, }, }; } @@ -908,9 +899,8 @@ fn parseForHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIte fn tryParseCapture(comptime tokens: ControlTokenIter) ?ParseResult(ControlTokenIter, []const []const u8) { comptime { - var iter = tokens; + var iter = skipWhitespace(tokens); - iter = skipWhitespace(iter); if ((iter.next() orelse return null) != .pipe) return null; var captures: []const []const u8 = &.{}; while (true) { @@ -1115,7 +1105,8 @@ const For = struct { const ForHeader = struct { iterable: Expression, - capture: []const u8, + item_capture: []const u8, + idx_capture: ?[]const u8, }; const If = struct { diff --git a/src/template/test.tmp.html b/src/template/test.tmp.html index 1879bd0..7023179 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -14,6 +14,7 @@ {$b}: {= /for =} {= /for} + {#for .baz |$f, $i| =}{$i}{/for} {#if .quxx == .quxx2}eql{#else}neq{/if} {#if .quxx == .qux}eql{#else}neq{/if} {#if @isTag(.snap, foo)}foo{/if} From 27ee0ecd5d415003c543d7f25cdbac707ffe91ab Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Wed, 14 Dec 2022 02:57:27 -0800 Subject: [PATCH 5/7] Add support for array.len pseudo-field --- src/template/lib.zig | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/template/lib.zig b/src/template/lib.zig index c5fb6b6..4e8803c 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -215,6 +215,17 @@ fn print(writer: anytype, arg: anytype) !void { const ExpressionError = error{ IndexOutOfBounds, NullOptional }; +fn Deref(comptime T: type, comptime field: []const u8) type { + if (std.meta.trait.isIndexable(T) and std.mem.eql(u8, field, "len")) return usize; + switch (@typeInfo(T)) { + .Pointer => return Deref(std.meta.Child(T), field), + .Struct => |info| for (info.fields) |f| { + if (std.mem.eql(u8, field, f.name)) return f.field_type; + } else @compileError("Field " ++ field ++ " does not exist on type " ++ @typeName(T)), + else => @compileError("Cannot retrieve field " ++ field ++ " from type " ++ @typeName(T)), + } +} + fn EvaluateExpression( comptime expression: Expression, comptime Args: type, @@ -227,9 +238,7 @@ fn EvaluateExpression( .context => Context, .deref => |expr| { const T = EvaluateExpression(expr.container, Args, Captures, Context); - for (@typeInfo(T).Struct.fields) |f| { - if (std.mem.eql(u8, expr.field, f.name)) return f.field_type; - } else @compileError("Field " ++ expr.field ++ " does not exist on type " ++ @typeName(T)); + return Deref(T, expr.field); }, .equals => bool, .builtin => |call| switch (call.*) { @@ -300,12 +309,14 @@ fn AddCapture(comptime Root: type, comptime name: []const u8, comptime Val: type .alignment = @alignOf(Val), }}; - return @Type(.{ .Struct = .{ + const Result = @Type(.{ .Struct = .{ .layout = .Auto, .fields = fields, .decls = &.{}, .is_tuple = false, } }); + + return Result; } fn addCapture(root: anytype, comptime name: []const u8, val: anytype) AddCapture(@TypeOf(root), name, @TypeOf(val)) { From ee059ac36c6f2e6506dcc324bdd71269c62573ca Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Wed, 14 Dec 2022 02:57:53 -0800 Subject: [PATCH 6/7] Add breadcrumbs --- src/main/controllers.zig | 4 ++ src/main/controllers/web.zig | 16 ++++++- .../controllers/web/drive/directory.tmpl.html | 43 ++++++++++++------- static/site.css | 5 +++ 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/src/main/controllers.zig b/src/main/controllers.zig index 98cbcf5..ae62126 100644 --- a/src/main/controllers.zig +++ b/src/main/controllers.zig @@ -90,6 +90,8 @@ pub fn EndpointRequest(comptime Endpoint: type) type { body: Body, query: Query, + mount_path: []const u8, + const args_middleware = //if (Args == void) //mdw.injectContext(.{ .args = {} }) //else @@ -120,6 +122,8 @@ fn CallApiEndpoint(comptime Endpoint: type) type { .args = ctx.args, .body = ctx.body, .query = ctx.query_params, + + .mount_path = if (@hasField(@TypeOf(ctx), "mounted_at")) ctx.mounted_at else "", }; var response = Response{ .headers = http.Fields.init(ctx.allocator), .res = res }; diff --git a/src/main/controllers/web.zig b/src/main/controllers/web.zig index eed3e5f..cde66a8 100644 --- a/src/main/controllers/web.zig +++ b/src/main/controllers/web.zig @@ -245,8 +245,22 @@ const drive = struct { const info = try srv.driveGet(req.args.path); defer util.deepFree(srv.allocator, info); + var breadcrumbs = std.ArrayList([]const u8).init(srv.allocator); + defer breadcrumbs.deinit(); + + var iter = util.PathIter.from(req.args.path); + while (iter.next()) |p| { + std.log.debug("breadcrumb: {s}", .{p}); + try breadcrumbs.append(if (p.len != 0) p else continue); + } + switch (info) { - .dir => |dir| try res.template(.ok, srv, dir_tmpl, .{ .dir = dir }), + .dir => |dir| try res.template(.ok, srv, dir_tmpl, .{ + .dir = dir, + .breadcrumbs = breadcrumbs.items, + .mount_path = req.mount_path, + .base_drive_path = "drive", + }), else => unreachable, } } diff --git a/src/main/controllers/web/drive/directory.tmpl.html b/src/main/controllers/web/drive/directory.tmpl.html index 54f2136..aaafe36 100644 --- a/src/main/controllers/web/drive/directory.tmpl.html +++ b/src/main/controllers/web/drive/directory.tmpl.html @@ -1,29 +1,42 @@
+ {#for .dir.children.? |$child| =} {#switch $child case dir |$dir| =} - - - + + {#case file |$file|} - - + - diff --git a/static/site.css b/static/site.css index e6820a1..5106b6b 100644 --- a/static/site.css +++ b/static/site.css @@ -218,3 +218,8 @@ button:hover, a.button:hover { } } +.drive .icons { + display: flex; + justify-content: flex-end; + vertical-align: bottom; +} From a6b928b42b3b55a7b5f36a1d2f892e86c2776968 Mon Sep 17 00:00:00 2001 From: jaina heartles Date: Wed, 14 Dec 2022 03:13:09 -0800 Subject: [PATCH 7/7] Basic styling for breadcrumbs --- .../controllers/web/drive/directory.tmpl.html | 18 ++++++++++++++---- static/site.css | 5 +++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/main/controllers/web/drive/directory.tmpl.html b/src/main/controllers/web/drive/directory.tmpl.html index aaafe36..f6d59bf 100644 --- a/src/main/controllers/web/drive/directory.tmpl.html +++ b/src/main/controllers/web/drive/directory.tmpl.html @@ -1,10 +1,20 @@
{$dir.name.?} + + {$dir.name.?} + + - {= #if %user |$u|} + + {#if %user |$u|} {#if $u.avatar_file_id == $file.meta.id =} - + {= #elif $u.header_file_id == $file.meta.id =} - + {= /if =} - {= /if =} - - {= #if $file.meta.sensitive =} - + {= /if} + {#if $file.meta.sensitive =} + {= #else =} - - {= /if =} + + {= /if} + + + {$file.name.?} + {$file.name.?} {#if $file.meta.content_type |$t|}{$t}{/if} {$file.meta.size} {$file.meta.created_at}
diff --git a/static/site.css b/static/site.css index 5106b6b..1a172eb 100644 --- a/static/site.css +++ b/static/site.css @@ -218,6 +218,11 @@ button:hover, a.button:hover { } } +.drive .breadcrumbs { + list-style: none; + display: flex; +} + .drive .icons { display: flex; justify-content: flex-end;