diff --git a/src/main/controllers.zig b/src/main/controllers.zig index ae62126..98cbcf5 100644 --- a/src/main/controllers.zig +++ b/src/main/controllers.zig @@ -90,8 +90,6 @@ 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 @@ -122,8 +120,6 @@ 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 cde66a8..eed3e5f 100644 --- a/src/main/controllers/web.zig +++ b/src/main/controllers/web.zig @@ -245,22 +245,8 @@ 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, - .breadcrumbs = breadcrumbs.items, - .mount_path = req.mount_path, - .base_drive_path = "drive", - }), + .dir => |dir| try res.template(.ok, srv, dir_tmpl, .{ .dir = dir }), else => unreachable, } } diff --git a/src/main/controllers/web/drive/directory.tmpl.html b/src/main/controllers/web/drive/directory.tmpl.html index f6d59bf..54f2136 100644 --- a/src/main/controllers/web/drive/directory.tmpl.html +++ b/src/main/controllers/web/drive/directory.tmpl.html @@ -1,52 +1,29 @@
- | - - {$dir.name.?} - - | ++ | + | {$dir.name.?} | {#case file |$file|} -- {#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} - | -- - {$file.name.?} - + + {= /if =} | +{$file.name.?} | {#if $file.meta.content_type |$t|}{$t}{/if} | {$file.meta.size} | {$file.meta.created_at} | diff --git a/src/template/lib.zig b/src/template/lib.zig index 4e8803c..77460bd 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -82,15 +82,13 @@ fn executeStatement( const iterable = try evaluateExpression(loop.header.iterable, args, captures, context); const subtemplate = loop.subtemplate; //std.log.debug("{any}", .{subtemplate}); - 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; + for (iterable) |v| { try executeTemplate( writer, templates, subtemplate, args, - with_idx_capture, + addCapture(captures, loop.header.capture, v), context, ); } @@ -213,19 +211,35 @@ fn print(writer: anytype, arg: anytype) !void { try std.fmt.format(writer, "{}", .{arg}); } -const ExpressionError = error{ IndexOutOfBounds, NullOptional }; +const DerefError = error{NullOptional}; +fn Deref(comptime T: type, comptime names: []const DerefDecl) type { + if (names.len == 0) return T; -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)), + // 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; + fn EvaluateExpression( comptime expression: Expression, comptime Args: type, @@ -233,20 +247,14 @@ fn EvaluateExpression( comptime Context: type, ) type { return switch (expression) { - .args => Args, - .captures => Captures, - .context => Context, - .deref => |expr| { - const T = EvaluateExpression(expr.container, Args, Captures, Context); - return Deref(T, expr.field); - }, + .arg_deref => |names| Deref(Args, names), + .capture_deref => |names| Deref(Captures, names), + .context_deref => |names| Deref(Context, names), .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)), - .int => isize, }; } @@ -257,15 +265,9 @@ fn evaluateExpression( context: anytype, ) ExpressionError!EvaluateExpression(expression, @TypeOf(args), @TypeOf(captures), @TypeOf(context)) { return switch (expression) { - .args => args, - .captures => captures, - .context => context, - .deref => |expr| { - return @field( - try evaluateExpression(expr.container, args, captures, context), - expr.field, - ); - }, + .arg_deref => |names| try deref(args, names), + .capture_deref => |names| try deref(captures, names), + .context_deref => |names| try deref(context, names), .equals => |eql| { const lhs = try evaluateExpression(eql.lhs, args, captures, context); const rhs = try evaluateExpression(eql.rhs, args, captures, context); @@ -283,19 +285,14 @@ fn evaluateExpression( }, .slice => |sl| { const iterable = try evaluateExpression(sl.iterable, 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; + const start = try evaluateExpression(sl.start, args, captures, context); + const end = try evaluateExpression(sl.end, args, captures, context); 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; return iterable[start..end]; }, }, - .optional_unwrap => |expr| { - const val = try evaluateExpression(expr.*, args, captures, context); - return val orelse error.NullOptional; - }, - .int => |i| return i, }; } @@ -309,14 +306,12 @@ fn AddCapture(comptime Root: type, comptime name: []const u8, comptime Val: type .alignment = @alignOf(Val), }}; - const Result = @Type(.{ .Struct = .{ + return @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)) { @@ -527,7 +522,7 @@ fn parseTemplateTokens(comptime tokens: ControlTokenIter) []const TemplateToken while (iter.next()) |token| switch (token) { .whitespace => |wsp| items = items ++ [_]TemplateToken{.{ .whitespace = wsp }}, - .number, .text => |text| items = items ++ [_]TemplateToken{.{ .text = text }}, + .text => |text| items = items ++ [_]TemplateToken{.{ .text = text }}, .open_bracket => { const next = iter.next() orelse @compileError("Unexpected end of template"); if (next == .open_bracket) { @@ -565,113 +560,60 @@ 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; var last_valid_iter: ?ControlTokenIter = null; var expr: ?Expression = null; - 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 if (tryParseIdentifier(iter)) |ident| { - iter = ident.new_iter; + 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 = .{ - .deref = &.{ - .container = expr.?, - .field = ident.item, - }, - }; - } else if (iter.peek()) |next| if (next == .question_mark) { - _ = iter.next(); - expr = .{ - .optional_unwrap = blk: { - const e = expr.?; - break :blk &e; - }, - }; + expr = .{ + .equals = &.{ + .lhs = lhs, + .rhs = rhs.item, + }, }; last_valid_iter = iter; - }, - .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; - }, - .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, - } - } + } 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"), @@ -855,7 +797,7 @@ fn parseControlBlock(comptime tokens: ControlTokenIter) ParseResult(ControlToken }, else => { @compileLog(iter.row); - @compileError("TODO " ++ @tagName(token)); + @compileError("TODO " ++ @tagName(token) ++ " " ++ token.text); }, }; @@ -888,21 +830,32 @@ fn endControlBlock(comptime tokens: ControlTokenIter) ControlTokenIter { fn parseForHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIter, ForHeader) { comptime { const iterable = parseExpression(tokens); - var iter = skipWhitespace(iterable.new_iter); + var iter = iterable.new_iter; - const captures = tryParseCapture(iter) orelse { - @compileLog(iter.row); - @compileError("Expected capture"); + 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; }; - - if (captures.item.len == 0 or captures.item.len > 2) @compileError("Expected 1 or 2 captures"); + { + const token = iter.next() orelse @compileError("Unexpected end of template"); + if (token != .pipe) @compileError("Unexpected token"); + } return .{ - .new_iter = captures.new_iter, + .new_iter = iter, .item = .{ .iterable = iterable.item, - .item_capture = captures.item[0], - .idx_capture = if (captures.item.len == 2) captures.item[1] else null, + .capture = capture, }, }; } @@ -910,8 +863,9 @@ fn parseForHeader(comptime tokens: ControlTokenIter) ParseResult(ControlTokenIte fn tryParseCapture(comptime tokens: ControlTokenIter) ?ParseResult(ControlTokenIter, []const []const u8) { comptime { - var iter = skipWhitespace(tokens); + var iter = tokens; + iter = skipWhitespace(iter); if ((iter.next() orelse return null) != .pipe) return null; var captures: []const []const u8 = &.{}; while (true) { @@ -1018,6 +972,40 @@ 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; @@ -1046,7 +1034,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, .number, .whitespace => |t| fmt_str = fmt_str ++ t, + .text, .whitespace => |t| fmt_str = fmt_str ++ t, .open_bracket => fmt_str = fmt_str ++ "{", .close_bracket => fmt_str = fmt_str ++ "}", .period => fmt_str = fmt_str ++ ".", @@ -1088,9 +1076,9 @@ const TemplateItem = union(enum) { statement: Statement, }; -const DerefExpr = struct { - container: Expression, +const DerefDecl = union(enum) { field: []const u8, + optional_unwrap: void, }; const EqualsExpr = struct { @@ -1099,14 +1087,11 @@ const EqualsExpr = struct { }; const Expression = union(enum) { - args: void, - captures: void, - context: void, - deref: *const DerefExpr, + arg_deref: []const DerefDecl, + capture_deref: []const DerefDecl, + context_deref: []const DerefDecl, equals: *const EqualsExpr, builtin: *const BuiltinCall, - optional_unwrap: *const Expression, - int: isize, }; const For = struct { @@ -1116,8 +1101,7 @@ const For = struct { const ForHeader = struct { iterable: Expression, - item_capture: []const u8, - idx_capture: ?[]const u8, + capture: []const u8, }; const If = struct { @@ -1230,7 +1214,6 @@ const BuiltinCall = union(Builtin) { const ControlToken = union(enum) { text: []const u8, - number: []const u8, open_bracket: void, close_bracket: void, period: void, @@ -1252,29 +1235,14 @@ const ControlToken = union(enum) { const ControlTokenIter = struct { start: usize = 0, text: []const u8, - peeked_tokens: [2]?ControlToken = [2]?ControlToken{ null, null }, - peeked_token_count: usize = 0, + peeked_token: ?ControlToken = null, 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].?; - self.peeked_tokens[self.peeked_token_count - 1] = null; - self.peeked_token_count -= 1; - return t; + if (self.peeked_token) |token| { + self.peeked_token = null; + return token; } const remaining = self.text[self.start..]; @@ -1307,16 +1275,9 @@ 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 isTextChar(remaining[idx])) : (idx += 1) {} + while (idx < remaining.len and std.mem.indexOfScalar(u8, "{}.#|$/=@,%()\"? \t\n\r", remaining[idx]) == null) : (idx += 1) {} self.start += idx - 1; return .{ .text = remaining[0..idx] }; @@ -1326,14 +1287,13 @@ const ControlTokenIter = struct { fn peek(self: *ControlTokenIter) ?ControlToken { const token = self.next(); - if (token) |t| self.putBack(t); + self.peeked_token = token; return token; } fn putBack(self: *ControlTokenIter, token: ControlToken) void { - std.debug.assert(self.peeked_token_count < self.peeked_tokens.len); - self.peeked_tokens[self.peeked_token_count] = token; - self.peeked_token_count += 1; + std.debug.assert(self.peeked_token == null); + self.peeked_token = token; } }; @@ -1350,8 +1310,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 7023179..1b484b9 100644 --- a/src/template/test.tmp.html +++ b/src/template/test.tmp.html @@ -14,7 +14,6 @@ {$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} @@ -28,7 +27,6 @@ {=/if} sliced: {#for @slice(.foo, .start, .end) |$s|}{$s}, {/for} - sliced: {#for @slice(.foo, 1, 3) |$s|}{$s}, {/for} format: {#format "s" .x} diff --git a/static/site.css b/static/site.css index 1a172eb..e6820a1 100644 --- a/static/site.css +++ b/static/site.css @@ -218,13 +218,3 @@ button:hover, a.button:hover { } } -.drive .breadcrumbs { - list-style: none; - display: flex; -} - -.drive .icons { - display: flex; - justify-content: flex-end; - vertical-align: bottom; -}