diff --git a/build.zig b/build.zig index 0272f9f..0a6a141 100644 --- a/build.zig +++ b/build.zig @@ -84,15 +84,6 @@ pub fn build(b: *std.Build) void { .use_llvm = true, }); - // const lib = b.addLibrary(.{ - // .name = "zdt_prov", - // .root_module = mod, - // }); - // - // lib.root_module.addIncludePath(b.path("src/svg")); - // - // b.installArtifact(lib); - // This declares intent for the executable to be installed into the // install prefix when running `zig build` (i.e. when executing the default // step). By default the install prefix is `zig-out/` but can be overridden diff --git a/src/cache.zig b/src/cache.zig index 2543d8a..6a921c9 100644 --- a/src/cache.zig +++ b/src/cache.zig @@ -2,13 +2,10 @@ const std = @import("std"); const Allocator = std.heap.ArenaAllocator; const parser = @import("parser.zig"); -const icons = @import("svg/icons.zig"); pub const TemplateCache = struct { arena: Allocator, cache: std.StringHashMapUnmanaged([]parser.Node), - default_path: ?[]const u8 = "templates", - icons: ?icons.SvgIcon =null, pub fn init(child_allocator: std.mem.Allocator) TemplateCache { const arena = std.heap.ArenaAllocator.init(child_allocator); @@ -18,6 +15,7 @@ pub const TemplateCache = struct { }; } + pub fn deinit(self: *TemplateCache) void { self.arena.deinit(); } @@ -52,10 +50,6 @@ pub const TemplateCache = struct { } } - pub fn initIcons(self: *TemplateCache) !void { - self.icons = icons.SvgIcon.init(self.allocator()) catch null; - } - pub fn clear(self: *TemplateCache) void { self.deinit(); self.cache = .{}; diff --git a/src/cache_bkp.zig b/src/cache_bkp.zig new file mode 100644 index 0000000..c701b6d --- /dev/null +++ b/src/cache_bkp.zig @@ -0,0 +1,51 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const parser = @import("parser.zig"); + +pub const TemplateCache = struct { + allocator: Allocator, + cache: std.StringHashMapUnmanaged([]parser.Node), + + pub fn init(allocator: Allocator) TemplateCache { + return .{ + .allocator = allocator, + .cache = .{}, + }; + } + + pub fn deinit(self: *TemplateCache) void { + var it = self.cache.iterator(); + while (it.next()) |entry| { + self.allocator.free(entry.key_ptr.*); + for (entry.value_ptr.*) |node| node.deinit(self.allocator); + self.allocator.free(entry.value_ptr.*); + } + self.cache.deinit(self.allocator); + } + + pub fn get(self: *const TemplateCache, key: []const u8) ?[]parser.Node { + return self.cache.get(key); + } + + pub fn add(self: *TemplateCache, key: []const u8, nodes: []parser.Node) !void { + const key_copy = try self.allocator.dupe(u8, key); + errdefer self.allocator.free(key_copy); + + try self.cache.put(self.allocator, key_copy, nodes); + } + + pub fn invalidate(self: *TemplateCache, key: []const u8) void { + if (self.cache.getEntry(key)) |entry| { + self.allocator.free(entry.key_ptr.*); + for (entry.value_ptr.*) |node| node.deinit(self.allocator); + self.allocator.free(entry.value_ptr.*); + _ = self.cache.remove(key); + } + } + + pub fn clear(self: *TemplateCache) void { + self.deinit(); + self.cache = .{}; + } +}; diff --git a/src/context.zig b/src/context.zig index 6cf1744..8b5dd92 100644 --- a/src/context.zig +++ b/src/context.zig @@ -1,6 +1,4 @@ const std = @import("std"); -const time = @import("time.zig"); -const util = @import("util.zig"); pub const Value = union(enum) { null, @@ -36,24 +34,8 @@ pub const Context = struct { self.arena.deinit(); } - pub fn toValue(self: *Context, value: anytype) !Value { + fn toValue(self: *Context, value: anytype) !Value { const T = @TypeOf(value); - if (T == time.Time) { - return Value{ .string = try time.formatDateTime(self.allocator(), value, "Y-m-d H:i:s") }; - } - - if (@typeInfo(T) == .pointer) { - if (@typeInfo(T).pointer.size == .slice) { - if (@typeInfo(@typeInfo(T).pointer.child) == .@"struct") { - var list = try self.allocator().alloc(Value, value.len); - for (value, 0..) |item, i| { - list[i] = try self.toValue(item); - } - return Value{ .list = list }; - } - } - } - return switch (@typeInfo(T)) { .bool => Value{ .bool = value }, .int, .comptime_int => Value{ .int = @intCast(value) }, @@ -123,14 +105,6 @@ pub const Context = struct { return current; } - pub fn getOr(self: *Context, path: []const u8, default: anytype) ?Value { - return self.get(path) orelse try self.toValue(default); - } - - fn isArrayList(value: anytype) bool { - if (std.mem.startsWith(u8, @typeName(value), "array_list")) return true; - return false; - } // pub fn get(self: *const Context, comptime T: type, key: []const u8) !T { // // const opt_value = self.map.get(key) orelse return error.KeyNotFound; diff --git a/src/context_proposta.zig b/src/context_proposta.zig new file mode 100644 index 0000000..9e15f8c --- /dev/null +++ b/src/context_proposta.zig @@ -0,0 +1,81 @@ +const std = @import("std"); + +pub const Value = union(enum) { + null, + bool: bool, + int: i64, + float: f64, + string: []const u8, + list: []Value, + dict: std.HashMapUnmanaged([]const u8, Value, std.hash_map.StringContext, 80), + struct_: std.HashMapUnmanaged([]const u8, Value, std.hash_map.StringContext, 80), + + pub fn deinit(self: *Value, allocator: std.mem.Allocator) void { + switch (self.*) { + .string => allocator.free(self.string), + .list => { + for (self.list) |*v| v.deinit(allocator); + allocator.free(self.list); + }, + .dict => { + var iter = self.dict.iterator(); + while (iter.next()) |entry| { + allocator.free(entry.key_ptr.*); + entry.value_ptr.deinit(allocator); + } + self.dict.deinit(allocator); + }, + .struct_ => { + var iter = self.struct_.iterator(); + while (iter.next()) |entry| { + allocator.free(entry.key_ptr.*); + entry.value_ptr.deinit(allocator); + } + self.struct_.deinit(allocator); + }, + else => {}, + } + } +}; + +pub const Context = struct { + allocator: std.mem.Allocator, + map: std.HashMapUnmanaged([]const u8, Value, std.hash_map.StringContext, 80), + parent: ?*Context = null, + + pub fn init(allocator: std.mem.Allocator) Context { + return .{ + .allocator = allocator, + .map = .{}, + }; + } + + pub fn deinit(self: *Context) void { + var iter = self.map.iterator(); + while (iter.next()) |entry| { + allocator.free(entry.key_ptr.*); + entry.value_ptr.deinit(self.allocator); + } + self.map.deinit(self.allocator); + if (self.parent) |p| p.deinit(); + } + + pub fn get(self: *const Context, key: []const u8) ?Value { + return self.map.get(key) or (self.parent orelse return null).get(key); + } + + pub fn set(self: *Context, key: []const u8, value: Value) !void { + const duped_key = try self.allocator.dupe(u8, key); + try self.map.put(self.allocator, duped_key, value); + } + + pub fn pushScope(self: *Context) *Context { + var child = Context.init(self.allocator); + child.parent = self; + return &child; + } + + pub fn popScope(self: *Context) void { + self.deinit(); // libera o escopo atual + } +}; diff --git a/src/context_test.zig b/src/context_test.zig index 25080b6..899284a 100644 --- a/src/context_test.zig +++ b/src/context_test.zig @@ -4,9 +4,6 @@ const Context = @import("context.zig").Context; const Value = @import("context.zig").Value; test "context set amigável e get com ponto" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("1 - context set amigável e get com ponto\n", .{}); - const allocator = testing.allocator; var ctx = Context.init(allocator); defer ctx.deinit(); @@ -20,26 +17,17 @@ test "context set amigável e get com ponto" { // struct const Person = struct { nome: []const u8, idade: i64 }; const p = Person{ .nome = "Ana", .idade = 25 }; - const p2 = Person{ .nome = "Fulana", .idade = 28 }; - - const people = [_]Person{ p, p2 }; - - // try ctx.set("user", p); - try ctx.set("user", people); + try ctx.set("user", p); // list const numeros = [_]i64{ 1, 2, 3 }; try ctx.set("lista", numeros); - for (ctx.get("user").?.list) |item| { - std.debug.print("user {any}\n", .{item.dict.get("nome").?}); - } - // acesso try testing.expectEqualStrings("Lucas", ctx.get("nome").?.string); try testing.expect(ctx.get("idade").?.int == 30); - // try testing.expectEqualStrings("Ana", ctx.get("user.nome").?.string); - // try testing.expect(ctx.get("user.idade").?.int == 25); + try testing.expectEqualStrings("Ana", ctx.get("user.nome").?.string); + try testing.expect(ctx.get("user.idade").?.int == 25); try testing.expect(ctx.get("lista.1").?.int == 2); try testing.expect(ctx.get("vazio").?.string.len == 0); try testing.expect(ctx.get("preco").?.float == 99.99); diff --git a/src/delta.zig b/src/delta.zig deleted file mode 100644 index e82bad1..0000000 --- a/src/delta.zig +++ /dev/null @@ -1,83 +0,0 @@ -// Em time.zig (ou crie um novo arquivo relativedelta.zig e importe) - -pub const RelativeDelta = struct { - years: i32 = 0, - months: i32 = 0, - days: i32 = 0, - hours: i32 = 0, - minutes: i32 = 0, - seconds: i32 = 0, - - pub fn init(fields: struct { - years: i32 = 0, - months: i32 = 0, - days: i32 = 0, - hours: i32 = 0, - minutes: i32 = 0, - seconds: i32 = 0, - }) RelativeDelta { - return .{ - .years = fields.years, - .months = fields.months, - .days = fields.days, - .hours = fields.hours, - .minutes = fields.minutes, - .seconds = fields.seconds, - }; - } - - // Helpers úteis (muito usados depois) - pub fn isZero(self: RelativeDelta) bool { - return self.years == 0 and - self.months == 0 and - self.days == 0 and - self.hours == 0 and - self.minutes == 0 and - self.seconds == 0; - } - - pub fn inSeconds(self: RelativeDelta) i64 { - return @as(i64, self.years) * 365 * 24 * 60 * 60 + - @as(i64, self.months) * 30 * 24 * 60 * 60 + - @as(i64, self.days) * 24 * 60 * 60 + - @as(i64, self.hours) * 60 * 60 + - @as(i64, self.minutes) * 60 + - @as(i64, self.seconds); - } - - pub fn inDays(self: RelativeDelta) i64 { - return @as(i64, self.years) * 365 + - @as(i64, self.months) * 30 + - @as(i64, self.days); - } - - pub fn normalize(self: *RelativeDelta) void { - // Normaliza meses → anos + meses - if (self.months >= 12 or self.months <= -12) { - const carry = @divTrunc(self.months, 12); - self.years += carry; - self.months -= carry * 12; - } - - // Normaliza segundos → minutos + segundos - if (self.seconds >= 60 or self.seconds <= -60) { - const carry = @divTrunc(self.seconds, 60); - self.minutes += carry; - self.seconds -= carry * 60; - } - - // Normaliza minutos → horas + minutos - if (self.minutes >= 60 or self.minutes <= -60) { - const carry = @divTrunc(self.minutes, 60); - self.hours += carry; - self.minutes -= carry * 60; - } - - // Normaliza horas → dias + horas - if (self.hours >= 24 or self.hours <= -24) { - const carry = @divTrunc(self.hours, 24); - self.days += carry; - self.hours -= carry * 24; - } - } -}; diff --git a/src/delta_test.zig b/src/delta_test.zig deleted file mode 100644 index 496443b..0000000 --- a/src/delta_test.zig +++ /dev/null @@ -1,224 +0,0 @@ -const std = @import("std"); -const testing = std.testing; -const Time = @import("time.zig").Time; -const Context = @import("context.zig").Context; -const RelativeDelta = @import("delta.zig").RelativeDelta; - -test "relativedelta rigoroso - meses" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("1 - context set amigável e get com ponto\n", .{}); - - const a = try Time.parse("2025-03-31"); - const b = try Time.parse("2025-01-31"); - const delta = a.subRelative(b); - try testing.expectEqual(delta.months, 2); - try testing.expectEqual(delta.years, 0); - try testing.expectEqual(delta.days, 0); - - const rev = b.subRelative(a); - try testing.expectEqual(rev.months, -2); -} - -test "relativedelta - overflow de dia" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("2 - relativedelta - overflow de dia\n", .{}); - - const jan31 = try Time.parse("2023-01-31"); - const mar01 = try Time.parse("2023-03-01"); - - const delta = mar01.subRelative(jan31); - // Esperado algo como: +1 mês +1 dia (ou +2 meses -30 dias, mas dateutil prefere o primeiro) - try testing.expect(delta.months == 1); - try testing.expect(delta.days == 1); -} - -test "bissexto: 2021-02-28 - 2020-02-29" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("3 - bissexto: 2021-02-28 - 2020-02-29\n", .{}); - - const a = try Time.parse("2021-02-28"); - const b = try Time.parse("2020-02-29"); - const delta = a.subRelative(b); - try testing.expectEqual(delta.years, 1); - try testing.expectEqual(delta.months, 0); - try testing.expectEqual(delta.days, 0); -} - -test "bissexto: 2021-03-01 - 2020-02-29" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("4 - bissexto: 2021-03-01 - 2020-02-29\n", .{}); - - const a = try Time.parse("2021-03-01"); - const b = try Time.parse("2020-02-29"); - const delta = a.subRelative(b); - try testing.expectEqual(delta.years, 1); - try testing.expectEqual(delta.months, 0); - try testing.expectEqual(delta.days, 1); -} - -test "bissexto: 2021-02-27 - 2020-02-29" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("5 - bissexto: 2021-02-27 - 2020-02-29\n", .{}); - - const a = try Time.parse("2021-02-27"); - const b = try Time.parse("2020-02-29"); - const delta = a.subRelative(b); - try testing.expectEqual(delta.years, 0); - try testing.expectEqual(delta.months, 11); - try testing.expectEqual(delta.days, 29); -} - -test "bissexto reverso: 2020-02-29 - 2021-02-28" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("6 - bissexto reverso: 2020-02-29 - 2021-02-28\n", .{}); - - const a = try Time.parse("2020-02-29"); - const b = try Time.parse("2021-02-28"); - const delta = a.subRelative(b); - try testing.expectEqual(delta.years, -1); - try testing.expectEqual(delta.months, 0); - try testing.expectEqual(delta.days, 0); -} - -test "addRelative: anos normais (não bissexto)" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("7 - addRelative: anos normais (não bissexto)\n", .{}); - - // 2023 não é bissexto - const base = try Time.parse("2023-06-15"); - const expected = try Time.parse("2026-06-15"); - - const delta = RelativeDelta.init(.{ .years = 3 }); - const result = base.addRelative(delta); - - try testing.expectEqualStrings( - try expected.toString( null), - try result.toString( null), - ); -} - -test "addRelative: anos normais com overflow de dia" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("8 - addRelative: anos normais com overflow de dia\n", .{}); - - // Janeiro 31 + 2 anos → deve ir para 31/jan (2025 não bissexto) - const base = try Time.parse("2023-01-31"); - const expected = try Time.parse("2025-01-31"); - - const delta = RelativeDelta.init(.{ .years = 2 }); - const result = base.addRelative(delta); - - try testing.expectEqualStrings( - try expected.toString( null), - try result.toString( null), - ); -} - -test "addRelative: de 29/fev bissexto + 1 ano (vai para 28/fev)" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("9 - addRelative: de 29/fev bissexto + 1 ano (vai para 28/fev)\n", .{}); - - // 2020 foi bissexto → +1 ano deve ir para 2021-02-28 (não bissexto) - const base = try Time.parse("2020-02-29"); - const expected = try Time.parse("2021-02-28"); - - const delta = RelativeDelta.init(.{ .years = 1 }); - const result = base.addRelative(delta); - - try testing.expectEqualStrings( - try expected.toString( null), - try result.toString( null), - ); -} - -test "addRelative: de 29/fev bissexto + 4 anos (permanece 29/fev)" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("10 - addRelative: de 29/fev bissexto + 4 anos (permanece 29/fev)\n", .{}); - - // 2020 → 2024 (ambos bissextos) - const base = try Time.parse("2020-02-29"); - const expected = try Time.parse("2024-02-29"); - - const delta = RelativeDelta.init(.{ .years = 4 }); - const result = base.addRelative(delta); - - try testing.expectEqualStrings( - try expected.toString( null), - try result.toString( null), - ); -} - -test "addRelative: de 29/fev + 1 ano + 1 mês (vai para março)" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("11 - addRelative: de 29/fev + 1 ano + 1 mês (vai para março)\n", .{}); - - // 2020-02-29 + 1 ano → 2021-02-28 + 1 mês → 2021-03-28 - const base = try Time.parse("2020-02-29"); - const expected = try Time.parse("2021-03-28"); - - const delta = RelativeDelta.init(.{ .years = 1, .months = 1 }); - const result = base.addRelative(delta); - - try testing.expectEqualStrings( - try expected.toString( null), - try result.toString( null), - ); -} - -test "addRelative: meses com overflow (31 → 28/30)" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("12 - addRelative: meses com overflow (31 → 28/30)\n", .{}); - - const cases = [_]struct { base: []const u8, months: i32, expected: []const u8 }{ - .{ .base = "2023-01-31", .months = 1, .expected = "2023-02-28" }, // não bissexto - .{ .base = "2024-01-31", .months = 1, .expected = "2024-02-29" }, // bissexto - .{ .base = "2023-03-31", .months = -1, .expected = "2023-02-28" }, - .{ .base = "2023-01-31", .months = 2, .expected = "2023-03-31" }, - .{ .base = "2023-08-31", .months = 1, .expected = "2023-09-30" }, // setembro tem 30 - }; - - for (cases) |c| { - const base_t = try Time.parse(c.base); - const exp_t = try Time.parse(c.expected); - - const delta = RelativeDelta.init(.{ .months = c.months }); - const result = base_t.addRelative(delta); - - try testing.expectEqualStrings( - try exp_t.toString( null), - try result.toString( null), - ); - } -} - -test "addRelative: combinação anos + meses + dias (bissexto envolvido)" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("13 - addRelative: combinação anos + meses + dias (bissexto envolvido)\n", .{}); - - // 2024-02-29 + 1 ano + 2 meses + 3 dias - // → 2025-02-28 + 2 meses → 2025-04-28 + 3 dias → 2025-05-01 - const base = try Time.parse("2024-02-29"); - const expected = try Time.parse("2025-05-01"); - - const delta = RelativeDelta.init(.{ .years = 1, .months = 2, .days = 3 }); - const result = base.addRelative(delta); - - try testing.expectEqualStrings( - try expected.toString( null), - try result.toString( null), - ); -} - -test "addRelative: delta zero não altera data" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("14 - addRelative: delta zero não altera data\n", .{}); - - const base = try Time.parse("2025-07-20"); - const delta = RelativeDelta.init(.{}); - const result = base.addRelative(delta); - - try testing.expectEqualStrings( - try base.toString( null), - try result.toString( null), - ); -} diff --git a/src/filters.zig b/src/filters.zig index 5f4fb3b..b86d356 100644 --- a/src/filters.zig +++ b/src/filters.zig @@ -2,15 +2,10 @@ const std = @import("std"); const Value = @import("context.zig").Value; const std_time = std.time; -const time = @import("time.zig"); - pub const FilterError = error{ InvalidArgument, - InvalidCharacter, - Overflow, OutOfMemory, - UnknownFilter, -} || time.TimeError; +}; const DictEntry = struct { key: []const u8, @@ -126,9 +121,35 @@ fn filter_cut(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!V return Value{ .string = try result.toOwnedSlice(alloc) }; } -fn filter_date(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { - return dateTimeToString(alloc, value, arg); -} +// fn filter_date(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { +// // Por enquanto, simples: aceita string ou int (timestamp) e formata com strftime-like +// // Futuro: suporte completo a std.time +// _ = alloc; +// const format = switch (arg orelse Value{ .string = "d/m/Y" }) { +// .string => |f| f, +// else => "d/m/Y", +// }; +// +// const timestamp = switch (value) { +// .int => |i| @as(i64, i), +// .string => |s| std.fmt.parseInt(i64, s, 10) catch 0, +// else => 0, +// }; +// +// // Simulação simples (em produção usar std.time) +// const day = @rem(timestamp, 30) + 1; +// const month = @rem(timestamp / 30, 12) + 1; +// const year = 2026 + @divFloor(timestamp, 360); +// +// var buf: [64]u8 = undefined; +// const formatted = switch (format) { +// "d/m/Y" => std.fmt.bufPrint(&buf, "{d:0>2}/{d:0>2}/{d}", .{ day, month, year }) catch "??/??/????", +// "Y-m-d" => std.fmt.bufPrint(&buf, "{d}-{d:0>2}-{d:0>2}", .{ year, month, day }) catch "????-??-??", +// else => "formato não suportado", +// }; +// +// return Value{ .string = try alloc.dupe(u8, formatted) }; +// } fn filter_default(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { _ = alloc; @@ -634,12 +655,6 @@ fn filter_make_list(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterE return Value{ .list = try list.toOwnedSlice(alloc) }; } -fn filter_now(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { - _ = value; - if (arg.?.string.len == 0) return Value{ .string = try time.Time.now().toStringAlloc(alloc, "F d, Y") }; - return Value{ .string = try time.Time.now().toStringAlloc(alloc, arg.?.string) }; -} - fn filter_phone2numeric(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { _ = arg; const s = switch (value) { @@ -943,22 +958,45 @@ fn filter_striptags(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterE return Value{ .string = try result.toOwnedSlice(alloc) }; } -fn filter_time(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { - return dateTimeToString(alloc, value, arg); -} - fn filter_timesince(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { - const d = time.Time.parse(value.string) catch return value; - const now = time.Time.parse(arg.?.string) catch return value; + _ = arg; + const then = switch (value) { + .int => |i| @as(i64, i), + else => std_time.timestamp(), + }; - return Value{ .string = try d.timeSince(alloc, now) }; + const now = std_time.timestamp(); + var diff = now - then; + if (diff < 0) diff = -diff; + + if (diff < 60) { + return Value{ .string = try alloc.dupe(u8, "menos de um minuto") }; + } else if (diff < 3600) { + const mins = diff / 60; + const str = if (mins == 1) "1 minuto" else try std.fmt.allocPrint(alloc, "{d} minutos", .{mins}); + return Value{ .string = str }; + } else if (diff < 86400) { + const hours = diff / 3600; + const str = if (hours == 1) "1 hora" else try std.fmt.allocPrint(alloc, "{d} horas", .{hours}); + return Value{ .string = str }; + } else { + const days = diff / 86400; + const str = if (days == 1) "1 dia" else try std.fmt.allocPrint(alloc, "{d} dias", .{days}); + return Value{ .string = str }; + } } fn filter_timeuntil(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { - const d = time.Time.parse(value.string) catch return value; - const now = time.Time.parse(arg.?.string) catch return value; + _ = arg; + // Reutiliza timesince, mas com sinal invertido + const future = switch (value) { + .int => |i| @as(i64, i), + else => std_time.timestamp(), + }; - return Value{ .string = try now.timeSince(alloc, d) }; + const fake_past = Value{ .int = std_time.timestamp() }; + const since = try filter_timesince(alloc, fake_past, Value{ .int = future }); + return since; } fn filter_title(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { @@ -1152,6 +1190,7 @@ fn filter_urlizetrunc(alloc: std.mem.Allocator, value: Value, arg: ?Value) Filte const s = try valueToSafeString(alloc, value); + std.debug.print("{s}\n", .{value.string}); var result = std.ArrayList(u8){}; var i: usize = 0; @@ -1279,20 +1318,6 @@ fn filter_yesno(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError } // ==================== AUX FUNCTIONS ==================== -pub fn dateTimeToString(allocator: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { - if (value.string.len > 0) { - const t: time.Time = try time.Time.parse(value.string); - const arg_str: Value = arg orelse Value{ .string = "F d, Y" }; - const format_str: []const u8 = arg_str.string; - - const result: []const u8 = try t.toStringAlloc(allocator, format_str); - if (result.len > 0) { - return Value{ .string = result }; - } - } - return value; -} - pub fn capFirst(allocator: std.mem.Allocator, input: []const u8) ![]const u8 { if (input.len == 0) return ""; @@ -1437,7 +1462,7 @@ pub const builtin_filters = std.StaticStringMap(*const FilterFn).initComptime(.{ .{ "capfirst", &filter_capfirst }, .{ "center", &filter_center }, .{ "cut", &filter_cut }, - .{ "date", &filter_date }, + // .{ "date", &filter_date }, .{ "default", &filter_default }, .{ "default_if_none", &filter_default_if_none }, .{ "dictsort", &filter_dictsort }, @@ -1462,7 +1487,6 @@ pub const builtin_filters = std.StaticStringMap(*const FilterFn).initComptime(.{ .{ "ljust", &filter_ljust }, .{ "lower", &filter_lower }, .{ "make_list", &filter_make_list }, - .{ "now", &filter_now }, .{ "phone2numeric", &filter_phone2numeric }, .{ "pluralize", &filter_pluralize }, .{ "pprint", &filter_pprint }, @@ -1474,9 +1498,9 @@ pub const builtin_filters = std.StaticStringMap(*const FilterFn).initComptime(.{ .{ "slugify", &filter_slugify }, .{ "stringformat", &filter_stringformat }, .{ "striptags", &filter_striptags }, - .{ "time", &filter_time }, - .{ "timesince", &filter_timesince }, - .{ "timeuntil", &filter_timeuntil }, + // .{ "time", &filter_time }, + // .{ "timesince", &filter_timesince }, + // .{ "timeuntil", &filter_timeuntil }, .{ "title", &filter_title }, .{ "truncatechars", &filter_truncatechars }, .{ "truncatechars_html", &filter_truncatechars_html }, diff --git a/src/filters_test.zig b/src/filters_test.zig index d31cb1b..c5fe77d 100644 --- a/src/filters_test.zig +++ b/src/filters_test.zig @@ -3,16 +3,10 @@ const testing = std.testing; const Value = @import("context.zig").Value; const Context = @import("context.zig").Context; const builtin_filters = @import("filters.zig").builtin_filters; -const filter = @import("filters.zig"); -const FilterError = filter.FilterError; -const time = @import("time.zig"); +const FilterError = @import("filters.zig").FilterError; const std_time = std.time; -const RelativeDelta = @import("delta.zig").RelativeDelta; test "filters upper/lower, capfirst" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("1 - upper/lower, capfirst\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -35,9 +29,6 @@ test "filters upper/lower, capfirst" { } test "builtin filters - add" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("2 - add\n", .{}); - const alloc = testing.allocator; const add = builtin_filters.get("add").?; @@ -58,9 +49,6 @@ test "builtin filters - add" { } test "builtin filters - default and default_if_none" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("3 - default and default_if_none\n", .{}); - const alloc = testing.allocator; const default_filter = builtin_filters.get("default").?; const default_if_none = builtin_filters.get("default_if_none").?; @@ -85,9 +73,6 @@ test "builtin filters - default and default_if_none" { } test "builtin filters - length" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("4 - length\n", .{}); - const alloc = testing.allocator; const length = builtin_filters.get("length").?; @@ -109,10 +94,7 @@ test "builtin filters - length" { try testing.expect((try length(ctx.allocator(), list_val, null)).int == 4); } -test "builtin filters - length with dict" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("5 - length with dict\n", .{}); - +test "builtin filters - length com dict" { const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -132,9 +114,6 @@ test "builtin filters - length with dict" { } test "builtin filters - first and last" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("6 - first and last\n", .{}); - const alloc = testing.allocator; const first = builtin_filters.get("first").?; const last = builtin_filters.get("last").?; @@ -164,9 +143,6 @@ test "builtin filters - first and last" { } test "builtin filters - join" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("7 - join\n", .{}); - const alloc = testing.allocator; const join = builtin_filters.get("join").?; @@ -183,6 +159,7 @@ test "builtin filters - join" { try ctx.set("mixed", mixed); const mixed_val = ctx.get("mixed").?; + const default_join = try join(ctx.allocator(), list_val, null); const custom_join = try join(ctx.allocator(), list_val, sep_dash); const mixed_join = try join(ctx.allocator(), mixed_val, null); @@ -193,9 +170,6 @@ test "builtin filters - join" { } test "builtin filters - yesno" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("8 - yesno\n", .{}); - const alloc = testing.allocator; const yesno = builtin_filters.get("yesno").?; @@ -218,9 +192,6 @@ test "builtin filters - yesno" { } test "builtin filters - truncatechars and truncatechars_html" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("9 - truncatechars and truncatechars_html\n", .{}); - const alloc = testing.allocator; const truncatechars = builtin_filters.get("truncatechars").?; const truncatechars_html = builtin_filters.get("truncatechars_html").?; @@ -247,9 +218,6 @@ test "builtin filters - truncatechars and truncatechars_html" { } test "builtin filters - truncatewords" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("10 - truncatewords\n", .{}); - const alloc = testing.allocator; const truncatewords = builtin_filters.get("truncatewords").?; const truncatewords_html = builtin_filters.get("truncatewords_html").?; @@ -273,9 +241,6 @@ test "builtin filters - truncatewords" { } test "builtin filters - slice" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("11 - slice\n", .{}); - const alloc = testing.allocator; const slice = builtin_filters.get("slice").?; @@ -302,9 +267,6 @@ test "builtin filters - slice" { } test "builtin filters - safe and force_escape" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("12 - safe and force_escape\n", .{}); - const alloc = testing.allocator; const safe = builtin_filters.get("safe").?; const force_escape = builtin_filters.get("force_escape").?; @@ -325,9 +287,6 @@ test "builtin filters - safe and force_escape" { } test "builtin filters - linebreaksbr and linebreaks" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("13 - linebreaksbr and linebreaks\n", .{}); - const alloc = testing.allocator; const linebreaksbr = builtin_filters.get("linebreaksbr").?; const linebreaks = builtin_filters.get("linebreaks").?; @@ -347,9 +306,6 @@ test "builtin filters - linebreaksbr and linebreaks" { } test "builtin filters - escape and force_escape" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("14 - escape and force_escape\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -370,9 +326,6 @@ test "builtin filters - escape and force_escape" { } test "builtin filters - striptags" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("15 - striptags\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -388,9 +341,6 @@ test "builtin filters - striptags" { } test "builtin filters - slugify" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("16 - slugify\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -406,9 +356,6 @@ test "builtin filters - slugify" { } test "builtin filters - floatformat" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("17 - floatformat\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -437,9 +384,6 @@ test "builtin filters - floatformat" { } test "builtin filters - stringformat" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("18 - stringformat\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -460,9 +404,6 @@ test "builtin filters - stringformat" { } test "builtin filters - cut" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("19 - cut\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -478,9 +419,6 @@ test "builtin filters - cut" { } test "builtin filters - title" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("20 - title\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -496,9 +434,6 @@ test "builtin filters - title" { } test "builtin filters - wordcount" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("21 - wordcount\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -514,9 +449,6 @@ test "builtin filters - wordcount" { } test "builtin filters - urlencode" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("22 - urlencode\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -532,9 +464,6 @@ test "builtin filters - urlencode" { } test "builtin filters - pluralize" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("23 - pluralize\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -560,35 +489,31 @@ test "builtin filters - pluralize" { // try testing.expectEqualStrings("", zero.string); } -test "builtin filters - addslashes, center" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("24 - addslashes, center\n", .{}); - +test "builtin filters - addslashes, center, date" { const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); try ctx.set("quote", "He's a good boy"); try ctx.set("texto", "zig"); - try ctx.set("time", time.Time.new(2026, 1, 1, 0, 0, 0)); const v_quote = ctx.get("quote").?; const v_texto = ctx.get("texto").?; const addslashes = builtin_filters.get("addslashes").?; const center = builtin_filters.get("center").?; + // const date = builtin_filters.get("date").?; const slashed = try addslashes(ctx.allocator(), v_quote, null); const centered = try center(ctx.allocator(), v_texto, Value{ .int = 10 }); + // const formatted = try date(ctx.allocator(), Value{ .int = 0 }, Value{ .string = "Y-m-d" }); try testing.expectEqualStrings("He\\'s a good boy", slashed.string); try testing.expectEqualStrings(" zig ", centered.string); + // try testing.expect(std.mem.startsWith(u8, formatted.string, "2026")); } test "builtin filters - dictsort and dictsortreversed" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("25 - dictsort and dictsortreversed\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -619,9 +544,6 @@ test "builtin filters - dictsort and dictsortreversed" { } test "builtin filters - divisibleby, escapejs, filesizeformat, get_digit, json_script" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("26 - divisibleby, escapejs, filesizeformat, get_digit, json_script\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -657,9 +579,6 @@ test "builtin filters - divisibleby, escapejs, filesizeformat, get_digit, json_s } test "builtin filters - escapeseq, iriencode, linenumbers" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("27 - escapeseq, iriencode, linenumbers\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -682,9 +601,6 @@ test "builtin filters - escapeseq, iriencode, linenumbers" { } test "builtin filters - ljust, rjust, center" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("28 - ljust, rjust, center\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -707,9 +623,6 @@ test "builtin filters - ljust, rjust, center" { } test "builtin filters - make_list, phone2numeric, pprint" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("29 - make_list, phone2numeric, pprint\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -735,9 +648,6 @@ test "builtin filters - make_list, phone2numeric, pprint" { } test "builtin filters - random, safeseq" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("30 - random, safeseq\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -760,69 +670,29 @@ test "builtin filters - random, safeseq" { try testing.expect(safe == .list); } -test "builtin filters - date, now, time, timesince, timeuntil" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("31 - date, now, time, timesince, timeuntil\n", .{}); - - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - const times = [_]time.Time{ - time.Time.new(2026, 1, 2, 13, 15, 10), - time.Time.new(2026, 1, 6, 19, 25, 0), - time.Time.new(2026, 1, 2, 13, 35, 0), - time.Time.new(2026, 1, 2, 13, 15, 19), - time.Time.new(2025, 1, 2, 13, 15,19), - time.Time.new(2024, 7, 5, 19, 4, 2), - }; - - const date_filter = builtin_filters.get("date").?; - const now_filter = builtin_filters.get("now").?; - const time_filter = builtin_filters.get("time").?; - const timesince_filter = builtin_filters.get("timesince").?; - const timeuntil_filer = builtin_filters.get("timeuntil").?; - - try ctx.set("dates", times); - const dates = ctx.get("dates").?; - - const date_formated = try date_filter(ctx.allocator(), dates.list[0], Value{ .string = "Y-m-d" }); - const now_formated = try now_filter(ctx.allocator(), dates.list[0], Value{ .string = "Y-m-d" }); - const time_formated = try time_filter(ctx.allocator(), dates.list[0], Value{ .string = "H:i:s" }); - const timesince_formated_1 = try timesince_filter(ctx.allocator(), dates.list[0], dates.list[1]); - const timesince_formated_2 = try timesince_filter(ctx.allocator(), dates.list[0], dates.list[2]); - const timesince_formated_3 = try timesince_filter(ctx.allocator(), dates.list[0], dates.list[3]); - const timesince_formated_4 = try timesince_filter(ctx.allocator(), dates.list[4], dates.list[0]); - const timesince_formated_5 = try timesince_filter(ctx.allocator(), dates.list[5], dates.list[0]); - - const timeuntil_formated_1 = try timeuntil_filer(ctx.allocator(), dates.list[1], dates.list[0]); - const timeuntil_formated_2 = try timeuntil_filer(ctx.allocator(), dates.list[2], dates.list[0]); - const timeuntil_formated_3 = try timeuntil_filer(ctx.allocator(), dates.list[3], dates.list[0]); - const timeuntil_formated_4 = try timeuntil_filer(ctx.allocator(), dates.list[0], dates.list[4]); - const timeuntil_formated_5 = try timeuntil_filer(ctx.allocator(), dates.list[0], dates.list[5]); - - try testing.expectEqualStrings("2026-01-02", date_formated.string); - try testing.expect(isDateFormat(now_formated.string)); - try testing.expectEqualStrings("13:15:10", time_formated.string); - - try testing.expectEqualStrings("4 days, 6 hours",timesince_formated_1.string); - try testing.expectEqualStrings("19 minutes",timesince_formated_2.string); - try testing.expectEqualStrings("0 minutes",timesince_formated_3.string); - try testing.expectEqualStrings("11 months, 4 weeks",timesince_formated_4.string); - try testing.expectEqualStrings("1 year, 5 months",timesince_formated_5.string); - - try testing.expectEqualStrings("4 days, 6 hours",timeuntil_formated_1.string); - try testing.expectEqualStrings("19 minutes",timeuntil_formated_2.string); - try testing.expectEqualStrings("0 minutes",timeuntil_formated_3.string); - try testing.expectEqualStrings("11 months, 4 weeks",timeuntil_formated_4.string); - try testing.expectEqualStrings("1 year, 5 months",timeuntil_formated_5.string); - -} +// test "builtin filters - date, time, timesince, timeuntil" { +// const alloc = testing.allocator; +// var ctx = Context.init(alloc); +// defer ctx.deinit(); +// +// const now = std_time.timestamp(); +// +// // const date = builtin_filters.get("date").?; +// const time = builtin_filters.get("time").?; +// const timesince = builtin_filters.get("timesince").?; +// // const timeuntil = builtin_filters.get("timeuntil").?; +// +// // const d = try date(ctx.allocator(), Value{ .int = now }, Value{ .string = "d/m/Y" }); +// const t = try time(ctx.allocator(), Value{ .int = now }, Value{ .string = "H:i" }); +// +// // try testing.expect(d.string.len > 0); +// try testing.expect(t.string.len > 0); +// +// const since = try timesince(ctx.allocator(), Value{ .int = now - 3600 }, null); +// try testing.expect(std.mem.indexOf(u8, since.string, "hora") != null); +// } test "builtin filters - urlize, urlizetrunc, wordwrap, unordered_list" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("32 - urlize, urlizetrunc, wordwrap, unordered_list\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -844,16 +714,15 @@ test "builtin filters - urlize, urlizetrunc, wordwrap, unordered_list" { const long_text = "Este é um texto muito longo que precisa ser quebrado em várias linhas para caber na largura especificada"; const wrapped = try wordwrap(ctx.allocator(), Value{ .string = long_text }, Value{ .int = 20 }); try testing.expect(std.mem.indexOf(u8, wrapped.string, "\n") != null); + } test "builtin filters - unordered_list" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("33 - unordered_list\n", .{}); - const alloc = testing.allocator; var ctx = Context.init(alloc); defer ctx.deinit(); + const list = [_]Value{ Value{ .string = "item1" }, Value{ .string = "item2" } }; try ctx.set("lista", list); @@ -868,15 +737,6 @@ test "builtin filters - unordered_list" { \\
  • item2
  • \\ ; + std.debug.print("lista gerada: {any}\n", .{ul.string}); try testing.expectEqualStrings(expected, ul.string); } - -fn isDateFormat(txt: []const u8) bool { - if (txt.len != 10) return false; - if (txt[4] != '-' or txt[7] != '-') return false; - for (txt, 0..) |c, i| { - if (i == 4 or i == 7) continue; - if (!std.ascii.isDigit(c)) return false; - } - return true; -} diff --git a/src/lorem.zig b/src/lorem.zig deleted file mode 100644 index b3ad1e0..0000000 --- a/src/lorem.zig +++ /dev/null @@ -1,338 +0,0 @@ -const std = @import("std"); -const rand = std.crypto.random; - -pub const LOREM_COMMON_P = - \\Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod - \\tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim - \\veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea - \\commodo consequat. Duis aute irure dolor in reprehenderit in voluptate - \\velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint - \\occaecat cupidatat non proident, sunt in culpa qui officia deserunt - \\mollit anim id est laborum. -; - -pub const LOREM_WORDS = [182][]const u8{ - "exercitationem", - "perferendis", - "perspiciatis", - "laborum", - "eveniet", - "sunt", - "iure", - "nam", - "nobis", - "eum", - "cum", - "officiis", - "excepturi", - "odio", - "consectetur", - "quasi", - "aut", - "quisquam", - "vel", - "eligendi", - "itaque", - "non", - "odit", - "tempore", - "quaerat", - "dignissimos", - "facilis", - "neque", - "nihil", - "expedita", - "vitae", - "vero", - "ipsum", - "nisi", - "animi", - "cumque", - "pariatur", - "velit", - "modi", - "natus", - "iusto", - "eaque", - "sequi", - "illo", - "sed", - "ex", - "et", - "voluptatibus", - "tempora", - "veritatis", - "ratione", - "assumenda", - "incidunt", - "nostrum", - "placeat", - "aliquid", - "fuga", - "provident", - "praesentium", - "rem", - "necessitatibus", - "suscipit", - "adipisci", - "quidem", - "possimus", - "voluptas", - "debitis", - "sint", - "accusantium", - "unde", - "sapiente", - "voluptate", - "qui", - "aspernatur", - "laudantium", - "soluta", - "amet", - "quo", - "aliquam", - "saepe", - "culpa", - "libero", - "ipsa", - "dicta", - "reiciendis", - "nesciunt", - "doloribus", - "autem", - "impedit", - "minima", - "maiores", - "repudiandae", - "ipsam", - "obcaecati", - "ullam", - "enim", - "totam", - "delectus", - "ducimus", - "quis", - "voluptates", - "dolores", - "molestiae", - "harum", - "dolorem", - "quia", - "voluptatem", - "molestias", - "magni", - "distinctio", - "omnis", - "illum", - "dolorum", - "voluptatum", - "ea", - "quas", - "quam", - "corporis", - "quae", - "blanditiis", - "atque", - "deserunt", - "laboriosam", - "earum", - "consequuntur", - "hic", - "cupiditate", - "quibusdam", - "accusamus", - "ut", - "rerum", - "error", - "minus", - "eius", - "ab", - "ad", - "nemo", - "fugit", - "officia", - "at", - "in", - "id", - "quos", - "reprehenderit", - "numquam", - "iste", - "fugiat", - "sit", - "inventore", - "beatae", - "repellendus", - "magnam", - "recusandae", - "quod", - "explicabo", - "doloremque", - "aperiam", - "consequatur", - "asperiores", - "commodi", - "optio", - "dolor", - "labore", - "temporibus", - "repellat", - "veniam", - "architecto", - "est", - "esse", - "mollitia", - "nulla", - "a", - "similique", - "eos", - "alias", - "dolore", - "tenetur", - "deleniti", - "porro", - "facere", - "maxime", - "corrupti", -}; - -pub const LOREM_COMMON_WORDS = [19][]const u8{ - "lorem", - "ipsum", - "dolor", - "sit", - "amet", - "consectetur", - "adipisicing", - "elit", - "sed", - "do", - "eiusmod", - "tempor", - "incididunt", - "ut", - "labore", - "et", - "dolore", - "magna", - "aliqua", -}; - -pub fn sentence(allocator: std.mem.Allocator) ![]const u8 { - const num_sections = rand.intRangeAtMost(u32, 1, 4); - - var parts = std.ArrayList([]u8){}; - defer { - for (parts.items) |p| allocator.free(p); - parts.deinit(allocator); - } - - var i: u32 = 0; - while (i < num_sections) : (i += 1) { - const num_words = rand.intRangeAtMost(u32, 3, 12); - - var wds = std.ArrayList([]const u8){}; - defer wds.deinit(allocator); - try wds.ensureTotalCapacity(allocator, num_words); - - var j: u32 = 0; - while (j < num_words) : (j += 1) { - const idx = rand.intRangeAtMost(usize, 0, LOREM_WORDS.len - 1); - try wds.append(allocator, LOREM_WORDS[idx]); - } - - const section = try std.mem.join(allocator, " ", wds.items); - try parts.append(allocator, section); - } - - const text = try std.mem.join(allocator, ", ", parts.items); - defer allocator.free(text); - - var result = try allocator.alloc(u8, text.len + 1); - if (text.len > 0) { - result[0] = std.ascii.toUpper(text[0]); - @memcpy(result[1..text.len], text[1..]); - } - result[text.len] = if (rand.boolean()) '.' else '?'; - - return result; -} - -pub fn paragraph(allocator: std.mem.Allocator) ![]const u8 { - const num_sentences = rand.intRangeAtMost(u32, 1, 4); - var sentences = std.ArrayList([]const u8){}; - defer sentences.deinit(allocator); - - for (0..num_sentences) |_| { - try sentences.append(allocator, try sentence(allocator)); - } - - return try std.mem.join(allocator, ". ", sentences.items); -} - -pub fn paragraphs(allocator: std.mem.Allocator, count: u32, random: bool) ![]const u8 { - var pa = std.ArrayList([]const u8){}; - defer pa.deinit(allocator); - - if (count == 0) return ""; - - if (random == true) { - for (0..count) |_| { - const pg = try paragraph(allocator); - if (pg.len > 0) { - try pa.append(allocator, try std.fmt.allocPrint(allocator, "

    {s}

    ", .{pg})); - } - } - - return try std.mem.join(allocator, "\n", pa.items); - } - - const first = try std.fmt.allocPrint(allocator, "

    {s}

    ", .{LOREM_COMMON_P}); - if (count == 1) { - return first; - } - - const ncount: u32 = count - 1; - try pa.append(allocator, first); - - for (0..ncount) |_| { - const pg = try paragraph(allocator); - if (pg.len > 0) { - try pa.append(allocator, try std.fmt.allocPrint(allocator, "

    {s}

    ", .{pg})); - } - } - - return try std.mem.join(allocator, "\n", pa.items); -} - -pub fn words(allocator: std.mem.Allocator, count: u32, random: bool) ![]const u8 { - var wd = std.ArrayList([]const u8){}; - defer wd.deinit(allocator); - - if (random == true) { - for (0..count) |_| { - const idx = rand.intRangeAtMost(usize, 0, LOREM_COMMON_WORDS.len - 1); - try wd.append(allocator, LOREM_COMMON_WORDS[idx]); - } - return try std.mem.join(allocator, " ", wd.items); - } - - var inc: u32 = 0; - - for (LOREM_COMMON_WORDS) |word| { - try wd.append(allocator, word); - inc += 1; - if (inc >= count or inc >= 20) break; - } - - if (count >= 20) { - const ncount = count - inc; - - for (0..ncount) |_| { - const idx = rand.intRangeAtMost(usize, 0, LOREM_COMMON_WORDS.len - 1); - try wd.append(allocator, LOREM_COMMON_WORDS[idx]); - } - } - - return try std.mem.join(allocator, " ", wd.items); -} diff --git a/src/main.zig b/src/main.zig index 80f291f..01d0fd9 100644 --- a/src/main.zig +++ b/src/main.zig @@ -9,40 +9,33 @@ pub fn main() !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); const alloc = arena.allocator(); - // const base = - // \\ - // \\{% block title %}Título Padrão{% endblock %} - // \\ - // \\{% block content %}Conteúdo padrão{% endblock %} - // \\ - // \\ - // ; - // - // const child = - // \\{% extends "base.html" %} - // \\{% block title %}Meu Título{% endblock %} - // \\{% block content %} - // \\Olá {{ nome }}! - // \\{% endblock %} - // ; - // - // try std.fs.cwd().writeFile(.{ - // .sub_path = "base.html", - // .data = base, - // }); - // try std.fs.cwd().writeFile(.{ - // .sub_path = "child.html", - // .data = child, - // }); - // defer std.fs.cwd().deleteFile("base.html") catch {}; - // defer std.fs.cwd().deleteFile("child.html") catch {}; + const base = + \\ + \\{% block title %}Título Padrão{% endblock %} + \\ + \\{% block content %}Conteúdo padrão{% endblock %} + \\ + \\ + ; + const child = + \\{% extends "base.html" %} + \\{% block title %}Meu Título{% endblock %} + \\{% block content %} + \\Olá {{ nome }}! + \\{% endblock %} + ; - const User =struct { - name: []const u8, - email: []const u8, - notifications: i64 = 0 - }; + try std.fs.cwd().writeFile(.{ + .sub_path = "base.html", + .data = base, + }); + try std.fs.cwd().writeFile(.{ + .sub_path = "child.html", + .data = child, + }); + defer std.fs.cwd().deleteFile("base.html") catch {}; + defer std.fs.cwd().deleteFile("child.html") catch {}; var ctx = Context.init(alloc); defer ctx.deinit(); @@ -52,27 +45,12 @@ pub fn main() !void { const renderer = Renderer.init(&ctx, &cache); - const user = User{ - .name = "Lucas", - .email = "lucas@email", - .notifications = 5 - }; - - const itens = [3][]const u8{"Livro", "Caneta", "Caderno"}; - try ctx.set("nome", "Lucas"); - try ctx.set("user", user); - try ctx.set("msg", "Bazinga!"); - try ctx.set("itens", itens); - - for(ctx.get("itens").?.list) |item| { - std.debug.print(" - {s}\n", .{item.string}); - } var buffer = std.ArrayList(u8){}; defer buffer.deinit(ctx.allocator()); - try renderer.render("home.html", buffer.writer(ctx.allocator())); + try renderer.render("child.html", buffer.writer(ctx.allocator())); const output = buffer.items; diff --git a/src/meta.zig b/src/meta.zig deleted file mode 100644 index 723a54d..0000000 --- a/src/meta.zig +++ /dev/null @@ -1,208 +0,0 @@ -// https://github.com/cztomsik/tokamak -const std = @import("std"); -const util = @import("util.zig"); - -// https://github.com/ziglang/zig/issues/19858#issuecomment-2370673253 -// NOTE: I've tried to make it work with enum / packed struct but I was still -// getting weird "operation is runtime due to this operand" here and there -// but it should be possible because we do something similar in util.Smol -pub const TypeId = *const struct { - name: [*:0]const u8, - - pub fn sname(self: *const @This()) []const u8 { - // NOTE: we can't switch (invalid record Zig 0.14.1) - if (self == tid([]const u8)) return "str"; - if (self == tid(?[]const u8)) return "?str"; - return shortName(std.mem.span(self.name), '.'); - } -}; - -pub inline fn tid(comptime T: type) TypeId { - const H = struct { - const id: Deref(TypeId) = .{ .name = @typeName(T) }; - }; - return &H.id; -} - -pub fn tids(comptime types: []const type) []const TypeId { - var buf = util.Buf(TypeId).initComptime(types.len); - for (types) |T| buf.push(tid(T)); - return buf.finish(); -} - -/// Ptr to a comptime value, wrapped together with its type. We use this to -/// pass around values (including a concrete fun types!) during the Bundle -/// compilation. -pub const ComptimeVal = struct { - type: type, - ptr: *const anyopaque, - - pub fn wrap(comptime val: anytype) ComptimeVal { - return .{ .type = @TypeOf(val), .ptr = @ptrCast(&val) }; - } - - pub fn unwrap(self: ComptimeVal) self.type { - return @as(*const self.type, @ptrCast(@alignCast(self.ptr))).*; - } -}; - -pub fn dupe(allocator: std.mem.Allocator, value: anytype) !@TypeOf(value) { - return switch (@typeInfo(@TypeOf(value))) { - .optional => try dupe(allocator, value orelse return null), - .@"struct" => |s| { - var res: @TypeOf(value) = undefined; - inline for (s.fields) |f| @field(res, f.name) = try dupe(allocator, @field(value, f.name)); - return res; - }, - .pointer => |p| switch (p.size) { - .slice => if (p.child == u8) allocator.dupe(p.child, value) else error.NotSupported, - else => value, - }, - else => value, - }; -} - -pub fn free(allocator: std.mem.Allocator, value: anytype) void { - switch (@typeInfo(@TypeOf(value))) { - .optional => if (value) |v| free(allocator, v), - .@"struct" => |s| { - inline for (s.fields) |f| free(allocator, @field(value, f.name)); - }, - .pointer => |p| switch (p.size) { - .slice => if (p.child == u8) allocator.free(value), - else => {}, - }, - else => {}, - } -} - -pub fn upcast(context: anytype, comptime T: type) T { - return .{ - .context = context, - .vtable = comptime brk: { - const Impl = Deref(@TypeOf(context)); - var vtable: T.VTable = undefined; - for (std.meta.fields(T.VTable)) |f| { - @field(vtable, f.name) = @ptrCast(&@field(Impl, f.name)); - } - - const copy = vtable; - break :brk © - }, - }; -} - -pub fn Return(comptime fun: anytype) type { - return switch (@typeInfo(@TypeOf(fun))) { - .@"fn" => |f| f.return_type.?, - else => @compileError("Expected a function, got " ++ @typeName(@TypeOf(fun))), - }; -} - -pub fn Result(comptime fun: anytype) type { - const R = Return(fun); - - return switch (@typeInfo(R)) { - .error_union => |r| r.payload, - else => R, - }; -} - -pub fn LastArg(comptime fun: anytype) type { - const params = @typeInfo(@TypeOf(fun)).@"fn".params; - return params[params.len - 1].type.?; -} - -pub inline fn isStruct(comptime T: type) bool { - return @typeInfo(T) == .@"struct"; -} - -pub inline fn isTuple(comptime T: type) bool { - return switch (@typeInfo(T)) { - .@"struct" => |s| s.is_tuple, - else => false, - }; -} - -pub inline fn isGeneric(comptime fun: anytype) bool { - return @typeInfo(@TypeOf(fun)).@"fn".is_generic; -} - -pub inline fn isOptional(comptime T: type) bool { - return @typeInfo(T) == .optional; -} - -pub inline fn isOnePtr(comptime T: type) bool { - return switch (@typeInfo(T)) { - .pointer => |p| p.size == .one, - else => false, - }; -} - -pub inline fn isSlice(comptime T: type) bool { - return switch (@typeInfo(T)) { - .pointer => |p| p.size == .slice, - else => false, - }; -} - -pub inline fn isString(comptime T: type) bool { - return switch (@typeInfo(T)) { - .pointer => |ptr| ptr.child == u8 or switch (@typeInfo(ptr.child)) { - .array => |arr| arr.child == u8, - else => false, - }, - else => false, - }; -} - -pub fn Deref(comptime T: type) type { - return if (isOnePtr(T)) std.meta.Child(T) else T; -} - -pub fn Unwrap(comptime T: type) type { - return switch (@typeInfo(T)) { - .optional => |o| o.child, - else => T, - }; -} - -pub fn Const(comptime T: type) type { - return switch (@typeInfo(T)) { - .pointer => |p| { - var info = p; - info.is_const = true; - return @Type(.{ .pointer = info }); - }, - else => T, - }; -} - -pub inline fn hasDecl(comptime T: type, comptime name: []const u8) bool { - return switch (@typeInfo(T)) { - .@"struct", .@"union", .@"enum", .@"opaque" => @hasDecl(T, name), - else => false, - }; -} - -pub fn fieldTypes(comptime T: type) []const type { - const fields = std.meta.fields(T); - var buf = util.Buf(type).initComptime(fields.len); - for (fields) |f| buf.push(f.type); - return buf.finish(); -} - -pub fn fnParams(comptime fun: anytype) []const type { - const info = @typeInfo(@TypeOf(fun)); - if (info != .@"fn") @compileError("Expected a function, got " ++ @typeName(@TypeOf(fun))); - - const params = info.@"fn".params; - var buf = util.Buf(type).initComptime(params.len); - for (params) |param| buf.push(param.type.?); - return buf.finish(); -} - -// TODO: move somewhere else? -fn shortName(name: []const u8, delim: u8) []const u8 { - return if (std.mem.lastIndexOfScalar(u8, name, delim)) |i| name[i + 1 ..] else name; -} diff --git a/src/old/renderer.zig b/src/old/renderer.zig new file mode 100644 index 0000000..77a86c9 --- /dev/null +++ b/src/old/renderer.zig @@ -0,0 +1,386 @@ +const std = @import("std"); + +const builtin_filters = @import("filters.zig").builtin_filters; +const Context = @import("context.zig").Context; +const FilterError = @import("filters.zig").FilterError; +const Value = @import("context.zig").Value; + +pub const RenderError = FilterError || error{ + OutOfMemory, + InvalidTemplate, + BlockNotFound, + CircularExtends, + FileNotFound, + AccessDenied, + FileTooBig, + NoSpaceLeft, + Unexpected, +}; + +const TemplateBlock = struct { + name: []const u8, + content: []const u8, +}; + +const TemplateParser = struct { + template: []const u8, + pos: usize = 0, + last_position: usize = 0, + + const Block = struct { + kind: enum { text, variable, tag }, + content: []const u8, + }; + + fn init(template: []const u8) TemplateParser { + return .{ .template = template }; + } + + fn nextBlock(self: *TemplateParser) ?Block { + if (self.pos >= self.template.len) return null; + + const start = self.pos; + + if (std.mem.startsWith(u8, self.template[self.pos..], "{{")) { + self.pos += 2; + const end = std.mem.indexOfPos(u8, self.template, self.pos, "}}") orelse self.template.len; + self.pos = end + 2; + return .{ .kind = .variable, .content = self.template[start + 2 .. end] }; + } else if (std.mem.startsWith(u8, self.template[self.pos..], "{%")) { + self.pos += 2; + const end = std.mem.indexOfPos(u8, self.template, self.pos, "%}") orelse self.template.len; + self.pos = end + 2; + return .{ .kind = .tag, .content = self.template[start + 2 .. end] }; + } else { + const end = std.mem.indexOfAnyPos(u8, self.template, self.pos, "{%") orelse self.template.len; + self.pos = end; + return .{ .kind = .text, .content = self.template[start..end] }; + } + } + + fn skipTo(self: *TemplateParser, tag: []const u8) void { + while (self.nextBlock()) |block| { + if (block.kind == .tag) { + const trimmed = std.mem.trim(u8, block.content, " \t\r\n"); + if (std.mem.eql(u8, trimmed, tag)) { + break; + } + } + } + } + + fn reset(self: *TemplateParser) void { + self.pos = 0; + } + + fn resetToLastPosition(self: *TemplateParser) void { + self.pos = self.last_position; + } +}; + +pub const Renderer = struct { + context: *Context, + blocks: std.ArrayList(TemplateBlock), + allocator: std.mem.Allocator, + + pub fn init(context: *Context) Renderer { + return .{ + .context = context, + .blocks = std.ArrayList(TemplateBlock){}, + .allocator = context.allocator(), + }; + } + + pub fn deinit(self: *Renderer) void { + for (self.blocks.items) |block| { + self.allocator.free(block.name); + } + self.blocks.deinit(); + } + + pub fn render(self: *Renderer, template_path: []const u8, writer: anytype) RenderError!void { + const max_size = 10 * 1024 * 1024; // 10MB + const template = std.fs.cwd().readFileAlloc(self.allocator, template_path, max_size) catch |err| switch (err) { + error.FileNotFound => return RenderError.FileNotFound, + error.AccessDenied => return RenderError.AccessDenied, + error.FileTooBig => return RenderError.FileTooBig, + error.NoSpaceLeft => return RenderError.NoSpaceLeft, + error.OutOfMemory => return RenderError.OutOfMemory, + else => return RenderError.Unexpected, + }; + defer self.allocator.free(template); + + try self.renderTemplate(template, writer); + } + + pub fn renderString(self: *Renderer, template: []const u8, writer: anytype) RenderError!void { + try self.renderTemplate(template, writer); + } + + fn renderTemplate(self: *Renderer, template: []const u8, writer: anytype) RenderError!void { + var parser = TemplateParser.init(template); + + var extends_path: ?[]const u8 = null; + + while (parser.nextBlock()) |block| { + const trimmed = std.mem.trim(u8, block.content, " \t\r\n"); + if (block.kind == .tag) { + if (std.mem.startsWith(u8, trimmed, "extends ")) { + const quoted = trimmed["extends ".len..]; + extends_path = std.mem.trim(u8, quoted, " \"'"); + } else if (std.mem.startsWith(u8, trimmed, "block ")) { + const block_name = std.mem.trim(u8, trimmed["block ".len..], " \t\r\n"); + const name_copy = try self.allocator.dupe(u8, block_name); + + // Agora coletamos o conteúdo real do block + // Começa **após** o fechamento da tag de abertura + const block_start = parser.pos; // já está após o %} + + // Avança até o endblock + parser.skipTo("endblock"); + + // O conteúdo vai do início do corpo até o início do endblock + const endblock_start = parser.pos; + const block_content = template[block_start..endblock_start]; + + try self.blocks.append(self.allocator, .{ .name = name_copy, .content = block_content }); + } + } + } + + if (extends_path) |path| { + const max_size = 10 * 1024 * 1024; + const base_template = std.fs.cwd().readFileAlloc(self.allocator, path, max_size) catch |err| switch (err) { + error.FileNotFound => return RenderError.FileNotFound, + error.AccessDenied => return RenderError.AccessDenied, + error.FileTooBig => return RenderError.FileTooBig, + error.NoSpaceLeft => return RenderError.NoSpaceLeft, + error.OutOfMemory => return RenderError.OutOfMemory, + else => return RenderError.Unexpected, + }; + defer self.allocator.free(base_template); + + var base_parser = TemplateParser.init(base_template); + try self.renderWithInheritance(&base_parser, writer); + } else { + parser.reset(); + try self.renderBlocks(&parser, writer); + } + } + + fn renderWithInheritance(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void { + while (parser.nextBlock()) |block| { + if (block.kind == .tag) { + const trimmed = std.mem.trim(u8, block.content, " \t\r\n"); + if (std.mem.startsWith(u8, trimmed, "block ")) { + const block_name = std.mem.trim(u8, trimmed["block ".len..], " \t\r\n"); + if (self.findBlock(block_name)) |child| { + try writer.writeAll(child.content); + parser.skipTo("endblock"); + } else { + try self.renderBlockContent(parser, writer); + } + } else { + try self.renderTag(block.content, parser, writer); + } + } else { + switch (block.kind) { + .text => try writer.writeAll(block.content), + .variable => try self.renderExpression(block.content, writer), + .tag => unreachable, + } + } + } + } + + fn findBlock(self: *Renderer, name: []const u8) ?TemplateBlock { + for (self.blocks.items) |b| { + if (std.mem.eql(u8, b.name, name)) return b; + } + return null; + } + + fn renderBlockContent(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void { + while (parser.nextBlock()) |block| { + if (block.kind == .tag and std.mem.eql(u8, std.mem.trim(u8, block.content, " \t\r\n"), "endblock")) { + break; + } + switch (block.kind) { + .text => try writer.writeAll(block.content), + .variable => try self.renderExpression(block.content, writer), + .tag => try self.renderTag(block.content, parser, writer), + } + } + } + + fn renderBlocks(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void { + while (parser.nextBlock()) |block| { + switch (block.kind) { + .text => try writer.writeAll(block.content), + .variable => try self.renderExpression(block.content, writer), + .tag => try self.renderTag(block.content, parser, writer), + } + } + } + + /// Renderiza uma expressão: var ou var|filtro|... + fn renderExpression(self: *Renderer, expr: []const u8, writer: anytype) !void { + var parts = std.mem.splitScalar(u8, expr, '|'); + var var_name = parts.next() orelse return; + var_name = std.mem.trim(u8, var_name, " \t\r\n"); + + var value = self.context.get(var_name) orelse Value.null; + var is_safe = false; + + while (parts.next()) |part| { + const trimmed = std.mem.trim(u8, part, " \t\r\n"); + if (trimmed.len == 0) continue; + + const colon_pos = std.mem.indexOf(u8, trimmed, ":"); + const filter_name = if (colon_pos) |c| trimmed[0..c] else trimmed; + const arg_str = if (colon_pos) |c| std.mem.trim(u8, trimmed[c + 1 ..], " \t\r\n") else null; + + if (std.mem.eql(u8, filter_name, "safe")) { + is_safe = true; + continue; + } + + const filter_fn = builtin_filters.get(filter_name) orelse continue; + + const arg = if (arg_str) |a| blk: { + if (std.mem.eql(u8, a, "null")) break :blk Value.null; + if (std.fmt.parseInt(i64, a, 10)) |num| break :blk Value{ .int = num } else |_| {} + break :blk Value{ .string = a }; + } else null; + + value = try filter_fn(self.allocator, value, arg); + } + + // Autoescape: escapa se não for safe + const str = if (is_safe) blk: { + break :blk try self.valueToString(value); + } else blk: { + const escaped = try self.escapeHtml(value); + break :blk try self.valueToString(escaped); + }; + + try writer.writeAll(str); + } + + fn renderTag(self: *Renderer, tag_content: []const u8, parser: *TemplateParser, writer: anytype) !void { + const trimmed = std.mem.trim(u8, tag_content, " \t\r\n"); + + if (std.mem.startsWith(u8, trimmed, "if ")) { + try self.renderIf(trimmed["if ".len..], parser, writer); + } else if (std.mem.startsWith(u8, trimmed, "for ")) { + try self.renderFor(trimmed["for ".len..], parser, writer); + } else if (std.mem.eql(u8, trimmed, "endif") or std.mem.eql(u8, trimmed, "endfor")) { + // Nada a fazer — o bloco já foi consumido no if/for + } else { + // Tag desconhecida — ignora + } + } + + fn renderIf(self: *Renderer, condition_expr: []const u8, parser: *TemplateParser, writer: anytype) !void { + const condition = try self.evaluateCondition(condition_expr); + if (condition) { + try self.renderBlocksUntilEndif(parser, writer); + } else { + parser.skipTo("endif"); // sem try — retorna void + } + } + + fn renderFor(self: *Renderer, for_expr: []const u8, parser: *TemplateParser, writer: anytype) !void { + var parts = std.mem.splitSequence(u8, for_expr, " in "); + const item_name = std.mem.trim(u8, parts.next() orelse return, " \t\r\n"); + const list_expr = std.mem.trim(u8, parts.next() orelse return, " \t\r\n"); + + const list_value = self.context.get(list_expr) orelse Value.null; + const list = switch (list_value) { + .list => |l| l, + else => return, + }; + + for (list) |item| { + try self.context.set(item_name, item); + try self.renderBlocksUntilEndfor(parser, writer); + parser.resetToLastPosition(); + } + } + + fn evaluateCondition(self: *Renderer, expr: []const u8) !bool { + const value = self.context.get(expr) orelse Value.null; + return switch (value) { + .bool => |b| b, + .int => |i| i != 0, + .float => |f| f != 0.0, + .string => |s| s.len > 0, + .list => |l| l.len > 0, + .dict => |d| d.count() > 0, + .null => false, + }; + } + + fn renderBlocksUntilEndif(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void { + while (parser.nextBlock()) |block| { + if (block.kind == .tag and std.mem.eql(u8, std.mem.trim(u8, block.content, " \t\r\n"), "endif")) { + break; + } + switch (block.kind) { + .text => try writer.writeAll(block.content), + .variable => try self.renderExpression(block.content, writer), + .tag => try self.renderTag(block.content, parser, writer), + } + } + } + + fn renderBlocksUntilEndfor(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void { + const start_pos = parser.pos; + while (parser.nextBlock()) |block| { + if (block.kind == .tag and std.mem.eql(u8, std.mem.trim(u8, block.content, " \t\r\n"), "endfor")) { + parser.last_position = start_pos; + break; + } + switch (block.kind) { + .text => try writer.writeAll(block.content), + .variable => try self.renderExpression(block.content, writer), + .tag => try self.renderTag(block.content, parser, writer), + } + } + } + + /// Escapa HTML manualmente (sem depender de filtro externo) + fn escapeHtml(self: *Renderer, value: Value) !Value { + const s = switch (value) { + .string => |str| str, + else => return value, + }; + + var result = std.ArrayList(u8){}; + + for (s) |c| { + switch (c) { + '&' => try result.appendSlice(self.allocator, "&"), + '<' => try result.appendSlice(self.allocator, "<"), + '>' => try result.appendSlice(self.allocator, ">"), + '"' => try result.appendSlice(self.allocator, """), + '\'' => try result.appendSlice(self.allocator, "'"), + else => try result.append(self.allocator, c), + } + } + + return Value{ .string = try result.toOwnedSlice(self.allocator) }; + } + + /// Converte Value para string de forma segura + fn valueToString(self: *Renderer, value: Value) ![]const u8 { + return switch (value) { + .string => |s| s, + .int => |i| try std.fmt.allocPrint(self.allocator, "{d}", .{i}), + .float => |f| try std.fmt.allocPrint(self.allocator, "{d}", .{f}), + .bool => |b| if (b) "true" else "false", + .null => "", + else => "[object]", + }; + } +}; diff --git a/src/old/renderer_bkp.zig b/src/old/renderer_bkp.zig new file mode 100644 index 0000000..0f147e3 --- /dev/null +++ b/src/old/renderer_bkp.zig @@ -0,0 +1,395 @@ +const std = @import("std"); + +const builtin_filters = @import("filters.zig").builtin_filters; +const Context = @import("context.zig").Context; +const FilterError = @import("filters.zig").FilterError; +const Value = @import("context.zig").Value; + +pub const RenderError = FilterError || error{ + OutOfMemory, + InvalidTemplate, + BlockNotFound, + CircularExtends, + FileNotFound, + AccessDenied, + FileTooBig, + NoSpaceLeft, + Unexpected, +}; + +const TemplateBlock = struct { + name: []const u8, + content: []const u8, +}; + +const TemplateParser = struct { + template: []const u8, + pos: usize = 0, + last_position: usize = 0, + + const Block = struct { + kind: enum { text, variable, tag }, + content: []const u8, + }; + + fn init(template: []const u8) TemplateParser { + return .{ .template = template }; + } + + fn nextBlock(self: *TemplateParser) ?Block { + if (self.pos >= self.template.len) return null; + + const start = self.pos; + + if (std.mem.startsWith(u8, self.template[self.pos..], "{{")) { + self.pos += 2; + const end = std.mem.indexOfPos(u8, self.template, self.pos, "}}") orelse self.template.len; + self.pos = end + 2; + return .{ .kind = .variable, .content = self.template[start + 2 .. end] }; + } else if (std.mem.startsWith(u8, self.template[self.pos..], "{%")) { + self.pos += 2; + const end = std.mem.indexOfPos(u8, self.template, self.pos, "%}") orelse self.template.len; + self.pos = end + 2; + return .{ .kind = .tag, .content = self.template[start + 2 .. end] }; + } else { + const end = std.mem.indexOfAnyPos(u8, self.template, self.pos, "{%") orelse self.template.len; + self.pos = end; + return .{ .kind = .text, .content = self.template[start..end] }; + } + } + + fn skipTo(self: *TemplateParser, tag: []const u8) void { + while (self.nextBlock()) |block| { + if (block.kind == .tag) { + const trimmed = std.mem.trim(u8, block.content, " \t\r\n"); + if (std.mem.eql(u8, trimmed, tag)) { + break; + } + } + } + } + + fn reset(self: *TemplateParser) void { + self.pos = 0; + } + + fn resetToLastPosition(self: *TemplateParser) void { + self.pos = self.last_position; + } +}; + +pub const Renderer = struct { + context: *Context, + blocks: std.ArrayList(TemplateBlock), + + pub fn init(context: *Context) Renderer { + return .{ + .context = context, + .blocks = std.ArrayList(TemplateBlock){}, + }; + } + + pub fn deinit(self: *Renderer) void { + for (self.blocks.items) |block| { + self.context.allocator().free(block.name); + } + self.blocks.deinit(); + } + + pub fn render(self: *Renderer, template_path: []const u8, writer: anytype) RenderError!void { + const max_size = 10 * 1024 * 1024; // 10MB + const template = std.fs.cwd().readFileAlloc(self.context.allocator(), template_path, max_size) catch |err| switch (err) { + error.FileNotFound => return RenderError.FileNotFound, + error.AccessDenied => return RenderError.AccessDenied, + error.FileTooBig => return RenderError.FileTooBig, + error.NoSpaceLeft => return RenderError.NoSpaceLeft, + error.OutOfMemory => return RenderError.OutOfMemory, + else => return RenderError.Unexpected, + }; + defer self.context.allocator().free(template); + + try self.renderTemplate(template, writer); + } + + pub fn renderString(self: *Renderer, template: []const u8, writer: anytype) RenderError!void { + try self.renderTemplate(template, writer); + } + + fn renderTemplate(self: *Renderer, template: []const u8, writer: anytype) RenderError!void { + var parser = TemplateParser.init(template); + + var extends_path: ?[]const u8 = null; + + while (parser.nextBlock()) |block| { + const trimmed = std.mem.trim(u8, block.content, " \t\r\n"); + if (block.kind == .tag) { + if (std.mem.startsWith(u8, trimmed, "extends ")) { + const quoted = trimmed["extends ".len..]; + extends_path = std.mem.trim(u8, quoted, " \"'"); + } else if (std.mem.startsWith(u8, trimmed, "block ")) { + const block_name = std.mem.trim(u8, trimmed["block ".len..], " \t\r\n"); + const name_copy = try self.context.allocator().dupe(u8, block_name); + + // Agora extrai o conteúdo real do block (após o nome até endblock) + const block_start = parser.pos; // já está após o %} + parser.skipTo("endblock"); + const endblock_start = parser.pos; // início do {% endblock %} + const block_content = template[block_start..endblock_start]; + + try self.blocks.append(self.context.allocator(), .{ .name = name_copy, .content = block_content }); + } + } else if (std.mem.startsWith(u8, trimmed, "block ")) { + const block_name = std.mem.trim(u8, trimmed["block ".len..], " \t\r\n"); + const name_copy = try self.context.allocator().dupe(u8, block_name); + + // Agora coletamos o conteúdo real do block + // Começa **após** o fechamento da tag de abertura + const block_start = parser.pos; // já está após o %} + + // Avança até o endblock + parser.skipTo("endblock"); + + // O conteúdo vai do início do corpo até o início do endblock + const endblock_start = parser.pos; + const block_content = template[block_start..endblock_start]; + + try self.blocks.append(self.context.allocator(), .{ .name = name_copy, .content = block_content }); + } + } + + if (extends_path) |path| { + const max_size = 10 * 1024 * 1024; + const base_template = std.fs.cwd().readFileAlloc(self.context.allocator(), path, max_size) catch |err| switch (err) { + error.FileNotFound => return RenderError.FileNotFound, + error.AccessDenied => return RenderError.AccessDenied, + error.FileTooBig => return RenderError.FileTooBig, + error.NoSpaceLeft => return RenderError.NoSpaceLeft, + error.OutOfMemory => return RenderError.OutOfMemory, + else => return RenderError.Unexpected, + }; + defer self.context.allocator().free(base_template); + + var base_parser = TemplateParser.init(base_template); + try self.renderWithInheritance(&base_parser, writer); + } else { + parser.reset(); + try self.renderBlocks(&parser, writer); + } + } + + fn renderWithInheritance(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void { + while (parser.nextBlock()) |block| { + if (block.kind == .tag) { + const trimmed = std.mem.trim(u8, block.content, " \t\r\n"); + if (std.mem.startsWith(u8, trimmed, "block ")) { + const block_name = std.mem.trim(u8, trimmed["block ".len..], " \t\r\n"); + if (self.findBlock(block_name)) |child| { + try writer.writeAll(child.content); + parser.skipTo("endblock"); + } else { + try self.renderBlockContent(parser, writer); + } + } else { + try self.renderTag(block.content, parser, writer); + } + } else { + switch (block.kind) { + .text => try writer.writeAll(block.content), + .variable => try self.renderExpression(block.content, writer), + .tag => unreachable, + } + } + } + } + + fn findBlock(self: *Renderer, name: []const u8) ?TemplateBlock { + for (self.blocks.items) |b| { + if (std.mem.eql(u8, b.name, name)) return b; + } + return null; + } + + fn renderBlockContent(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void { + while (parser.nextBlock()) |block| { + if (block.kind == .tag and std.mem.eql(u8, std.mem.trim(u8, block.content, " \t\r\n"), "endblock")) { + break; + } + switch (block.kind) { + .text => try writer.writeAll(block.content), + .variable => try self.renderExpression(block.content, writer), + .tag => try self.renderTag(block.content, parser, writer), + } + } + } + + fn renderBlocks(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void { + while (parser.nextBlock()) |block| { + switch (block.kind) { + .text => try writer.writeAll(block.content), + .variable => try self.renderExpression(block.content, writer), + .tag => try self.renderTag(block.content, parser, writer), + } + } + } + + /// Renderiza uma expressão: var ou var|filtro|... + fn renderExpression(self: *Renderer, expr: []const u8, writer: anytype) !void { + var parts = std.mem.splitScalar(u8, expr, '|'); + var var_name = parts.next() orelse return; + var_name = std.mem.trim(u8, var_name, " \t\r\n"); + + var value = self.context.get(var_name) orelse Value.null; + var is_safe = false; + + while (parts.next()) |part| { + const trimmed = std.mem.trim(u8, part, " \t\r\n"); + if (trimmed.len == 0) continue; + + const colon_pos = std.mem.indexOf(u8, trimmed, ":"); + const filter_name = if (colon_pos) |c| trimmed[0..c] else trimmed; + const arg_str = if (colon_pos) |c| std.mem.trim(u8, trimmed[c + 1 ..], " \t\r\n") else null; + + if (std.mem.eql(u8, filter_name, "safe")) { + is_safe = true; + continue; + } + + const filter_fn = builtin_filters.get(filter_name) orelse continue; + + const arg = if (arg_str) |a| blk: { + if (std.mem.eql(u8, a, "null")) break :blk Value.null; + if (std.fmt.parseInt(i64, a, 10)) |num| break :blk Value{ .int = num } else |_| {} + break :blk Value{ .string = a }; + } else null; + + value = try filter_fn(self.context.allocator(), value, arg); + } + + // Autoescape: escapa se não for safe + const str = if (is_safe) blk: { + break :blk try self.valueToString(value); + } else blk: { + const escaped = try self.escapeHtml(value); + break :blk try self.valueToString(escaped); + }; + + try writer.writeAll(str); + } + + fn renderTag(self: *Renderer, tag_content: []const u8, parser: *TemplateParser, writer: anytype) !void { + const trimmed = std.mem.trim(u8, tag_content, " \t\r\n"); + + if (std.mem.startsWith(u8, trimmed, "if ")) { + try self.renderIf(trimmed["if ".len..], parser, writer); + } else if (std.mem.startsWith(u8, trimmed, "for ")) { + try self.renderFor(trimmed["for ".len..], parser, writer); + } else if (std.mem.eql(u8, trimmed, "endif") or std.mem.eql(u8, trimmed, "endfor")) { + // Nada a fazer — o bloco já foi consumido no if/for + } else { + // Tag desconhecida — ignora + } + } + + fn renderIf(self: *Renderer, condition_expr: []const u8, parser: *TemplateParser, writer: anytype) !void { + const condition = try self.evaluateCondition(condition_expr); + if (condition) { + try self.renderBlocksUntilEndif(parser, writer); + } else { + parser.skipTo("endif"); // sem try — retorna void + } + } + + fn renderFor(self: *Renderer, for_expr: []const u8, parser: *TemplateParser, writer: anytype) !void { + var parts = std.mem.splitSequence(u8, for_expr, " in "); + const item_name = std.mem.trim(u8, parts.next() orelse return, " \t\r\n"); + const list_expr = std.mem.trim(u8, parts.next() orelse return, " \t\r\n"); + + const list_value = self.context.get(list_expr) orelse Value.null; + const list = switch (list_value) { + .list => |l| l, + else => return, + }; + + for (list) |item| { + try self.context.set(item_name, item); + try self.renderBlocksUntilEndfor(parser, writer); + parser.resetToLastPosition(); + } + } + + fn evaluateCondition(self: *Renderer, expr: []const u8) !bool { + const value = self.context.get(expr) orelse Value.null; + return switch (value) { + .bool => |b| b, + .int => |i| i != 0, + .float => |f| f != 0.0, + .string => |s| s.len > 0, + .list => |l| l.len > 0, + .dict => |d| d.count() > 0, + .null => false, + }; + } + + fn renderBlocksUntilEndif(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void { + while (parser.nextBlock()) |block| { + if (block.kind == .tag and std.mem.eql(u8, std.mem.trim(u8, block.content, " \t\r\n"), "endif")) { + break; + } + switch (block.kind) { + .text => try writer.writeAll(block.content), + .variable => try self.renderExpression(block.content, writer), + .tag => try self.renderTag(block.content, parser, writer), + } + } + } + + fn renderBlocksUntilEndfor(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void { + const start_pos = parser.pos; + while (parser.nextBlock()) |block| { + if (block.kind == .tag and std.mem.eql(u8, std.mem.trim(u8, block.content, " \t\r\n"), "endfor")) { + parser.last_position = start_pos; + break; + } + switch (block.kind) { + .text => try writer.writeAll(block.content), + .variable => try self.renderExpression(block.content, writer), + .tag => try self.renderTag(block.content, parser, writer), + } + } + } + + /// Escapa HTML manualmente (sem depender de filtro externo) + fn escapeHtml(self: *Renderer, value: Value) !Value { + const s = switch (value) { + .string => |str| str, + else => return value, + }; + + var result = std.ArrayList(u8){}; + + for (s) |c| { + switch (c) { + '&' => try result.appendSlice(self.context.allocator(), "&"), + '<' => try result.appendSlice(self.context.allocator(), "<"), + '>' => try result.appendSlice(self.context.allocator(), ">"), + '"' => try result.appendSlice(self.context.allocator(), """), + '\'' => try result.appendSlice(self.context.allocator(), "'"), + else => try result.append(self.context.allocator(), c), + } + } + + return Value{ .string = try result.toOwnedSlice(self.context.allocator()) }; + } + + /// Converte Value para string de forma segura + fn valueToString(self: *Renderer, value: Value) ![]const u8 { + return switch (value) { + .string => |s| s, + .int => |i| try std.fmt.allocPrint(self.context.allocator(), "{d}", .{i}), + .float => |f| try std.fmt.allocPrint(self.context.allocator(), "{d}", .{f}), + .bool => |b| if (b) "true" else "false", + .null => "", + else => "[object]", + }; + } +}; diff --git a/src/old/renderer_test.zig b/src/old/renderer_test.zig new file mode 100644 index 0000000..1d1b504 --- /dev/null +++ b/src/old/renderer_test.zig @@ -0,0 +1,156 @@ +const std = @import("std"); +const testing = std.testing; +const Context = @import("context.zig").Context; +const Renderer = @import("renderer.zig").Renderer; +const Value = @import("context.zig").Value; + +test "renderer - texto literal e variável simples" { + const alloc = testing.allocator; + var ctx = Context.init(alloc); + defer ctx.deinit(); + + try ctx.set("nome", "Fulano"); + try ctx.set("idade", 30); + + var renderer = Renderer.init(&ctx); + + const template = "Olá {{ nome }}! Você tem {{ idade }} anos."; + + var buffer = std.ArrayList(u8){}; + defer buffer.deinit(alloc); + + try renderer.renderString(template, buffer.writer(alloc)); + + try testing.expectEqualStrings("Olá Fulano! Você tem 30 anos.", buffer.items); +} + +test "renderer - filtros com argumentos" { + const alloc = testing.allocator; + var ctx = Context.init(alloc); + defer ctx.deinit(); + + try ctx.set("titulo", "Olá Mundo Legal"); + + var renderer = Renderer.init(&ctx); + + const template = "{{ titulo|lower|slugify }}"; + + var buffer = std.ArrayList(u8){}; + defer buffer.deinit(alloc); + + try renderer.renderString(template, buffer.writer(alloc)); + + try testing.expectEqualStrings("ola-mundo-legal", buffer.items); +} + +test "renderer - literal, variável e autoescape" { + const alloc = testing.allocator; + var ctx = Context.init(alloc); + defer ctx.deinit(); + + try ctx.set("nome", "Lucas"); + try ctx.set("xss", ""); + try ctx.set("html", "negrito"); + + var renderer = Renderer.init(&ctx); + + const template = "Olá {{ nome }}! Perigo: {{ xss }} Seguro: {{ html|safe }}"; + + var buffer = std.ArrayList(u8){}; + defer buffer.deinit(alloc); + + try renderer.renderString(template, buffer.writer(alloc)); + + const output = buffer.items; + + try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas!") != null); + try testing.expect(std.mem.indexOf(u8, output, "<script>") != null); // escapado + try testing.expect(std.mem.indexOf(u8, output, "negrito") != null); // safe +} + +test "renderer - if and for" { + const alloc = testing.allocator; + var ctx = Context.init(alloc); + defer ctx.deinit(); + + try ctx.set("ativo", true); + try ctx.set("inativo", false); + try ctx.set("nomes", [_]Value{ Value{ .string = "Ana" }, Value{ .string = "Bia" }, Value{ .string = "Cris" } }); + + var renderer = Renderer.init(&ctx); + + const template = + \\{% if ativo %}Sim!{% endif %} + \\{% if inativo %}Não{% endif %} + \\Lista: + \\{% for nome in nomes %} + \\- {{ nome }} + \\{% endfor %} + ; + + var buffer = std.ArrayList(u8){}; + defer buffer.deinit(alloc); + + try renderer.renderString(template, buffer.writer(alloc)); + + const output = buffer.items; + + try testing.expect(std.mem.indexOf(u8, output, "Sim!") != null); + try testing.expect(std.mem.indexOf(u8, output, "Não") == null); + try testing.expect(std.mem.indexOf(u8, output, "- Ana") != null); + try testing.expect(std.mem.indexOf(u8, output, "- Bia") != null); + try testing.expect(std.mem.indexOf(u8, output, "- Cris") != null); +} + +// test "renderer - block and extends" { +// const alloc = testing.allocator; +// +// const base = +// \\ +// \\{% block title %}Título Padrão{% endblock %} +// \\ +// \\{% block content %}Conteúdo padrão{% endblock %} +// \\ +// \\ +// ; +// +// const child = +// \\{% extends "base.html" %} +// \\{% block title %}Meu Título{% endblock %} +// \\{% block content %} +// \\Olá {{ nome }}! +// \\{% endblock %} +// ; +// +// try std.fs.cwd().writeFile(.{ +// .sub_path = "base.html", +// .data = base, +// }); +// try std.fs.cwd().writeFile(.{ +// .sub_path = "child.html", +// .data = child, +// }); +// defer std.fs.cwd().deleteFile("base.html") catch {}; +// defer std.fs.cwd().deleteFile("child.html") catch {}; +// +// var ctx = Context.init(alloc); +// defer ctx.deinit(); +// +// try ctx.set("nome", "Lucas"); +// +// var renderer = Renderer.init(&ctx); +// +// var buffer = std.ArrayList(u8){}; +// defer buffer.deinit(ctx.allocator()); +// +// try renderer.render("child.html", buffer.writer(ctx.allocator())); +// +// const output = buffer.items; +// +// std.debug.print("{s}", .{output}); +// +// try testing.expect(std.mem.indexOf(u8, output, "Meu Título") != null); +// try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas!") != null); +// try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") == null); +// try testing.expect(std.mem.indexOf(u8, output, "Título Padrão") == null); +// } diff --git a/src/parser.zig b/src/parser.zig index c87158e..7332b9e 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -1,151 +1,36 @@ const std = @import("std"); -const RenderError = @import("renderer.zig").RenderError; -pub const ParserError = error{ - InvalidAssignmentSyntax, - InvalidAutoescapeArgument, - InvalidCsrfTokenArgs, - InvalidDebugArgs, - InvalidForSyntax, - InvalidRegroupSyntax, - InvalidTemplateTag, - InvalidUrlSyntax, - InvalidWidthRatioSyntax, - OutOfMemory, - ParseError, - UnclosedBlock, - UnclosedComment, - UnclosedQuoteInAssignment, - UnclosedQuoteInUrl, - UnclosedTag, - UnclosedVariable, - UnclosedVerbatim, - UnexpectedToken, -}; - -pub const TagNodeBody = union(enum) { - autoescape: AutoescapeNode, - block: BlockNode, - csrf_token: bool, - cycle: CycleNode, - debug: bool, - extends: ExtendsNode, - filter_block: FilterBlockNode, - firstof: FirstOfNode, - @"for": ForNode, - @"if": IfNode, - include: IncludeNode, - load: LoadNode, - lorem: LoremNode, - now: NowNode, - partial: PartialNode, - partialdef: PartialDefNode, - querystring: QueryStringNode, - regroup: RegroupNode, - resetcycle: ResetCycleNode, - spaceless: SpacelessNode, - super: bool, - svg: SvgNode, - templatetag: TemplateTagNode, - url: UrlNode, - verbatim: VerbatimNode, - widthratio: WidthRatioNode, - with: WithNode, - initial: bool, -}; - -pub const TagKind = enum { - autoescape, - block, - csrf_token, - comment, - cycle, - debug, - else_block, - empty, - endautoescape, - endblock, - endfilter, - endfor_block, - endif_block, - endpartialdef, - endspaceless, - endverbatim, - endwith_block, - extends, - filter_block, - firstof, - for_block, +pub const NodeType = enum { + text, + variable, + tag, if_block, + for_block, include, - initial, - load, - lorem, + with_block, now, - partial, + extends, + block, + super, + filter_block, + autoescape, + spaceless, + url, + cycle, + firstof, + load, + csrf_token, + lorem, + debug, partialdef, + partial, querystring, regroup, resetcycle, - spaceless, - super, - svg, - templatetag, - url, - verbatim, widthratio, - with_block, + templatetag, }; -pub const NodeType = enum { - tag, - text, - variable, -}; - -fn getTagKindByName(name: []const u8) TagKind { - if (std.mem.eql(u8, name, "autoescape")) return .autoescape; - if (std.mem.eql(u8, name, "block")) return .block; - if (std.mem.eql(u8, name, "csrf_token")) return .csrf_token; - if (std.mem.eql(u8, name, "comment")) return .comment; - if (std.mem.eql(u8, name, "cycle")) return .cycle; - if (std.mem.eql(u8, name, "debug")) return .debug; - if (std.mem.eql(u8, name, "else")) return .else_block; - if (std.mem.eql(u8, name, "empty")) return .empty; - if (std.mem.eql(u8, name, "endautoescape")) return .endautoescape; - if (std.mem.eql(u8, name, "endblock")) return .endblock; - if (std.mem.eql(u8, name, "endfilter")) return .endfilter; - if (std.mem.eql(u8, name, "endfor")) return .endfor_block; - if (std.mem.eql(u8, name, "endif")) return .endif_block; - if (std.mem.eql(u8, name, "endpartialdef")) return .endpartialdef; - if (std.mem.eql(u8, name, "endspaceless")) return .endspaceless; - if (std.mem.eql(u8, name, "endverbatim")) return .endverbatim; - if (std.mem.eql(u8, name, "endwith")) return .endwith_block; - if (std.mem.eql(u8, name, "extends")) return .extends; - if (std.mem.eql(u8, name, "filter")) return .filter_block; - if (std.mem.eql(u8, name, "firstof")) return .firstof; - if (std.mem.eql(u8, name, "for")) return .for_block; - if (std.mem.eql(u8, name, "if")) return .if_block; - if (std.mem.eql(u8, name, "include")) return .include; - if (std.mem.eql(u8, name, "load")) return .load; - if (std.mem.eql(u8, name, "lorem")) return .lorem; - if (std.mem.eql(u8, name, "now")) return .now; - if (std.mem.eql(u8, name, "partial")) return .partial; - if (std.mem.eql(u8, name, "partialdef")) return .partialdef; - if (std.mem.eql(u8, name, "querystring")) return .querystring; - if (std.mem.eql(u8, name, "regroup")) return .regroup; - if (std.mem.eql(u8, name, "resetcycle")) return .resetcycle; - if (std.mem.eql(u8, name, "spaceless")) return .spaceless; - if (std.mem.eql(u8, name, "super")) return .super; - if (std.mem.eql(u8, name, "svg")) return .svg; - if (std.mem.eql(u8, name, "templatetag")) return .templatetag; - if (std.mem.eql(u8, name, "url")) return .url; - if (std.mem.eql(u8, name, "verbatim")) return .verbatim; - if (std.mem.eql(u8, name, "widthratio")) return .widthratio; - if (std.mem.eql(u8, name, "with")) return .with_block; - return .initial; -} - pub const TemplateTagNode = struct { kind: enum { openblock, closeblock, openvariable, closevariable, openbrace, closebrace, opencomment, closecomment }, @@ -163,84 +48,24 @@ pub const TemplateTagNode = struct { } }; -pub const Assignment = struct { - key: []const u8, - value_expr: []const u8, - is_literal: bool, +pub const WidthRatioNode = struct { + value: []const u8, + max_value: []const u8, + divisor: ?[]const u8 = null, // opcional }; -pub const AutoescapeNode = struct { - body: []Node, - raw_open: []const u8, - raw_close: []const u8, - enabled: bool, +pub const ResetCycleNode = struct { + cycle_name: ?[]const u8 = null, // null = reseta todos, ou nome específico }; -pub const BlockNode = struct { - name: []const u8, - body: []Node, - raw_open: []const u8, - raw_close: []const u8, +pub const RegroupNode = struct { + source: []const u8, + by: []const u8, + as_var: []const u8, }; -pub const CycleNode = struct { - values: []const []const u8, -}; - -pub const ExtendsNode = struct { - parent_name: []const u8, -}; - -pub const Filter = struct { - name: []const u8, - arg: ?[]const u8 = null, -}; - -pub const FilterBlockNode = struct { - filters: []const u8, // "upper|escape|truncatewords:30" - body: []Node, - raw_open: []const u8, - raw_close: []const u8, -}; - -pub const FirstOfNode = struct { - values: []const []const u8, - fallback: []const u8, -}; - -pub const ForNode = struct { - loop_var: []const u8, - iterable: []const u8, - body: []Node, - empty_body: []Node, - raw_open: []const u8, - raw_close: []const u8, -}; - -pub const IfNode = struct { - condition: []const u8, - true_body: []Node, - false_body: []Node, - raw_open: []const u8, - raw_close: []const u8, -}; - -pub const IncludeNode = struct { - template_path: []const u8, -}; - -pub const LoadNode = struct { - libraries: []const []const u8, -}; - -pub const LoremNode = struct { - count: ?[]const u8 = null, // "3" ou null - method: ?[]const u8 = null, // "p" ou "w" ou null - random: bool = false, -}; - -pub const NowNode = struct { - format: []const u8, +pub const QueryStringNode = struct { + modifications: []const []const u8, // array de "key=value" ou "key=None" }; pub const PartialDefNode = struct { @@ -254,18 +79,34 @@ pub const PartialNode = struct { name: []const u8, }; -pub const QueryStringNode = struct { - modifications: []const []const u8, // array de "key=value" ou "key=None" +pub const LoremNode = struct { + count: ?[]const u8 = null, // "3" ou null + method: ?[]const u8 = null, // "p" ou "w" ou null + format: ?[]const u8 = null, // "html" ou null }; -pub const RegroupNode = struct { - source: []const u8, - by: []const u8, - as_var: []const u8, +pub const LoadNode = struct { + libraries: []const []const u8, }; -pub const ResetCycleNode = struct { - cycle_name: ?[]const u8 = null, // null = reseta todos, ou nome específico +pub const AutoescapeNode = struct { + body: []Node, + raw_open: []const u8, + raw_close: []const u8, + enabled: bool, +}; + +pub const FirstOfNode = struct { + values: []const []const u8, +}; + +pub const CycleNode = struct { + values: []const []const u8, +}; + +pub const UrlNode = struct { + name: []const u8, + args: []const []const u8, }; pub const SpacelessNode = struct { @@ -274,43 +115,42 @@ pub const SpacelessNode = struct { raw_close: []const u8, }; -pub const SvgNode = struct { - kind: []const u8, - name: []const u8, -}; - -pub const TagNode = struct { - kind: TagKind, - args: []const u8, - raw: []const u8, - body: TagNodeBody, -}; - -pub const TextNode = struct { - content: []const u8, -}; - -pub const UrlNode = struct { - name: []const u8, - args: []const []const u8, -}; - -pub const VerbatimNode = struct { - name: ?[]const u8, - content: []const u8, +pub const FilterBlockNode = struct { + filters: []const u8, // "upper|escape|truncatewords:30" + body: []Node, raw_open: []const u8, raw_close: []const u8, }; +pub const Filter = struct { + name: []const u8, + arg: ?[]const u8 = null, // null ou string com argumento +}; + pub const VariableNode = struct { expr: []const u8, filters: []const Filter, }; -pub const WidthRatioNode = struct { - value: []const u8, - max_value: []const u8, - divisor: ?[]const u8 = null, // opcional +pub const NowNode = struct { + format: []const u8, +}; + +pub const ExtendsNode = struct { + parent_name: []const u8, +}; + +pub const BlockNode = struct { + name: []const u8, + body: []Node, + raw_open: []const u8, + raw_close: []const u8, +}; + +pub const Assignment = struct { + key: []const u8, + value_expr: []const u8, + is_literal: bool, }; pub const WithNode = struct { @@ -320,102 +160,203 @@ pub const WithNode = struct { raw_close: []const u8, }; +pub const IncludeNode = struct { + template_name: []const u8, +}; + +pub const TextNode = struct { + content: []const u8, +}; + +pub const TagNode = struct { + name: []const u8, + args: []const u8, + raw: []const u8, +}; + +pub const IfNode = struct { + condition: []const u8, // dupe esse sim + true_body: []Node, + false_body: []Node, + raw_open: []const u8, // slice original, NÃO free + raw_close: []const u8, // slice original, NÃO free +}; + +pub const ForNode = struct { + loop_var: []const u8, + iterable: []const u8, + body: []Node, + empty_body: []Node, + raw_open: []const u8, + raw_close: []const u8, +}; + pub const Node = struct { type: NodeType, text: ?TextNode = null, variable: ?VariableNode = null, tag: ?TagNode = null, + @"if": ?IfNode = null, + @"for": ?ForNode = null, + include: ?IncludeNode = null, + with: ?WithNode = null, + now: ?NowNode = null, + extends: ?ExtendsNode = null, + block: ?BlockNode = null, + super: bool = false, // para {{ block.super }} + filter_block: ?FilterBlockNode = null, + autoescape: ?AutoescapeNode = null, + spaceless: ?SpacelessNode = null, + url: ?UrlNode = null, + cycle: ?CycleNode = null, + firstof: ?FirstOfNode = null, + load: ?LoadNode = null, + lorem: ?LoremNode = null, + partialdef: ?PartialDefNode = null, + partial: ?PartialNode = null, + querystring: ?QueryStringNode = null, + regroup: ?RegroupNode = null, + resetcycle: ?ResetCycleNode = null, + widthratio: ?WidthRatioNode = null, + templatetag: ?TemplateTagNode = null, + csrf_token: bool = false, + debug: bool = false, pub fn deinit(self: Node, allocator: std.mem.Allocator) void { switch (self.type) { - // .text => if (self.text) |t| allocator.free(t.content), - .text => {}, + .text => if (self.text) |t| allocator.free(t.content), .variable => if (self.variable) |v| { + allocator.free(v.expr); + for (v.filters) |f| { + allocator.free(f.name); + if (f.arg) |a| allocator.free(a); + } allocator.free(v.filters); }, - .tag => { - if (self.tag) |t| { - switch (t.body) { - .autoescape => { - allocator.free(t.body.autoescape.body); - }, - .block => { - const body_copy = t.body.block.body; - for (body_copy) |n| n.deinit(allocator); - allocator.free(body_copy); - }, - .cycle => { - allocator.free(t.body.cycle.values); - }, - .filter_block => { - const body_copy = t.body.filter_block.body; - for (body_copy) |n| n.deinit(allocator); - allocator.free(body_copy); - }, - .firstof => { - allocator.free(t.body.firstof.values); - }, - .@"for" => { - const body_copy = t.body.@"for".body; - for (body_copy) |n| n.deinit(allocator); - const empty_body_copy = t.body.@"for".empty_body; - for (empty_body_copy) |n| n.deinit(allocator); - allocator.free(body_copy); - allocator.free(empty_body_copy); - }, - .@"if" => { - const true_copy = t.body.@"if".true_body; - for (true_copy) |n| n.deinit(allocator); - const false_copy = t.body.@"if".false_body; - for (false_copy) |n| n.deinit(allocator); - allocator.free(true_copy); - allocator.free(false_copy); - }, - .load => { - allocator.free(t.body.load.libraries); - }, - .partialdef => { - const body_copy = t.body.partialdef.body; - for (body_copy) |n| n.deinit(allocator); - allocator.free(body_copy); - }, - .querystring => { - allocator.free(t.body.querystring.modifications); - }, - .spaceless => { - const body_copy = t.body.spaceless.body; - for (body_copy) |n| n.deinit(allocator); - allocator.free(body_copy); - }, - .svg => { - allocator.free(t.body.svg.kind); - allocator.free(t.body.svg.name); - }, - .url => { - allocator.free(t.body.url.args); - }, - .with => { - const body_copy = t.body.with.body; - for (body_copy) |n| n.deinit(allocator); - allocator.free(body_copy); - allocator.free(t.body.with.assignments); - }, - else => {}, - } - } + .tag => if (self.tag) |t| { + allocator.free(t.name); + allocator.free(t.args); + allocator.free(t.raw); }, - // .with_block => if (self.with) |w| { - // for (w.assignments) |a| { - // allocator.free(a.key); - // allocator.free(a.value_expr); - // } - // allocator.free(w.assignments); - // const body_copy = w.body; - // for (body_copy) |n| n.deinit(allocator); - // allocator.free(w.body); - // allocator.free(w.raw_open); - // allocator.free(w.raw_close); - // }, + .if_block => if (self.@"if") |ib| { + allocator.free(ib.condition); + const true_copy = ib.true_body; + for (true_copy) |n| n.deinit(allocator); + allocator.free(ib.true_body); + const false_copy = ib.false_body; + for (false_copy) |n| n.deinit(allocator); + allocator.free(ib.false_body); + allocator.free(ib.raw_open); + allocator.free(ib.raw_close); + }, + .for_block => if (self.@"for") |fb| { + allocator.free(fb.loop_var); + allocator.free(fb.iterable); + const body_copy = fb.body; + for (body_copy) |n| n.deinit(allocator); + allocator.free(fb.body); + const empty_copy = fb.empty_body; + for (empty_copy) |n| n.deinit(allocator); + allocator.free(fb.empty_body); + allocator.free(fb.raw_open); + allocator.free(fb.raw_close); + }, + .include => if (self.include) |inc| allocator.free(inc.template_name), + .with_block => if (self.with) |w| { + for (w.assignments) |a| { + allocator.free(a.key); + allocator.free(a.value_expr); + } + allocator.free(w.assignments); + const body_copy = w.body; + for (body_copy) |n| n.deinit(allocator); + allocator.free(w.body); + allocator.free(w.raw_open); + allocator.free(w.raw_close); + }, + .now => if (self.now) |n| allocator.free(n.format), + .extends => if (self.extends) |e| allocator.free(e.parent_name), + .block => if (self.block) |b| { + allocator.free(b.name); + const body_copy = b.body; + for (body_copy) |n| n.deinit(allocator); + allocator.free(b.body); + allocator.free(b.raw_open); + allocator.free(b.raw_close); + }, + .super => {}, + .filter_block => if (self.filter_block) |fb| { + allocator.free(fb.filters); + const body_copy = fb.body; + for (body_copy) |n| n.deinit(allocator); + allocator.free(fb.body); + }, + .autoescape => if (self.autoescape) |ae| { + const body_copy = ae.body; + for (body_copy) |n| n.deinit(allocator); + allocator.free(ae.body); + allocator.free(ae.raw_open); + allocator.free(ae.raw_close); + }, + .spaceless => if (self.spaceless) |sl| { + const body_copy = sl.body; + for (body_copy) |n| n.deinit(allocator); + allocator.free(sl.body); + allocator.free(sl.raw_open); + allocator.free(sl.raw_close); + }, + .url => if (self.url) |u| { + allocator.free(u.name); + for (u.args) |a| allocator.free(a); + allocator.free(u.args); + }, + .cycle => if (self.cycle) |c| { + for (c.values) |v| allocator.free(v); + allocator.free(c.values); + }, + .firstof => if (self.firstof) |fo| { + for (fo.values) |v| allocator.free(v); + allocator.free(fo.values); + }, + .load => if (self.load) |l| { + for (l.libraries) |lib| allocator.free(lib); + allocator.free(l.libraries); + }, + .csrf_token => {}, + .lorem => if (self.lorem) |l| { + if (l.count) |c| allocator.free(c); + if (l.method) |m| allocator.free(m); + if (l.format) |f| allocator.free(f); + }, + .debug => {}, + .partialdef => if (self.partialdef) |pd| { + allocator.free(pd.name); + allocator.free(pd.raw_open); + allocator.free(pd.raw_close); + for (pd.body) |n| n.deinit(allocator); + allocator.free(pd.body); + }, + .partial => if (self.partial) |p| { + allocator.free(p.name); + }, + .querystring => if (self.querystring) |qs| { + for (qs.modifications) |m| allocator.free(m); + allocator.free(qs.modifications); + }, + .regroup => if (self.regroup) |r| { + allocator.free(r.source); + allocator.free(r.by); + allocator.free(r.as_var); + }, + .resetcycle => if (self.resetcycle) |rc| { + if (rc.cycle_name) |name| allocator.free(name); + }, + .widthratio => if (self.widthratio) |wr| { + allocator.free(wr.value); + allocator.free(wr.max_value); + if (wr.divisor) |d| allocator.free(d); + }, + .templatetag => {}, } } @@ -447,521 +388,312 @@ pub const Node = struct { }, }; }, - .tag => { - switch (self.tag.?.kind) { - .debug => return Node{ .type = .tag, .tag = .{ - .kind = .debug, - .args = "", - .body = .{ .debug = true }, - .raw = self.tag.?.raw, - } }, - .firstof => { - const values_copy = try allocator.alloc([]const u8, self.tag.?.body.firstof.values.len); - errdefer allocator.free(values_copy); - for (self.tag.?.body.firstof.values, 0..) |f, i| { - values_copy[i] = try allocator.dupe(u8, f); - } + .if_block => { + const true_body_copy = try allocator.alloc(Node, self.@"if".?.true_body.len); + errdefer allocator.free(true_body_copy); - const fallback_copy = try allocator.dupe(u8, self.tag.?.body.firstof.fallback); - - return Node{ .type = .tag, .tag = .{ - .kind = .firstof, - .args = try allocator.dupe(u8, self.tag.?.args), - .body = .{ .firstof = .{ .values = values_copy, .fallback = fallback_copy } }, - .raw = try allocator.dupe(u8, self.tag.?.raw), - } }; - }, - .if_block => { - const true_body_copy = try allocator.alloc(Node, self.tag.?.body.@"if".true_body.len); - errdefer allocator.free(true_body_copy); - - for (self.tag.?.body.@"if".true_body, 0..) |child, j| { - true_body_copy[j] = try child.clone(allocator); - } - - const false_body_copy = try allocator.alloc(Node, self.tag.?.body.@"if".false_body.len); - errdefer allocator.free(false_body_copy); - - for (self.tag.?.body.@"if".false_body, 0..) |child, j| { - false_body_copy[j] = try child.clone(allocator); - } - - return Node{ - .type = .tag, - .tag = .{ - .kind = .if_block, - .args = try allocator.dupe(u8, self.tag.?.args), - .body = .{ - .@"if" = .{ - .condition = try allocator.dupe(u8, self.tag.?.body.@"if".condition), - .true_body = true_body_copy, - .false_body = false_body_copy, - .raw_open = try allocator.dupe(u8, self.tag.?.body.@"if".raw_open), - .raw_close = try allocator.dupe(u8, self.tag.?.body.@"if".raw_close), - }, - }, - .raw = try allocator.dupe(u8, self.tag.?.raw), - }, - }; - }, - .for_block => { - const body_copy = try allocator.alloc(Node, self.tag.?.body.@"for".body.len); - errdefer allocator.free(body_copy); - - for (self.tag.?.body.@"for".body, 0..) |child, j| { - body_copy[j] = try child.clone(allocator); - } - - const empty_body_copy = try allocator.alloc(Node, self.tag.?.body.@"for".empty_body.len); - errdefer allocator.free(empty_body_copy); - - for (self.tag.?.body.@"for".empty_body, 0..) |child, j| { - empty_body_copy[j] = try child.clone(allocator); - } - - return Node{ - .type = .tag, - .tag = .{ - .kind = .for_block, - .args = try allocator.dupe(u8, self.tag.?.args), - .body = .{ - .@"for" = .{ - .loop_var = try allocator.dupe(u8, self.tag.?.body.@"for".loop_var), - .iterable = try allocator.dupe(u8, self.tag.?.body.@"for".iterable), - .body = body_copy, - .empty_body = empty_body_copy, - .raw_open = try allocator.dupe(u8, self.tag.?.body.@"for".raw_open), - .raw_close = try allocator.dupe(u8, self.tag.?.body.@"for".raw_close), - }, - }, - .raw = try allocator.dupe(u8, self.tag.?.raw), - }, - }; - }, - .block => { - const body_copy = try allocator.alloc(Node, self.tag.?.body.block.body.len); - errdefer allocator.free(body_copy); - - for (self.tag.?.body.block.body, 0..) |child, j| { - body_copy[j] = try child.clone(allocator); - } - - return Node{ - .type = .tag, - .tag = .{ - .kind = .block, - .args = try allocator.dupe(u8, self.tag.?.args), - .body = .{ - .block = .{ - .name = try allocator.dupe(u8, self.tag.?.body.block.name), - .body = body_copy, - .raw_open = try allocator.dupe(u8, self.tag.?.body.block.raw_open), - .raw_close = try allocator.dupe(u8, self.tag.?.body.block.raw_close), - }, - }, - .raw = try allocator.dupe(u8, self.tag.?.raw), - }, - }; - }, - .extends => { - return Node{ - .type = .tag, - .tag = .{ - .kind = .extends, - .args = try allocator.dupe(u8, self.tag.?.args), - .body = .{ - .extends = .{ - .parent_name = try allocator.dupe(u8, self.tag.?.body.extends.parent_name), - }, - }, - .raw = try allocator.dupe(u8, self.tag.?.raw), - }, - }; - }, - .super => { - return Node{ - .type = .tag, - .tag = .{ - .kind = .super, - .args = try allocator.dupe(u8, self.tag.?.args), - .body = .{ .super = true }, - .raw = try allocator.dupe(u8, self.tag.?.raw), - }, - }; - }, - .include => { - return Node{ - .type = .tag, - .tag = .{ - .kind = .include, - .args = try allocator.dupe(u8, self.tag.?.args), - .raw = try allocator.dupe(u8, self.tag.?.raw), - .body = .{ - .include = .{ - .template_path = try allocator.dupe(u8, self.tag.?.body.include.template_path), - }, - }, - }, - }; - }, - .with_block => { - const body_copy = try allocator.alloc(Node, self.tag.?.body.with.body.len); - errdefer allocator.free(body_copy); - - for (self.tag.?.body.with.body, 0..) |child, j| { - body_copy[j] = try child.clone(allocator); - } - - const assignments_copy = try allocator.alloc(Assignment, self.tag.?.body.with.assignments.len); - errdefer allocator.free(assignments_copy); - - for (self.tag.?.body.with.assignments, 0..) |f, i| { - assignments_copy[i] = .{ - .key = try allocator.dupe(u8, f.key), - .value_expr = try allocator.dupe(u8, f.value_expr), - .is_literal = f.is_literal, - }; - } - - return Node{ - .type = .tag, - .tag = .{ - .kind = .with_block, - .args = try allocator.dupe(u8, self.tag.?.args), - .raw = try allocator.dupe(u8, self.tag.?.raw), - - .body = .{ - .with = .{ - .assignments = assignments_copy, - .body = body_copy, - .raw_open = try allocator.dupe(u8, self.tag.?.body.with.raw_open), - .raw_close = try allocator.dupe(u8, self.tag.?.body.with.raw_close), - }, - }, - }, - }; - }, - .now => { - return Node{ - .type = .tag, - .tag = .{ - .kind = .now, - .args = try allocator.dupe(u8, self.tag.?.args), - .raw = try allocator.dupe(u8, self.tag.?.raw), - - .body = .{ - .now = .{ - .format = try allocator.dupe(u8, self.tag.?.body.now.format), - }, - }, - }, - }; - }, - .autoescape => { - const body_copy = try allocator.alloc(Node, self.tag.?.body.autoescape.body.len); - errdefer allocator.free(body_copy); - - for (self.tag.?.body.autoescape.body, 0..) |child, j| { - body_copy[j] = try child.clone(allocator); - } - - return Node{ - .type = .tag, - .tag = .{ - .kind = .autoescape, - .args = try allocator.dupe(u8, self.tag.?.args), - .raw = try allocator.dupe(u8, self.tag.?.raw), - - .body = .{ - .autoescape = .{ - .body = body_copy, - .raw_open = try allocator.dupe(u8, self.tag.?.body.autoescape.raw_open), - .raw_close = try allocator.dupe(u8, self.tag.?.body.autoescape.raw_close), - .enabled = self.tag.?.body.autoescape.enabled, - }, - }, - }, - }; - }, - .spaceless => { - const body_copy = try allocator.alloc(Node, self.tag.?.body.spaceless.body.len); - errdefer allocator.free(body_copy); - - for (self.tag.?.body.spaceless.body, 0..) |child, j| { - body_copy[j] = try child.clone(allocator); - } - - return Node{ - .type = .tag, - .tag = .{ - .kind = .spaceless, - .args = try allocator.dupe(u8, self.tag.?.args), - .raw = try allocator.dupe(u8, self.tag.?.raw), - - .body = .{ - .spaceless = .{ - .body = body_copy, - .raw_open = try allocator.dupe(u8, self.tag.?.body.spaceless.raw_open), - .raw_close = try allocator.dupe(u8, self.tag.?.body.spaceless.raw_close), - }, - }, - }, - }; - }, - .svg => { - return Node{ - .type = .tag, - .tag = .{ - .kind = .svg, - .args = try allocator.dupe(u8, self.tag.?.args), - .raw = try allocator.dupe(u8, self.tag.?.raw), - - .body = .{ - .svg = .{ - .name = try allocator.dupe(u8, self.tag.?.body.svg.name), - .kind = try allocator.dupe(u8, self.tag.?.body.svg.kind), - }, - }, - }, - }; - }, - .url => { - const args_copy = try allocator.alloc([]const u8, self.tag.?.body.url.args.len); - errdefer allocator.free(args_copy); - - for (self.tag.?.body.url.args, 0..) |f, i| { - args_copy[i] = try allocator.dupe(u8, f); - } - - return Node{ - .type = .tag, - .tag = .{ - .kind = .url, - .args = try allocator.dupe(u8, self.tag.?.args), - .raw = try allocator.dupe(u8, self.tag.?.raw), - - .body = .{ - .url = .{ - .name = try allocator.dupe(u8, self.tag.?.body.url.name), - .args = args_copy, - }, - }, - }, - }; - }, - .cycle => { - const values_copy = try allocator.alloc([]const u8, self.tag.?.body.cycle.values.len); - errdefer allocator.free(values_copy); - - for (self.tag.?.body.cycle.values, 0..) |f, i| { - values_copy[i] = try allocator.dupe(u8, f); - } - - return Node{ - .type = .tag, - .tag = .{ - .kind = .cycle, - .args = try allocator.dupe(u8, self.tag.?.args), - .raw = try allocator.dupe(u8, self.tag.?.raw), - - .body = .{ - .cycle = .{ - .values = values_copy, - }, - }, - }, - }; - }, - .load => { - const libraries_copy = try allocator.alloc([]const u8, self.tag.?.body.load.libraries.len); - errdefer allocator.free(libraries_copy); - - for (self.tag.?.body.load.libraries, 0..) |f, i| { - libraries_copy[i] = try allocator.dupe(u8, f); - } - - return Node{ - .type = .tag, - .tag = .{ - .kind = .load, - .args = try allocator.dupe(u8, self.tag.?.args), - .raw = try allocator.dupe(u8, self.tag.?.raw), - - .body = .{ - .load = .{ - .libraries = libraries_copy, - }, - }, - }, - }; - }, - .lorem => { - return Node{ - .type = .tag, - .tag = .{ - .kind = .lorem, - .args = try allocator.dupe(u8, self.tag.?.args), - .raw = try allocator.dupe(u8, self.tag.?.raw), - - .body = .{ - .lorem = .{ - .count = if (self.tag.?.body.lorem.count) |c| try allocator.dupe(u8, c) else null, - .method = if (self.tag.?.body.lorem.method) |m| try allocator.dupe(u8, m) else null, - .random = self.tag.?.body.lorem.random, - }, - }, - }, - }; - }, - .partialdef => { - const body_copy = try allocator.alloc(Node, self.tag.?.body.partialdef.body.len); - errdefer allocator.free(body_copy); - - for (self.tag.?.body.partialdef.body, 0..) |child, j| { - body_copy[j] = try child.clone(allocator); - } - - return Node{ - .type = .tag, - .tag = .{ - .kind = .partialdef, - .args = try allocator.dupe(u8, self.tag.?.args), - .raw = try allocator.dupe(u8, self.tag.?.raw), - - .body = .{ - .partialdef = .{ - .name = try allocator.dupe(u8, self.tag.?.body.partialdef.name), - .body = body_copy, - .raw_open = try allocator.dupe(u8, self.tag.?.body.partialdef.raw_open), - .raw_close = try allocator.dupe(u8, self.tag.?.body.partialdef.raw_close), - }, - }, - }, - }; - }, - .partial => { - return Node{ - .type = .tag, - .tag = .{ - .kind = .partial, - .args = try allocator.dupe(u8, self.tag.?.args), - .raw = try allocator.dupe(u8, self.tag.?.raw), - - .body = .{ - .partial = .{ - .name = try allocator.dupe(u8, self.tag.?.body.partial.name), - }, - }, - }, - }; - }, - .querystring => { - const modifications_copy = try allocator.alloc([]const u8, self.tag.?.body.querystring.modifications.len); - errdefer allocator.free(modifications_copy); - - for (self.tag.?.body.querystring.modifications, 0..) |f, i| { - modifications_copy[i] = try allocator.dupe(u8, f); - } - - return Node{ - .type = .tag, - .tag = .{ - .kind = .querystring, - .args = try allocator.dupe(u8, self.tag.?.args), - .raw = try allocator.dupe(u8, self.tag.?.raw), - - .body = .{ - .querystring = .{ - .modifications = modifications_copy, - }, - }, - }, - }; - }, - .regroup => { - return Node{ - .type = .tag, - .tag = .{ - .kind = .regroup, - .args = try allocator.dupe(u8, self.tag.?.args), - .raw = try allocator.dupe(u8, self.tag.?.raw), - - .body = .{ - .regroup = .{ - .source = try allocator.dupe(u8, self.tag.?.body.regroup.source), - .by = try allocator.dupe(u8, self.tag.?.body.regroup.by), - .as_var = try allocator.dupe(u8, self.tag.?.body.regroup.as_var), - }, - }, - }, - }; - }, - .resetcycle => { - return Node{ - .type = .tag, - .tag = .{ - .kind = .resetcycle, - .args = try allocator.dupe(u8, self.tag.?.args), - .raw = try allocator.dupe(u8, self.tag.?.raw), - - .body = .{ - .resetcycle = .{ - .cycle_name = if (self.tag.?.body.resetcycle.cycle_name) |n| try allocator.dupe(u8, n) else null, - }, - }, - }, - }; - }, - .widthratio => { - return Node{ - .type = .tag, - .tag = .{ - .kind = .widthratio, - .args = try allocator.dupe(u8, self.tag.?.args), - .raw = try allocator.dupe(u8, self.tag.?.raw), - - .body = .{ - .widthratio = .{ - .value = try allocator.dupe(u8, self.tag.?.body.widthratio.value), - .max_value = try allocator.dupe(u8, self.tag.?.body.widthratio.max_value), - .divisor = if (self.tag.?.body.widthratio.divisor) |d| try allocator.dupe(u8, d) else null, - }, - }, - }, - }; - }, - .templatetag => { - return Node{ - .type = .tag, - .tag = .{ - .kind = .templatetag, - .args = try allocator.dupe(u8, self.tag.?.args), - .raw = try allocator.dupe(u8, self.tag.?.raw), - - .body = .{ - .templatetag = .{ - .kind = self.tag.?.body.templatetag.kind, - }, - }, - }, - }; - }, - .csrf_token => { - return Node{ - .type = .tag, - .tag = .{ - .kind = .csrf_token, - .args = try allocator.dupe(u8, self.tag.?.args), - .raw = try allocator.dupe(u8, self.tag.?.raw), - .body = .{ - .csrf_token = true, - }, - }, - }; - }, - else => {}, + for (self.@"if".?.true_body, 0..) |child, j| { + true_body_copy[j] = try child.clone(allocator); } + + const false_body_copy = try allocator.alloc(Node, self.@"if".?.false_body.len); + errdefer allocator.free(false_body_copy); + + for (self.@"if".?.false_body, 0..) |child, j| { + false_body_copy[j] = try child.clone(allocator); + } + + return Node{ + .type = .if_block, + .@"if" = .{ + .condition = try allocator.dupe(u8, self.@"if".?.condition), + .true_body = true_body_copy, + .false_body = false_body_copy, + .raw_open = try allocator.dupe(u8, self.@"if".?.raw_open), + .raw_close = try allocator.dupe(u8, self.@"if".?.raw_close), + }, + }; + }, + + .for_block => { + const body_copy = try allocator.alloc(Node, self.@"for".?.body.len); + errdefer allocator.free(body_copy); + + for (self.@"for".?.body, 0..) |child, j| { + body_copy[j] = try child.clone(allocator); + } + + const empty_body_copy = try allocator.alloc(Node, self.@"for".?.empty_body.len); + errdefer allocator.free(empty_body_copy); + + for (self.@"for".?.empty_body, 0..) |child, j| { + empty_body_copy[j] = try child.clone(allocator); + } + + return Node{ + .type = .for_block, + .@"for" = .{ + .loop_var = try allocator.dupe(u8, self.@"for".?.loop_var), + .iterable = try allocator.dupe(u8, self.@"for".?.iterable), + .body = body_copy, + .empty_body = empty_body_copy, + .raw_open = try allocator.dupe(u8, self.@"for".?.raw_open), + .raw_close = try allocator.dupe(u8, self.@"for".?.raw_close), + }, + }; + }, + + .block => { + const body_copy = try allocator.alloc(Node, self.block.?.body.len); + errdefer allocator.free(body_copy); + + for (self.block.?.body, 0..) |child, j| { + body_copy[j] = try child.clone(allocator); + } + + return Node{ + .type = .block, + .block = .{ + .name = try allocator.dupe(u8, self.block.?.name), + .body = body_copy, + .raw_open = try allocator.dupe(u8, self.block.?.raw_open), + .raw_close = try allocator.dupe(u8, self.block.?.raw_close), + }, + }; + }, + + .extends => { + return Node{ + .type = .extends, + .extends = .{ + .parent_name = try allocator.dupe(u8, self.extends.?.parent_name), + }, + }; + }, + + .super => { + return Node{ + .type = .super, + }; + }, + .tag => { + return Node{ + .type = .tag, + .tag = .{ + .name = try allocator.dupe(u8, self.tag.?.name), + .args = try allocator.dupe(u8, self.tag.?.args), + .raw = try allocator.dupe(u8, self.tag.?.raw), + }, + }; + }, + .include => { + return Node{ + .type = .include, + .include = .{ + .template_name = try allocator.dupe(u8, self.include.?.template_name), + }, + }; + }, + .with_block => { + const body_copy = try allocator.alloc(Node, self.block.?.body.len); + errdefer allocator.free(body_copy); + + for (self.block.?.body, 0..) |child, j| { + body_copy[j] = try child.clone(allocator); + } + + const assignments_copy = try allocator.alloc(Assignment, self.with.?.assignments.len); + errdefer allocator.free(assignments_copy); + + for (self.with.?.assignments, 0..) |f, i| { + assignments_copy[i] = .{ + .key = try allocator.dupe(u8, f.key), + .value_expr = try allocator.dupe(u8, f.value_expr), + .is_literal = f.is_literal, + }; + } + + return Node{ + .type = .with_block, + .with = .{ + .assignments = assignments_copy, + .body = body_copy, + .raw_open = try allocator.dupe(u8, self.with.?.raw_open), + .raw_close = try allocator.dupe(u8, self.with.?.raw_close), + }, + }; + }, + .now => { + return Node{ .type = .now, .now = .{ .format = try allocator.dupe(u8, self.now.?.format) } }; + }, + .filter_block => { + const body_copy = try allocator.alloc(Node, self.filter_block.?.body.len); + errdefer allocator.free(body_copy); + + for (self.filter_block.?.body, 0..) |child, j| { + body_copy[j] = try child.clone(allocator); + } + + return Node{ + .type = .filter_block, + .filter_block = .{ + .filters = try allocator.dupe(u8, self.filter_block.?.filters), + .body = body_copy, + .raw_open = try allocator.dupe(u8, self.filter_block.?.raw_open), + .raw_close = try allocator.dupe(u8, self.filter_block.?.raw_close), + }, + }; + }, + .autoescape => { + const body_copy = try allocator.alloc(Node, self.autoescape.?.body.len); + errdefer allocator.free(body_copy); + + for (self.autoescape.?.body, 0..) |child, j| { + body_copy[j] = try child.clone(allocator); + } + + return Node{ + .type = .autoescape, + .autoescape = .{ + .body = body_copy, + .raw_open = try allocator.dupe(u8, self.autoescape.?.raw_open), + .raw_close = try allocator.dupe(u8, self.autoescape.?.raw_close), + .enabled = self.autoescape.?.enabled, + }, + }; + }, + .spaceless => { + const body_copy = try allocator.alloc(Node, self.spaceless.?.body.len); + errdefer allocator.free(body_copy); + + for (self.spaceless.?.body, 0..) |child, j| { + body_copy[j] = try child.clone(allocator); + } + + return Node{ + .type = .spaceless, + .spaceless = .{ + .body = body_copy, + .raw_open = try allocator.dupe(u8, self.spaceless.?.raw_open), + .raw_close = try allocator.dupe(u8, self.spaceless.?.raw_close), + }, + }; + }, + .url => { + const args_copy = try allocator.alloc([]const u8, self.url.?.args.len); + errdefer allocator.free(args_copy); + + for (self.url.?.args, 0..) |f, i| { + args_copy[i] = try allocator.dupe(u8, f); + } + + return Node{ .type = .url, .url = .{ + .name = try allocator.dupe(u8, self.url.?.name), + .args = args_copy, + } }; + }, + .cycle => { + const values_copy = try allocator.alloc([]const u8, self.cycle.?.values.len); + errdefer allocator.free(values_copy); + + for (self.cycle.?.values, 0..) |f, i| { + values_copy[i] = try allocator.dupe(u8, f); + } + + return Node{ .type = .cycle, .cycle = .{ .values = values_copy } }; + }, + .firstof => { + const values_copy = try allocator.alloc([]const u8, self.firstof.?.values.len); + errdefer allocator.free(values_copy); + + for (self.firstof.?.values, 0..) |f, i| { + values_copy[i] = try allocator.dupe(u8, f); + } + + return Node{ .type = .firstof, .firstof = .{ .values = values_copy } }; + }, + .load => { + const libraries_copy = try allocator.alloc([]const u8, self.load.?.libraries.len); + errdefer allocator.free(libraries_copy); + + for (self.load.?.libraries, 0..) |f, i| { + libraries_copy[i] = try allocator.dupe(u8, f); + } + + return Node{ .type = .load, .load = .{ .libraries = libraries_copy } }; + }, + .csrf_token => { + return Node{ + .type = .csrf_token, + }; + }, + .lorem => { + return Node{ + .type = .lorem, + .lorem = .{ + .count = if (self.lorem.?.count) |c| try allocator.dupe(u8, c) else null, + .method = if (self.lorem.?.method) |m| try allocator.dupe(u8, m) else null, + .format = if (self.lorem.?.format) |f| try allocator.dupe(u8, f) else null, + }, + }; + }, + .debug => { + return Node{ .type = .debug }; + }, + .partialdef => { + const body_copy = try allocator.alloc(Node, self.partialdef.?.body.len); + errdefer allocator.free(body_copy); + + for (self.partialdef.?.body, 0..) |child, j| { + body_copy[j] = try child.clone(allocator); + } + + return Node{ .type = .partialdef, .partialdef = .{ + .name = try allocator.dupe(u8, self.partialdef.?.name), + .body = body_copy, + .raw_open = try allocator.dupe(u8, self.partialdef.?.raw_open), + .raw_close = try allocator.dupe(u8, self.partialdef.?.raw_close), + } }; + }, + .partial => { + return Node{ .type = .partial, .partial = .{ + .name = try allocator.dupe(u8, self.partial.?.name), + } }; + }, + .querystring => { + const modifications = try allocator.alloc([]const u8, self.querystring.?.modifications.len); + errdefer allocator.free(modifications); + + for (self.querystring.?.modifications, 0..) |f, i| { + modifications[i] = try allocator.dupe(u8, f); + } + + return Node{ .type = .querystring, .querystring = .{ + .modifications = modifications, + } }; + }, + .regroup => { + return Node{ .type = .regroup, .regroup = .{ + .source = try allocator.dupe(u8, self.regroup.?.source), + .by = try allocator.dupe(u8, self.regroup.?.by), + .as_var = try allocator.dupe(u8, self.regroup.?.as_var), + } }; + }, + .resetcycle => { + return Node{ .type = .resetcycle, .resetcycle = .{ + .cycle_name = if (self.resetcycle.?.cycle_name) |n| try allocator.dupe(u8, n) else null, + } }; + }, + .widthratio => { + return Node{ .type = .widthratio, .widthratio = .{ + .value = try allocator.dupe(u8, self.widthratio.?.value), + .max_value = try allocator.dupe(u8, self.widthratio.?.max_value), + .divisor = if (self.widthratio.?.divisor) |d| try allocator.dupe(u8, d) else null, + } }; + }, + .templatetag => { + return Node{ .type = .templatetag, .templatetag = .{ .kind = self.templatetag.?.kind } }; }, } } @@ -975,35 +707,6 @@ pub const Parser = struct { return .{ .template = template }; } - pub fn debugNodes(self: Parser, data: struct { - nodes: []Node, - name: ?[]const u8 = null, - }) void { - _ = self; - var prefix: []const u8 = ""; - - if (data.name) |name| { - prefix = " - "; - std.debug.print("{s}:\n", .{name}); - } - - for (data.nodes) |node| { - if (node.type == .tag) { - if (node.type == .tag and node.tag.?.kind == .block) { - std.debug.print("{s}tag: {any}, name: {s}\n", .{ prefix, node.tag.?.kind, node.tag.?.body.block.name }); - } else { - std.debug.print("{s}tag: {any}\n", .{ prefix, node.tag.?.kind }); - } - } - if (node.type == .text) { - std.debug.print("{s}text: {s}\n", .{ prefix, node.text.?.content }); - } - if (node.type == .variable) { - std.debug.print("{s}variable: {s}\n", .{ prefix, node.variable.?.expr }); - } - } - } - fn advance(self: *Parser, n: usize) void { self.pos += n; if (self.pos > self.template.len) self.pos = self.template.len; @@ -1022,7 +725,7 @@ pub const Parser = struct { self: *Parser, allocator: std.mem.Allocator, args: []const u8, - ) ParserError![]const Assignment { + ) ![]const Assignment { var list = std.ArrayList(Assignment){}; defer list.deinit(allocator); @@ -1072,10 +775,8 @@ pub const Parser = struct { } try list.append(allocator, .{ - // .key = try allocator.dupe(u8, key), - // .value_expr = try allocator.dupe(u8, value), - .key = key, - .value_expr = value, + .key = try allocator.dupe(u8, key), + .value_expr = try allocator.dupe(u8, value), .is_literal = in_quote, }); } @@ -1083,7 +784,7 @@ pub const Parser = struct { return try list.toOwnedSlice(allocator); } - fn parsePartialDefBlock(self: *Parser, allocator: std.mem.Allocator, name: []const u8, raw_open: []const u8) ParserError!TagNodeBody { + fn parsePartialDefBlock(self: *Parser, allocator: std.mem.Allocator, name: []const u8, raw_open: []const u8) !Node { var body = std.ArrayList(Node){}; defer body.deinit(allocator); @@ -1101,23 +802,31 @@ pub const Parser = struct { } if (try self.parseTag(allocator)) |tag_node| { - if (tag_node.tag.?.kind == .partialdef) { + const tag_name = tag_node.tag.?.name; + + if (std.mem.eql(u8, tag_name, "partialdef")) { depth += 1; try body.append(allocator, tag_node); continue; } - if (tag_node.tag.?.kind == .endpartialdef) { + if (std.mem.eql(u8, tag_name, "endpartialdef")) { depth -= 1; const raw_close = tag_node.tag.?.raw; + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); + if (depth == 0) { - return TagNodeBody{ .partialdef = .{ - .name = name, - .body = try body.toOwnedSlice(allocator), - .raw_open = raw_open, - .raw_close = raw_close, - } }; + return Node{ + .type = .partialdef, + .partialdef = .{ + .name = name, + .body = try body.toOwnedSlice(allocator), + .raw_open = raw_open, + .raw_close = raw_close, + }, + }; } try body.append(allocator, tag_node); @@ -1133,12 +842,7 @@ pub const Parser = struct { return error.UnclosedBlock; } - fn parseAutoescapeBlock( - self: *Parser, - allocator: std.mem.Allocator, - enabled: bool, - raw_open: []const u8, - ) ParserError!TagNodeBody { + fn parseAutoescapeBlock(self: *Parser, allocator: std.mem.Allocator, enabled: bool, raw_open: []const u8) !Node { var body = std.ArrayList(Node){}; defer body.deinit(allocator); @@ -1156,18 +860,24 @@ pub const Parser = struct { } if (try self.parseTag(allocator)) |tag_node| { - if (tag_node.tag.?.kind == .autoescape) { + const tag_name = tag_node.tag.?.name; + + if (std.mem.eql(u8, tag_name, "autoescape")) { depth += 1; try body.append(allocator, tag_node); continue; } - if (tag_node.tag.?.kind == .endautoescape) { + if (std.mem.eql(u8, tag_name, "endautoescape")) { depth -= 1; const raw_close = tag_node.tag.?.raw; + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); + if (depth == 0) { - return TagNodeBody{ + return Node{ + .type = .autoescape, .autoescape = .{ .enabled = enabled, .body = try body.toOwnedSlice(allocator), @@ -1180,6 +890,8 @@ pub const Parser = struct { try body.append(allocator, tag_node); continue; } + + try body.append(allocator, tag_node); } else { self.advance(1); } @@ -1188,11 +900,11 @@ pub const Parser = struct { return error.UnclosedBlock; } - fn parseVerbatimBlock(self: *Parser, allocator: std.mem.Allocator, name: []const u8, raw_open: []const u8) ParserError!TagNodeBody { - _ = allocator; + fn parseVerbatim(self: *Parser, allocator: std.mem.Allocator) !Node { const start = self.pos; var depth: usize = 1; + while (self.pos < self.template.len and depth > 0) { if (self.peek(2)) |p| { if (std.mem.eql(u8, p, "{%")) { @@ -1208,22 +920,19 @@ pub const Parser = struct { const inner = std.mem.trim(u8, self.template[content_start..self.pos], " \t\r\n"); - const endverbatim_idx = std.mem.indexOf(u8, inner, "endverbatim"); if (std.mem.eql(u8, inner, "verbatim")) { depth += 1; - } else if (endverbatim_idx != null) { + } else if (std.mem.eql(u8, inner, "endverbatim")) { depth -= 1; if (depth == 0) { // Copia até o início da tag endverbatim const content_end = content_start - 3; // retrocede "{% " - const content = self.template[start..content_end]; + const content = try allocator.dupe(u8, self.template[start..content_end]); self.advance(2); // consome %} - return TagNodeBody{ .verbatim = .{ - .name = name, - .content = content, - .raw_open = raw_open, - .raw_close = self.template[start..self.pos], - } }; + return Node{ + .type = .text, + .text = .{ .content = content }, + }; } } @@ -1231,14 +940,13 @@ pub const Parser = struct { continue; } } - self.advance(1); } return error.UnclosedVerbatim; } - fn parseSpacelessBlock(self: *Parser, allocator: std.mem.Allocator, raw_open: []const u8) ParserError!TagNodeBody { + fn parseSpacelessBlock(self: *Parser, allocator: std.mem.Allocator, raw_open: []const u8) !Node { var body = std.ArrayList(Node){}; defer body.deinit(allocator); @@ -1257,17 +965,26 @@ pub const Parser = struct { } if (try self.parseTag(allocator)) |tag_node| { - if (tag_node.tag.?.kind == .spaceless) { + const tag_name = tag_node.tag.?.name; + + if (std.mem.eql(u8, tag_name, "spaceless")) { depth += 1; - try body.append(allocator, tag_node); + // Ignora a tag open aninhada + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); continue; } - if (tag_node.tag.?.kind == .endspaceless) { + + if (std.mem.eql(u8, tag_name, "endspaceless")) { depth -= 1; const raw_close = tag_node.tag.?.raw; + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); + if (depth == 0) { - return TagNodeBody{ + return Node{ + .type = .spaceless, .spaceless = .{ .body = try body.toOwnedSlice(allocator), .raw_open = raw_open, @@ -1275,6 +992,9 @@ pub const Parser = struct { }, }; } + + // depth > 0: endspaceless aninhado — IGNORA a tag + // NÃO adicione ao body continue; } @@ -1287,7 +1007,7 @@ pub const Parser = struct { return error.UnclosedBlock; } - fn parseFilterBlock(self: *Parser, allocator: std.mem.Allocator, filters_raw: []const u8, raw_open: []const u8) ParserError!TagNodeBody { + fn parseFilterBlock(self: *Parser, allocator: std.mem.Allocator, filters_raw: []const u8, raw_open: []const u8) !Node { var body = std.ArrayList(Node){}; defer body.deinit(allocator); @@ -1305,25 +1025,35 @@ pub const Parser = struct { } if (try self.parseTag(allocator)) |tag_node| { - if (tag_node.tag.?.kind == .filter_block) { + const tag_name = tag_node.tag.?.name; + + if (std.mem.eql(u8, tag_name, "filter")) { depth += 1; try body.append(allocator, tag_node); continue; } - if (tag_node.tag.?.kind == .endfilter) { + if (std.mem.eql(u8, tag_name, "endfilter")) { depth -= 1; const raw_close = tag_node.tag.?.raw; + + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); + if (depth == 0) { - return TagNodeBody{ + // const filters = try allocator.dupe(u8, filters_raw); + + return Node{ + .type = .filter_block, .filter_block = .{ - .filters = filters_raw, + .filters = filters_raw, // já duped .body = try body.toOwnedSlice(allocator), .raw_open = raw_open, .raw_close = raw_close, }, }; } + try body.append(allocator, tag_node); continue; } @@ -1337,12 +1067,7 @@ pub const Parser = struct { return error.UnclosedBlock; } - fn parseBlockBlock( - self: *Parser, - allocator: std.mem.Allocator, - name: []const u8, - raw_open: []const u8, - ) ParserError!TagNodeBody { + fn parseBlockBlock(self: *Parser, allocator: std.mem.Allocator, name: []const u8, raw_open: []const u8) !Node { var body = std.ArrayList(Node){}; defer body.deinit(allocator); @@ -1357,7 +1082,8 @@ pub const Parser = struct { if (try self.parseVariable(allocator)) |node| { if (node.variable) |v| { if (std.mem.eql(u8, v.expr, "block.super")) { - try body.append(allocator, Node{ .type = .tag, .tag = .{ .kind = .super, .args = "", .raw = raw_open, .body = .{ .super = true } } }); + try body.append(allocator, Node{ .type = .super, .super = true }); + allocator.free(v.expr); continue; } } @@ -1366,55 +1092,37 @@ pub const Parser = struct { } if (try self.parseTag(allocator)) |tag_node| { - if (tag_node.tag.?.kind == .block) { + const tag_name = tag_node.tag.?.name; + + if (std.mem.eql(u8, tag_name, "block")) { depth += 1; try body.append(allocator, tag_node); continue; } - if (tag_node.tag.?.kind == .endblock) { + if (std.mem.eql(u8, tag_name, "endblock")) { depth -= 1; const raw_close = tag_node.tag.?.raw; + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); + if (depth == 0) { - return TagNodeBody{ .block = .{ - .name = name, - .body = try body.toOwnedSlice(allocator), - .raw_open = raw_open, - .raw_close = raw_close, - } }; + return Node{ + .type = .block, + .block = .{ + .name = name, + .body = try body.toOwnedSlice(allocator), + .raw_open = raw_open, + .raw_close = raw_close, + }, + }; } try body.append(allocator, tag_node); continue; } - // var tag = try self.parseTag(allocator); - // if (tag != null) { - // if (tag.?.tag.?.kind == .comment) { - // std.debug.print("vou parsear comentário\n", .{}); - // try self.parseComment(); - // continue; - // } else { - // if (try self.parseTagContent(allocator, tag.?)) |tn| { - // tag.?.tag.?.body = tn; - // try body.append(allocator, tag.?); - // } - // continue; - // } - // } - var tag: Node = tag_node; - if (tag_node.tag.?.kind == .comment) { - try self.parseComment(); - continue; - } else { - if (try self.parseTagContent(allocator, tag_node)) |tn| { - tag.tag.?.body = tn; - try body.append(allocator, tag); - continue; - } - } - try body.append(allocator, tag_node); } else { self.advance(1); @@ -1424,7 +1132,8 @@ pub const Parser = struct { return error.UnclosedBlock; } - fn parseWithBlock(self: *Parser, allocator: std.mem.Allocator, assignments: []const Assignment, raw_open: []const u8) ParserError!TagNodeBody { + fn parseWithBlock(self: *Parser, allocator: std.mem.Allocator, assignments: []const Assignment, raw_open: []const u8) !Node { + std.debug.print("Vou verificar se sou bloco with\n", .{}); var body = std.ArrayList(Node){}; defer body.deinit(allocator); @@ -1432,28 +1141,44 @@ pub const Parser = struct { while (self.pos < self.template.len and depth > 0) { if (try self.parseText(allocator)) |node| { + std.debug.print("2.3 - Encontrei um texto: {s}\n", .{node.text.?.content}); try body.append(allocator, node); continue; } if (try self.parseVariable(allocator)) |node| { + std.debug.print("2.3 - Encontrei uma variável: {s}\n", .{node.variable.?.expr}); try body.append(allocator, node); continue; } if (try self.parseTag(allocator)) |tag_node| { - if (tag_node.tag.?.kind == .with_block) { + const tag_name = tag_node.tag.?.name; + + if (std.mem.eql(u8, tag_name, "with")) { depth += 1; try body.append(allocator, tag_node); continue; } - if (tag_node.tag.?.kind == .endwith_block) { + if (std.mem.eql(u8, tag_name, "endwith")) { depth -= 1; const raw_close = tag_node.tag.?.raw; + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); + if (depth == 0) { - return TagNodeBody{ + // para fins de debug + std.debug.print("2.4 - Encontrei um bloco with:\n - assignments: {any}\n - body: {any}\n - raw_open: {s}\n - raw_close: {s}\n", .{ + assignments, + body.items, + raw_open, + raw_close, + }); + // fim para fins de debug + return Node{ + .type = .with_block, .with = .{ .assignments = assignments, .body = try body.toOwnedSlice(allocator), @@ -1476,7 +1201,7 @@ pub const Parser = struct { return error.UnclosedBlock; } - fn parseComment(self: *Parser) ParserError!void { + fn parseComment(self: *Parser) !void { // Consome a tag open {% comment %} // Já estamos após o %}, então só avançamos var depth: usize = 1; @@ -1520,13 +1245,16 @@ pub const Parser = struct { return error.UnclosedComment; } - fn parseText(self: *Parser, allocator: std.mem.Allocator) ParserError!?Node { - _ = allocator; + fn parseText(self: *Parser, allocator: std.mem.Allocator) !?Node { + std.debug.print("2.0 - Vou verificar se sou texto\n", .{}); const start = self.pos; + std.debug.print("2.1 - meu start é {d}\n", .{start}); + while (self.pos < self.template.len) { if (self.peek(2)) |p| { if (std.mem.eql(u8, p, "{{") or std.mem.eql(u8, p, "{%")) { + std.debug.print("2.2 - fiz o peek de 2 em 2 até que achei {{{{ ou {{%, então parei\n", .{}); break; } } @@ -1535,13 +1263,18 @@ pub const Parser = struct { if (self.pos == start) return null; + const content = try allocator.dupe(u8, self.template[start..self.pos]); + std.debug.print("2.2 - meu content é \'{s}\'\n", .{content}); return Node{ .type = .text, - .text = .{ .content = self.template[start..self.pos] }, + .text = .{ .content = content }, }; } - fn parseVariable(self: *Parser, allocator: std.mem.Allocator) ParserError!?Node { + fn parseVariable(self: *Parser, allocator: std.mem.Allocator) !?Node { + std.debug.print("2.0 - Vou verificar se sou variável\n", .{}); + std.debug.print("2.1 - meu start é {d}\n", .{self.pos}); + if (self.peek(2)) |p| { if (!std.mem.eql(u8, p, "{{")) return null; } else return null; @@ -1553,10 +1286,14 @@ pub const Parser = struct { while (self.pos < self.template.len) : (self.advance(1)) { if (self.peek(2)) |p| { if (std.mem.eql(u8, p, "}}")) break; + + std.debug.print("2.1 - fiz o peek de 2 em 2 até que achei {{{{\n", .{}); } } + std.debug.print("2.2 - fiz o peek de 2 em 2 até que achei }}}}\n", .{}); if (self.pos + 2 > self.template.len or !std.mem.eql(u8, self.template[self.pos .. self.pos + 2], "}}")) { + std.debug.print("2.3 - deu ruim achei uma variável que não fecha!\n", .{}); return error.UnclosedVariable; } @@ -1579,35 +1316,35 @@ pub const Parser = struct { const filter_arg = if (colon_pos) |cp| std.mem.trim(u8, filter_part[cp + 1 ..], " \"") else null; try filters.append(allocator, .{ - // .name = try allocator.dupe(u8, filter_name), - .name = filter_name, - // .arg = if (filter_arg) |a| try allocator.dupe(u8, a) else null, - .arg = if (filter_arg) |a| a else null, + .name = try allocator.dupe(u8, filter_name), + .arg = if (filter_arg) |a| try allocator.dupe(u8, a) else null, }); pipe_pos = std.mem.lastIndexOfScalar(u8, full_expr[0..expr_end], '|'); } const base_expr = std.mem.trim(u8, full_expr[0..expr_end], " \t\r\n"); - // const duped_expr = try allocator.dupe(u8, base_expr); + const duped_expr = try allocator.dupe(u8, base_expr); // Inverte os filters (porque usamos lastIndexOf) std.mem.reverse(Filter, filters.items); + std.debug.print("2.3 - meu conteúdo:\n - expr: \'{s}\' \n - filters: {any}\n", .{ duped_expr, filters }); return Node{ .type = .variable, .variable = .{ - // .expr = duped_expr, - .expr = base_expr, + .expr = duped_expr, .filters = try filters.toOwnedSlice(allocator), }, }; } - fn parseTag(self: *Parser, allocator: std.mem.Allocator) ParserError!?Node { - _ = allocator; + fn parseTag(self: *Parser, allocator: std.mem.Allocator) !?Node { + std.debug.print("2.0 - Vou verificar se sou uma tag\n", .{}); + std.debug.print("2.1 - meu start é {d}\n", .{self.pos}); if (self.peek(2)) |p| { if (!std.mem.eql(u8, p, "{%")) return null; + std.debug.print("2.1 - fiz o peek de 2 em 2 até que achei {{%\n", .{}); } else return null; const raw_start = self.pos; @@ -1621,6 +1358,7 @@ pub const Parser = struct { } } + std.debug.print("2.2 - fiz o peek de 2 em 2 até que achei %}}\n", .{}); if (self.pos + 2 > self.template.len or !std.mem.eql(u8, self.template[self.pos .. self.pos + 2], "%}")) { return error.UnclosedTag; } @@ -1632,23 +1370,24 @@ pub const Parser = struct { const name_raw = inner[0..space_idx]; const args_raw = if (space_idx < inner.len) std.mem.trim(u8, inner[space_idx + 1 ..], " \t\r\n") else ""; + const name = try allocator.dupe(u8, name_raw); + const args = try allocator.dupe(u8, args_raw); + + std.debug.print("2.3 - meu node:\n - nome: {s}\n - args: {s}\n - raw: {s}\n", .{ name, args, raw_slice }); self.advance(2); - const kind: TagKind = getTagKindByName(name_raw); - return Node{ .type = .tag, .tag = .{ - .kind = kind, - .raw = raw_slice, - .args = args_raw, - .body = .{ .initial = true }, - } }; + return Node{ + .type = .tag, + .tag = .{ + .name = name, + .args = args, + .raw = raw_slice, // slice original, sem dupe + }, + }; } - fn parseIfBlock( - self: *Parser, - allocator: std.mem.Allocator, - condition: []const u8, - raw_open: []const u8, - ) ParserError!TagNodeBody { + fn parseIfBlock(self: *Parser, allocator: std.mem.Allocator, condition: []const u8, raw_open: []const u8) !Node { + std.debug.print("Vou verificar se sou bloco\n", .{}); var true_body = std.ArrayList(Node){}; defer true_body.deinit(allocator); var false_body = std.ArrayList(Node){}; @@ -1659,56 +1398,66 @@ pub const Parser = struct { while (self.pos < self.template.len and depth > 0) { if (try self.parseText(allocator)) |node| { + std.debug.print("2.3 - Encontrei um texto: {s}\n", .{node.text.?.content}); try current_body.append(allocator, node); continue; } if (try self.parseVariable(allocator)) |node| { + std.debug.print("2.3 - Encontrei uma variável: {s}\n", .{node.variable.?.expr}); try current_body.append(allocator, node); continue; } if (try self.parseTag(allocator)) |tag_node| { - if (tag_node.tag.?.kind == .if_block) { + const tag_name = tag_node.tag.?.name; + + if (std.mem.eql(u8, tag_name, "if")) { depth += 1; try current_body.append(allocator, tag_node); continue; } - if (tag_node.tag.?.kind == .endif_block) { + if (std.mem.eql(u8, tag_name, "endif")) { depth -= 1; const raw_close = tag_node.tag.?.raw; + // Libera name e args da tag endif + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); + if (depth == 0) { - return TagNodeBody{ .@"if" = .{ - .condition = condition, - .true_body = try true_body.toOwnedSlice(allocator), - .false_body = try false_body.toOwnedSlice(allocator), - .raw_open = raw_open, - .raw_close = raw_close, - } }; + std.debug.print("2.4 - Encontrei um bloco if:\n - condition: {s}\n - true_body: {any}\n - false_body: {any}\n - raw_open: {s}\n - raw_close: {s}\n", .{ + condition, + true_body.items, + false_body.items, + raw_open, + raw_close, + }); + return Node{ + .type = .if_block, + .@"if" = .{ + .condition = condition, + .true_body = try true_body.toOwnedSlice(allocator), + .false_body = try false_body.toOwnedSlice(allocator), + .raw_open = raw_open, + .raw_close = raw_close, + }, + }; } + + // Se depth > 0, é endif aninhado — adiciona como tag normal try current_body.append(allocator, tag_node); continue; } - if (tag_node.tag.?.kind == .else_block and depth == 1) { + if (std.mem.eql(u8, tag_name, "else") and depth == 1) { current_body = &false_body; + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); continue; } - var tag: Node = tag_node; - if (tag_node.tag.?.kind == .comment) { - try self.parseComment(); - continue; - } else { - if (try self.parseTagContent(allocator, tag_node)) |tn| { - tag.tag.?.body = tn; - try current_body.append(allocator, tag); - continue; - } - } - // Qualquer outra tag try current_body.append(allocator, tag_node); } else { @@ -1719,13 +1468,7 @@ pub const Parser = struct { return error.UnclosedBlock; } - fn parseForBlock( - self: *Parser, - allocator: std.mem.Allocator, - loop_var: []const u8, - iterable: []const u8, - raw_open: []const u8, - ) ParserError!TagNodeBody { + fn parseForBlock(self: *Parser, allocator: std.mem.Allocator, loop_var: []const u8, iterable: []const u8, raw_open: []const u8) !Node { var body = std.ArrayList(Node){}; defer body.deinit(allocator); var empty_body = std.ArrayList(Node){}; @@ -1736,58 +1479,68 @@ pub const Parser = struct { while (self.pos < self.template.len and depth > 0) { if (try self.parseText(allocator)) |node| { + std.debug.print("2.3 - Encontrei um texto: {s}\n", .{node.text.?.content}); try current_body.append(allocator, node); continue; } if (try self.parseVariable(allocator)) |node| { + std.debug.print("2.3 - Encontrei uma variável: {s}\n", .{node.variable.?.expr}); try current_body.append(allocator, node); continue; } if (try self.parseTag(allocator)) |tag_node| { - if (tag_node.tag.?.kind == .for_block) { + const tag_name = tag_node.tag.?.name; + + if (std.mem.eql(u8, tag_name, "for")) { depth += 1; try current_body.append(allocator, tag_node); continue; } - if (tag_node.tag.?.kind == .endfor_block) { + if (std.mem.eql(u8, tag_name, "endfor")) { depth -= 1; const raw_close = tag_node.tag.?.raw; if (depth == 0) { - return TagNodeBody{ .@"for" = .{ - .loop_var = loop_var, - .iterable = iterable, - .body = try body.toOwnedSlice(allocator), - .empty_body = try empty_body.toOwnedSlice(allocator), - .raw_open = raw_open, - .raw_close = raw_close, - } }; + // Libera name e args — essa é a tag de fechamento final + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); + + std.debug.print("2.4 - Encontrei um bloco for:\n - loop_var: {s}\n - iterable: {s}\n - body: {any}\n - empty_body: {any}\n - raw_open: {s}\n - raw_close: {s}\n", .{ + loop_var, + iterable, + body.items, + empty_body.items, + raw_open, + raw_close, + }); + return Node{ + .type = .for_block, + .@"for" = .{ + .loop_var = loop_var, + .iterable = iterable, + .body = try body.toOwnedSlice(allocator), + .empty_body = try empty_body.toOwnedSlice(allocator), + .raw_open = raw_open, + .raw_close = raw_close, + }, + }; } + // depth > 0: endfor aninhado — adiciona como tag normal try current_body.append(allocator, tag_node); continue; } - if (tag_node.tag.?.kind == .empty and depth == 1) { + if (std.mem.eql(u8, tag_name, "empty") and depth == 1) { current_body = &empty_body; + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); continue; } - var tag: Node = tag_node; - if (tag_node.tag.?.kind == .comment) { - try self.parseComment(); - continue; - } else { - if (try self.parseTagContent(allocator, tag_node)) |tn| { - tag.tag.?.body = tn; - try body.append(allocator, tag); - continue; - } - } - try current_body.append(allocator, tag_node); } else { self.advance(1); @@ -1797,542 +1550,724 @@ pub const Parser = struct { return error.UnclosedBlock; } - fn parseTagContent(self: *Parser, allocator: std.mem.Allocator, tag_node: Node) ParserError!?TagNodeBody { - switch (tag_node.tag.?.kind) { - .autoescape => { - const args = std.mem.trim(u8, tag_node.tag.?.args, " \t\r\n"); - const raw_open = tag_node.tag.?.raw; - - const enabled = if (std.mem.eql(u8, args, "on")) - true - else if (std.mem.eql(u8, args, "off")) - false - else - return error.InvalidAutoescapeArgument; - - const ae_node = try self.parseAutoescapeBlock(allocator, enabled, raw_open); - return ae_node; - }, - .block => { - const block_name_raw = tag_node.tag.?.args; - const raw_open = tag_node.tag.?.raw; - const block_name = std.mem.trim(u8, block_name_raw, " \t\r\n"); - - const block_node = try self.parseBlockBlock(allocator, block_name, raw_open); - return block_node; - }, - .csrf_token => { - if (tag_node.tag.?.args.len > 0 and !std.mem.allEqual(u8, tag_node.tag.?.args, ' ')) { - return error.InvalidCsrfTokenArgs; - } - return TagNodeBody{ - .csrf_token = true, - }; - }, - .cycle => { - const args = tag_node.tag.?.args; - - var values = std.ArrayList([]const u8){}; - defer values.deinit(allocator); - - var i: usize = 0; - while (i < args.len) { - while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} - - if (i >= args.len) break; - - const start = i; - var in_quote = false; - var quote_char: u8 = 0; - if (args[i] == '"' or args[i] == '\'') { - in_quote = true; - quote_char = args[i]; - i += 1; - } - - while (i < args.len) { - if (in_quote) { - if (args[i] == quote_char) { - i += 1; - break; - } - } else { - if (std.ascii.isWhitespace(args[i])) break; - } - i += 1; - } - - const value = std.mem.trim(u8, args[start..i], " \t\r\n\"'"); - try values.append(allocator, value); - } - - return TagNodeBody{ - .cycle = .{ - .values = try values.toOwnedSlice(allocator), - }, - }; - }, - .debug => { - if (tag_node.tag.?.args.len > 0 and !std.mem.allEqual(u8, tag_node.tag.?.args, ' ')) { - return error.InvalidDebugArgs; - } - return TagNodeBody{ - .debug = true, - }; - }, - .extends => { - const parent = std.mem.trim(u8, tag_node.tag.?.args, " \t\""); - return TagNodeBody{ - .extends = .{ - .parent_name = parent, - }, - }; - }, - .filter_block => { - const filters_raw = tag_node.tag.?.args; - const raw_open = tag_node.tag.?.raw; - const filter_node = try self.parseFilterBlock(allocator, filters_raw, raw_open); - return filter_node; - }, - .firstof => { - const args = tag_node.tag.?.args; - - var values = std.ArrayList([]const u8){}; - defer values.deinit(allocator); - var i: usize = 0; - var fallback: []const u8 = ""; - while (i < args.len) { - while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} - - if (i >= args.len) break; - - const start = i; - var in_quote = false; - var quote_char: u8 = 0; - if (args[i] == '"' or args[i] == '\'') { - in_quote = true; - quote_char = args[i]; - i += 1; - } - - while (i < args.len) { - if (in_quote) { - if (args[i] == quote_char) { - i += 1; - break; - } - } else { - if (std.ascii.isWhitespace(args[i])) break; - } - i += 1; - } - - const value = std.mem.trim(u8, args[start..i], " \t\r\n\"'"); - if (in_quote) { - fallback = value; - } else { - try values.append(allocator, value); - } - } - - return TagNodeBody{ - .firstof = .{ - .values = try values.toOwnedSlice(allocator), - .fallback = fallback, - }, - }; - }, - .for_block => { - const args = tag_node.tag.?.args; - const raw_open = tag_node.tag.?.raw; - - const in_pos = std.mem.indexOf(u8, args, " in ") orelse return error.InvalidForSyntax; - const loop_var_raw = std.mem.trim(u8, args[0..in_pos], " \t"); - const iterable_raw = std.mem.trim(u8, args[in_pos + 4 ..], " \t"); - - const for_node = try self.parseForBlock(allocator, loop_var_raw, iterable_raw, raw_open); - return for_node; - }, - .if_block => { - const args = tag_node.tag.?.args; - const raw_open = tag_node.tag.?.raw; - const if_node = try self.parseIfBlock(allocator, args, raw_open); - return if_node; - }, - .include => { - const args = tag_node.tag.?.args; - var template_path = args; - if (template_path.len >= 2 and template_path[0] == '"' and template_path[template_path.len - 1] == '"') { - template_path = template_path[1 .. template_path.len - 1]; - } - return TagNodeBody{ - .include = .{ - .template_path = template_path, - }, - }; - }, - .load => { - const args = tag_node.tag.?.args; - var libraries = std.ArrayList([]const u8){}; - defer libraries.deinit(allocator); - - var i: usize = 0; - while (i < args.len) { - while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} - - if (i >= args.len) break; - - const start = i; - while (i < args.len and !std.ascii.isWhitespace(args[i])) : (i += 1) {} - - const lib_name = args[start..i]; - try libraries.append(allocator, lib_name); - } - - return TagNodeBody{ - .load = .{ - .libraries = try libraries.toOwnedSlice(allocator), - }, - }; - }, - .lorem => { - const args = tag_node.tag.?.args; - var count: ?[]const u8 = null; - var method: ?[]const u8 = null; - var random: bool = false; - - var parts = std.mem.splitScalar(u8, args, ' '); - while (parts.next()) |part| { - const trimmed = std.mem.trim(u8, part, " \t\r\n"); - if (trimmed.len == 0) continue; - - const num: usize = std.fmt.parseInt(usize, trimmed, 10) catch 0; - if (num > 0) { - count = trimmed; - } - - if (std.mem.eql(u8, trimmed, "p") or std.mem.eql(u8, trimmed, "w")) { - method = trimmed; - } else if (std.mem.eql(u8, trimmed, "true")) { - random = true; - } - } - - return TagNodeBody{ - .lorem = .{ - .count = count, - .method = method, - .random = random, - }, - }; - }, - .now => { - const args = tag_node.tag.?.args; - var format_str = args; - if (format_str.len >= 2 and format_str[0] == '"' and format_str[format_str.len - 1] == '"') { - format_str = format_str[1 .. format_str.len - 1]; - } - return TagNodeBody{ - .now = .{ - .format = format_str, - }, - }; - }, - .partial => { - const args = tag_node.tag.?.args; - var partial_name = args; - if (partial_name.len >= 2 and partial_name[0] == '"' and partial_name[partial_name.len - 1] == '"') { - partial_name = partial_name[1 .. partial_name.len - 1]; - } - return TagNodeBody{ - .partial = .{ - .name = partial_name, - }, - }; - }, - .partialdef => { - const args = tag_node.tag.?.args; - const raw_open = tag_node.tag.?.raw; - const partialdef_node = try self.parsePartialDefBlock(allocator, args, raw_open); - return partialdef_node; - }, - .querystring => { - const args = tag_node.tag.?.args; - var modifications = std.ArrayList([]const u8){}; - defer modifications.deinit(allocator); - - var i: usize = 0; - while (i < args.len) { - while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} - - if (i >= args.len) break; - - const start = i; - var in_quote = false; - var quote_char: u8 = 0; - if (args[i] == '"' or args[i] == '\'') { - in_quote = true; - quote_char = args[i]; - i += 1; - } - - while (i < args.len) { - if (in_quote) { - if (args[i] == quote_char) { - i += 1; - break; - } - } else { - if (std.ascii.isWhitespace(args[i])) { - break; - } - } - - i += 1; - } - - // const mod = args[start..i]; - const mod = std.mem.trim(u8, args[start..i], " \t\r\n\"'"); - try modifications.append(allocator, mod); - } - - return TagNodeBody{ - .querystring = .{ - .modifications = try modifications.toOwnedSlice(allocator), - }, - }; - }, - .regroup => { - const args = tag_node.tag.?.args; - - const by_pos = std.mem.indexOf(u8, args, " by ") orelse return error.InvalidRegroupSyntax; - const as_pos = std.mem.indexOf(u8, args[by_pos + 4 ..], " as ") orelse return error.InvalidRegroupSyntax; - const as_pos_abs = by_pos + 4 + as_pos; - - const source_raw = std.mem.trim(u8, args[0..by_pos], " \t\r\n"); - const by_raw = std.mem.trim(u8, args[by_pos + 4 .. as_pos_abs], " \t\r\n"); - const as_raw = std.mem.trim(u8, args[as_pos_abs + 4 ..], " \t\r\n"); - - const source = source_raw; - const by = by_raw; - const as = as_raw; - - return TagNodeBody{ - .regroup = .{ - .source = source, - .by = by, - .as_var = as, - }, - }; - }, - .resetcycle => { - const args = tag_node.tag.?.args; - var cycle_name = args; - if (cycle_name.len >= 2 and cycle_name[0] == '"' and cycle_name[cycle_name.len - 1] == '"') { - cycle_name = cycle_name[1 .. cycle_name.len - 1]; - } - return TagNodeBody{ - .resetcycle = .{ - .cycle_name = if (args.len > 0) cycle_name else null, - }, - }; - }, - .spaceless => { - const raw_open = tag_node.tag.?.raw; - const spaceless_node = try self.parseSpacelessBlock(allocator, raw_open); - return spaceless_node; - }, - .svg => { - const args = tag_node.tag.?.args; - - var values = std.ArrayList([]const u8){}; - defer values.deinit(allocator); - - var i: usize = 0; - while (i < args.len) { - while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} - - if (i >= args.len) break; - - const start = i; - var in_quote = false; - var quote_char: u8 = 0; - if (args[i] == '"' or args[i] == '\'') { - in_quote = true; - quote_char = args[i]; - i += 1; - } - - while (i < args.len) { - if (in_quote) { - if (args[i] == quote_char) { - i += 1; - break; - } - } else { - if (std.ascii.isWhitespace(args[i])) break; - } - i += 1; - } - - const value = std.mem.trim(u8, args[start..i], " \t\r\n\"'"); - // std.debug.print("value: {s}\n", .{value}); - try values.append(allocator, value); - } - return TagNodeBody{ - .svg = .{ - .kind = try allocator.dupe(u8, values.items[0]), - .name = try allocator.dupe(u8, values.items[1]), - }, - }; - }, - .templatetag => { - const args = tag_node.tag.?.args; - const templatetag = TemplateTagNode.parse(args); - if (templatetag == null) return error.InvalidTemplateTag; - - return TagNodeBody{ .templatetag = templatetag.? }; - }, - .url => { - const args = tag_node.tag.?.args; - - var arg_list = std.ArrayList([]const u8){}; - defer arg_list.deinit(allocator); - - var i: usize = 0; - // Pula o nome da url (entre aspas) - while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} - if (i >= args.len or args[i] != '\'') return error.InvalidUrlSyntax; - i += 1; - const name_start = i; - while (i < args.len and args[i] != '\'') : (i += 1) {} - if (i >= args.len or args[i] != '\'') return error.InvalidUrlSyntax; - const url_name = args[name_start..i]; - i += 1; - - // const duped_view = try allocator.dupe(u8, url_name); - // try arg_list.append(duped_view); - - while (i < args.len) { - while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} - if (i >= args.len) break; - - const arg_start = i; - if (args[i] == '"' or args[i] == '\'') { - const quote = args[i]; - i += 1; - while (i < args.len and args[i] != quote) : (i += 1) {} - if (i >= args.len) return error.UnclosedQuoteInUrl; - i += 1; - } else { - while (i < args.len and !std.ascii.isWhitespace(args[i])) : (i += 1) {} - } - - const arg = args[arg_start..i]; - // const duped_arg = try allocator.dupe(u8, arg); - try arg_list.append(allocator, arg); - } - - return TagNodeBody{ - .url = .{ - .name = url_name, - .args = try arg_list.toOwnedSlice(allocator), - }, - }; - }, - .verbatim => { - const raw_open = tag_node.tag.?.raw; - const block_name_raw = tag_node.tag.?.args; - const block_name = std.mem.trim(u8, block_name_raw, " \t\r\n"); - const verbatim_node = try self.parseVerbatimBlock(allocator, block_name, raw_open); - return verbatim_node; - }, - .widthratio => { - const args = tag_node.tag.?.args; - var parts = std.mem.splitScalar(u8, args, ' '); - var value_part: ?[]const u8 = null; - var max_part: ?[]const u8 = null; - var divisor_part: ?[]const u8 = null; - - if (parts.next()) |p| value_part = std.mem.trim(u8, p, " \t\r\n"); - if (parts.next()) |p| max_part = std.mem.trim(u8, p, " \t\r\n"); - if (parts.next()) |p| divisor_part = std.mem.trim(u8, p, " \t\r\n"); - - if (value_part == null or max_part == null) return error.InvalidWidthRatioSyntax; - - const value = value_part.?; - const max_value = max_part.?; - const divisor = if (divisor_part) |d| d else null; - - return TagNodeBody{ - .widthratio = .{ - .value = value, - .max_value = max_value, - .divisor = divisor, - }, - }; - }, - .with_block => { - const args = tag_node.tag.?.args; - const raw_open = tag_node.tag.?.raw; - - const assignments = try self.parseAssignments(allocator, args); - const with_node = try self.parseWithBlock(allocator, assignments, raw_open); - return with_node; - }, - else => return null, - } - // if (std.mem.eql(u8, tag_name, "with")) { - // const args = tag_node.tag.?.args; - // const raw_open = tag_node.tag.?.raw; - // - // const assignments = try self.parseAssignments(allocator, args); - // - // allocator.free(tag_node.tag.?.name); - // allocator.free(tag_node.tag.?.args); - // - // const with_node = try self.parseWithBlock(allocator, assignments, raw_open); - // return with_node; - // } - // - // - // if (std.mem.eql(u8, tag_name, "verbatim")) { - // allocator.free(tag_node.tag.?.name); - // allocator.free(tag_node.tag.?.args); - // - // const verbatim_node = try self.parseVerbatim(allocator); - // return verbatim_node; - // } - - // return null; - } - - pub fn parse(self: *Parser, allocator: std.mem.Allocator) ParserError![]Node { + pub fn parse(self: *Parser, allocator: std.mem.Allocator) ![]Node { var list = std.ArrayList(Node){}; defer list.deinit(allocator); + std.debug.print("O template recebido é:\n\n{s}\n\n", .{self.template}); while (self.pos < self.template.len) { - var tag = try self.parseTag(allocator); - if (tag != null) { - if (tag.?.tag.?.kind == .comment) { - try self.parseComment(); - continue; - } else { - // std.log.debug("Tag: {s}", .{tag.?.tag.?.raw}); - if (try self.parseTagContent(allocator, tag.?)) |tn| { - tag.?.tag.?.body = tn; - try list.append(allocator, tag.?); - } + std.debug.print("1.0 - minha posição ainda é menor que o tamanho do template\n", .{}); + if (try self.parseTag(allocator)) |node| { + std.debug.print("3.0 - na real sou uma tag\n", .{}); + const tag_name = node.tag.?.name; + + std.debug.print("3.1 - meu tag name é: {s}\n", .{tag_name}); + + if (std.mem.eql(u8, tag_name, "autoescape")) { + const args = std.mem.trim(u8, node.tag.?.args, " \t\r\n"); + const raw_open = node.tag.?.raw; + + const enabled = if (std.mem.eql(u8, args, "on")) + true + else if (std.mem.eql(u8, args, "off")) + false + else + return error.InvalidAutoescapeArgument; + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um autoescape\n", .{}); + std.debug.print("===================================\n", .{}); + + const ae_node = try self.parseAutoescapeBlock(allocator, enabled, raw_open); + try list.append(allocator, ae_node); continue; } + + if (std.mem.eql(u8, tag_name, "spaceless")) { + const raw_open = node.tag.?.raw; + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um spaceless\n", .{}); + std.debug.print("===================================\n", .{}); + + const spaceless_node = try self.parseSpacelessBlock(allocator, raw_open); + try list.append(allocator, spaceless_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "comment")) { + std.debug.print("3.0 - na real sou um comentário\n", .{}); + std.debug.print("===================================\n", .{}); + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + try self.parseComment(); + continue; + } + + if (std.mem.eql(u8, tag_name, "partialdef")) { + const partial_name = std.mem.trim(u8, node.tag.?.args, " \t\r\n"); + const raw_open = node.tag.?.raw; + + const duped_name = try allocator.dupe(u8, partial_name); + errdefer allocator.free(duped_name); + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um partialdef\n", .{}); + std.debug.print("===================================\n", .{}); + + const partialdef_node = try self.parsePartialDefBlock(allocator, duped_name, raw_open); + try list.append(allocator, partialdef_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "partial")) { + const partial_name = std.mem.trim(u8, node.tag.?.args, " \t\r\n\"'"); + + const duped_name = try allocator.dupe(u8, partial_name); + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um partial\n", .{}); + std.debug.print("===================================\n", .{}); + + try list.append(allocator, Node{ + .type = .partial, + .partial = .{ .name = duped_name }, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "lorem")) { + const args = node.tag.?.args; + + var count: ?[]const u8 = null; + var method: ?[]const u8 = null; + var format: ?[]const u8 = null; + + var parts = std.mem.splitScalar(u8, args, ' '); + while (parts.next()) |part| { + const trimmed = std.mem.trim(u8, part, " \t\r\n"); + if (trimmed.len == 0) continue; + + const num: usize = std.fmt.parseInt(usize, trimmed, 10) catch 0; + if (num > 0) { + std.debug.print("trimmed: {s}\n", .{trimmed}); + count = try allocator.dupe(u8, trimmed); + } + + if (std.mem.eql(u8, trimmed, "p") or std.mem.eql(u8, trimmed, "w")) { + std.debug.print("trimmed: {s}\n", .{trimmed}); + method = try allocator.dupe(u8, trimmed); + } else if (std.mem.eql(u8, trimmed, "html")) { + std.debug.print("trimmed: {s}\n", .{trimmed}); + format = try allocator.dupe(u8, trimmed); + } + } + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + try list.append(allocator, Node{ + .type = .lorem, + .lorem = .{ + .count = count, + .method = method, + .format = format, + }, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "filter")) { + const filters_raw = node.tag.?.args; + const raw_open = node.tag.?.raw; + + // DUPE O FILTERS IMEDIATAMENTE + const duped_filters = try allocator.dupe(u8, filters_raw); + + // Agora libera a tag open + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + std.debug.print("3.0 - na real sou um filter\n", .{}); + std.debug.print("===================================\n", .{}); + + const filter_node = try self.parseFilterBlock(allocator, duped_filters, raw_open); + try list.append(allocator, filter_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "extends")) { + const parent = std.mem.trim(u8, node.tag.?.args, " \t\""); + + const duped = try allocator.dupe(u8, parent); + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um extends\n", .{}); + std.debug.print("===================================\n", .{}); + try list.append(allocator, Node{ + .type = .extends, + .extends = .{ .parent_name = duped }, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "block")) { + const block_name_raw = node.tag.?.args; + const raw_open = node.tag.?.raw; + + const block_name = std.mem.trim(u8, block_name_raw, " \t\r\n"); + + // DUPE O NOME ANTES DE LIBERAR A TAG + const duped_name = try allocator.dupe(u8, block_name); + + // Agora libera a tag open + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um block\n", .{}); + std.debug.print("===================================\n", .{}); + + const block_node = try self.parseBlockBlock(allocator, duped_name, raw_open); + try list.append(allocator, block_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "now")) { + const args = node.tag.?.args; + + // Remove aspas se existirem + var format_str = args; + if (format_str.len >= 2 and format_str[0] == '"' and format_str[format_str.len - 1] == '"') { + format_str = format_str[1 .. format_str.len - 1]; + } + + const duped_format = try allocator.dupe(u8, format_str); + + // Libera a tag now + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou uma tag now\n", .{}); + std.debug.print("===================================\n", .{}); + + try list.append(allocator, Node{ + .type = .now, + .now = .{ .format = duped_format }, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "with")) { + const args = node.tag.?.args; + const raw_open = node.tag.?.raw; + + const assignments = try self.parseAssignments(allocator, args); + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + const with_node = try self.parseWithBlock(allocator, assignments, raw_open); + std.debug.print("3.0 - na real sou um bloco with\n", .{}); + std.debug.print("===================================\n", .{}); + try list.append(allocator, with_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "include")) { + const args = node.tag.?.args; + + // Remove aspas se existirem + var template_name = args; + if (template_name.len >= 2 and template_name[0] == '"' and template_name[template_name.len - 1] == '"') { + template_name = template_name[1 .. template_name.len - 1]; + } + + const duped_name = try allocator.dupe(u8, template_name); + + // Libera a tag include + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um include\n", .{}); + std.debug.print("===================================\n", .{}); + try list.append(allocator, Node{ + .type = .include, + .include = .{ .template_name = duped_name }, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "if")) { + const condition_raw = node.tag.?.args; + const raw_open = node.tag.?.raw; + + const condition = try allocator.dupe(u8, condition_raw); + + // Libera apenas name e args da tag open + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + // NÃO chame node.deinit aqui — raw_open ainda é usado + + const if_node = try self.parseIfBlock(allocator, condition, raw_open); + try list.append(allocator, if_node); + + std.debug.print("===================================\n", .{}); + continue; + } + + if (std.mem.eql(u8, tag_name, "for")) { + const args = node.tag.?.args; + const raw_open = node.tag.?.raw; + + const in_pos = std.mem.indexOf(u8, args, " in ") orelse return error.InvalidForSyntax; + const loop_var_raw = std.mem.trim(u8, args[0..in_pos], " \t"); + const iterable_raw = std.mem.trim(u8, args[in_pos + 4 ..], " \t"); + + // DUPE ANTES DE LIBERAR! + const loop_var = try allocator.dupe(u8, loop_var_raw); + const iterable = try allocator.dupe(u8, iterable_raw); + + // Agora sim, libera a tag open + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + const for_node = try self.parseForBlock(allocator, loop_var, iterable, raw_open); + + std.debug.print("===================================\n", .{}); + try list.append(allocator, for_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "verbatim")) { + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um verbatim\n", .{}); + std.debug.print("===================================\n", .{}); + + const verbatim_node = try self.parseVerbatim(allocator); + try list.append(allocator, verbatim_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "url")) { + const args = node.tag.?.args; + + var arg_list = std.ArrayList([]const u8){}; + defer arg_list.deinit(allocator); + + var i: usize = 0; + // Pula o nome da view (entre aspas) + while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} + if (i >= args.len or args[i] != '\'') return error.InvalidUrlSyntax; + i += 1; + const view_start = i; + while (i < args.len and args[i] != '\'') : (i += 1) {} + if (i >= args.len or args[i] != '\'') return error.InvalidUrlSyntax; + const view_name = args[view_start..i]; + i += 1; + + const duped_view = try allocator.dupe(u8, view_name); + + // Agora os argumentos + while (i < args.len) { + while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} + if (i >= args.len) break; + + const arg_start = i; + if (args[i] == '"' or args[i] == '\'') { + const quote = args[i]; + i += 1; + while (i < args.len and args[i] != quote) : (i += 1) {} + if (i >= args.len) return error.UnclosedQuoteInUrl; + i += 1; + } else { + while (i < args.len and !std.ascii.isWhitespace(args[i])) : (i += 1) {} + } + const arg = args[arg_start..i]; + try arg_list.append(allocator, try allocator.dupe(u8, arg)); + } + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou uma url\n", .{}); + std.debug.print("===================================\n", .{}); + + try list.append(allocator, Node{ + .type = .url, + .url = .{ + .name = duped_view, + .args = try arg_list.toOwnedSlice(allocator), + }, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "cycle")) { + const args = node.tag.?.args; + + var values = std.ArrayList([]const u8){}; + defer values.deinit(allocator); + + var i: usize = 0; + while (i < args.len) { + while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} + + if (i >= args.len) break; + + const start = i; + var in_quote = false; + var quote_char: u8 = 0; + if (args[i] == '"' or args[i] == '\'') { + in_quote = true; + quote_char = args[i]; + i += 1; + } + + while (i < args.len) { + if (in_quote) { + if (args[i] == quote_char) { + i += 1; + break; + } + } else { + if (std.ascii.isWhitespace(args[i])) break; + } + i += 1; + } + + const value = std.mem.trim(u8, args[start..i], " \t\r\n\"'"); + try values.append(allocator, try allocator.dupe(u8, value)); + } + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um cycle\n", .{}); + std.debug.print("===================================\n", .{}); + + try list.append(allocator, Node{ + .type = .cycle, + .cycle = .{ + .values = try values.toOwnedSlice(allocator), + }, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "firstof")) { + const args = node.tag.?.args; + + var values = std.ArrayList([]const u8){}; + defer values.deinit(allocator); + + var i: usize = 0; + while (i < args.len) { + while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} + + if (i >= args.len) break; + + const start = i; + var in_quote = false; + var quote_char: u8 = 0; + if (args[i] == '"' or args[i] == '\'') { + in_quote = true; + quote_char = args[i]; + i += 1; + } + + while (i < args.len) { + if (in_quote) { + if (args[i] == quote_char) { + i += 1; + break; + } + } else { + if (std.ascii.isWhitespace(args[i])) break; + } + i += 1; + } + + const value = std.mem.trim(u8, args[start..i], " \t\r\n\"'"); + try values.append(allocator, try allocator.dupe(u8, value)); + } + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um firstof\n", .{}); + std.debug.print("===================================\n", .{}); + + try list.append(allocator, Node{ + .type = .firstof, + .firstof = .{ + .values = try values.toOwnedSlice(allocator), + }, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "load")) { + const args = node.tag.?.args; + + var libraries = std.ArrayList([]const u8){}; + defer libraries.deinit(allocator); + + var i: usize = 0; + while (i < args.len) { + while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} + + if (i >= args.len) break; + + const start = i; + while (i < args.len and !std.ascii.isWhitespace(args[i])) : (i += 1) {} + + const lib_name = args[start..i]; + try libraries.append(allocator, try allocator.dupe(u8, lib_name)); + } + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um load\n", .{}); + std.debug.print("===================================\n", .{}); + + try list.append(allocator, Node{ + .type = .load, + .load = .{ + .libraries = try libraries.toOwnedSlice(allocator), + }, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "debug")) { + // Verifica se tem argumentos (não deve ter) + if (node.tag.?.args.len > 0 and !std.mem.allEqual(u8, node.tag.?.args, ' ')) { + return error.InvalidDebugArgs; + } + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um debug\n", .{}); + std.debug.print("===================================\n", .{}); + + try list.append(allocator, Node{ + .type = .debug, + .debug = true, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "querystring")) { + const args = node.tag.?.args; + + var modifications = std.ArrayList([]const u8){}; + defer modifications.deinit(allocator); + + var i: usize = 0; + while (i < args.len) { + while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} + + if (i >= args.len) break; + + const start = i; + var in_quote = false; + var quote_char: u8 = 0; + if (args[i] == '"' or args[i] == '\'') { + in_quote = true; + quote_char = args[i]; + i += 1; + } + + while (i < args.len) { + if (in_quote) { + if (args[i] == quote_char) { + i += 1; + break; + } + } else { + if (std.ascii.isWhitespace(args[i])) break; + } + i += 1; + } + + const mod_str = std.mem.trim(u8, args[start..i], " \t\r\n\"'"); + try modifications.append(allocator, try allocator.dupe(u8, mod_str)); + } + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um querystring\n", .{}); + std.debug.print("===================================\n", .{}); + + try list.append(allocator, Node{ + .type = .querystring, + .querystring = .{ + .modifications = try modifications.toOwnedSlice(allocator), + }, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "regroup")) { + const args = node.tag.?.args; + + // Esperado: lista by atributo as nome_grupo + const by_pos = std.mem.indexOf(u8, args, " by ") orelse return error.InvalidRegroupSyntax; + const as_pos = std.mem.indexOf(u8, args[by_pos + 4 ..], " as ") orelse return error.InvalidRegroupSyntax; + const as_pos_abs = by_pos + 4 + as_pos; + + const source_raw = std.mem.trim(u8, args[0..by_pos], " \t\r\n"); + const by_raw = std.mem.trim(u8, args[by_pos + 4 .. as_pos_abs], " \t\r\n"); + const as_raw = std.mem.trim(u8, args[as_pos_abs + 4 ..], " \t\r\n"); + + const source = try allocator.dupe(u8, source_raw); + const by = try allocator.dupe(u8, by_raw); + const as_var = try allocator.dupe(u8, as_raw); + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um regroup\n", .{}); + std.debug.print("===================================\n", .{}); + + try list.append(allocator, Node{ + .type = .regroup, + .regroup = .{ + .source = source, + .by = by, + .as_var = as_var, + }, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "resetcycle")) { + const args = std.mem.trim(u8, node.tag.?.args, " \t\r\n"); + + var cycle_name: ?[]const u8 = null; + if (args.len > 0) { + cycle_name = try allocator.dupe(u8, args); + } + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um resetcycle\n", .{}); + std.debug.print("===================================\n", .{}); + + try list.append(allocator, Node{ + .type = .resetcycle, + .resetcycle = .{ + .cycle_name = cycle_name, + }, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "widthratio")) { + const args = node.tag.?.args; + + var parts = std.mem.splitScalar(u8, args, ' '); + var value_part: ?[]const u8 = null; + var max_part: ?[]const u8 = null; + var divisor_part: ?[]const u8 = null; + + if (parts.next()) |p| value_part = std.mem.trim(u8, p, " \t\r\n"); + if (parts.next()) |p| max_part = std.mem.trim(u8, p, " \t\r\n"); + if (parts.next()) |p| divisor_part = std.mem.trim(u8, p, " \t\r\n"); + + if (value_part == null or max_part == null) return error.InvalidWidthRatioSyntax; + + const value = try allocator.dupe(u8, value_part.?); + const max_value = try allocator.dupe(u8, max_part.?); + const divisor = if (divisor_part) |d| try allocator.dupe(u8, d) else null; + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um widthratio\n", .{}); + std.debug.print("===================================\n", .{}); + + try list.append(allocator, Node{ + .type = .widthratio, + .widthratio = .{ + .value = value, + .max_value = max_value, + .divisor = divisor, + }, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "templatetag")) { + const arg = std.mem.trim(u8, node.tag.?.args, " \t\r\n"); + + const templatetag = TemplateTagNode.parse(arg); + if (templatetag == null) return error.InvalidTemplateTag; + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + try list.append(allocator, Node{ + .type = .templatetag, + .templatetag = templatetag, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "csrf_token")) { + // Verifica se tem argumentos (não deve ter) + if (node.tag.?.args.len > 0 and !std.mem.allEqual(u8, node.tag.?.args, ' ')) { + return error.InvalidCsrfTokenArgs; + } + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um csrf_token\n", .{}); + std.debug.print("===================================\n", .{}); + + try list.append(allocator, Node{ + .type = .csrf_token, + .csrf_token = true, + }); + continue; + } + + // Para tags normais + std.debug.print("===================================\n", .{}); + try list.append(allocator, node); + continue; } if (try self.parseVariable(allocator)) |node| { + std.debug.print("3.0 - na real sou variável\n", .{}); + std.debug.print("4.0 - content: \'{s}\'\n", .{node.variable.?.expr}); + std.debug.print("4.1 - filters: \'{any}\'\n", .{node.variable.?.filters}); + std.debug.print("===================================\n", .{}); try list.append(allocator, node); continue; } if (try self.parseText(allocator)) |node| { + std.debug.print("3.0 - na real sou texto\n", .{}); + std.debug.print("4.0 - content: \'{s}\'\n", .{node.text.?.content}); + std.debug.print("===================================\n", .{}); try list.append(allocator, node); continue; } @@ -2340,20 +2275,16 @@ pub const Parser = struct { self.advance(1); } + std.debug.print("\nO resultado disso foi esse:\n", .{}); + for (list.items) |item| { + std.debug.print(" -> type: {s}\n", .{@tagName(item.type)}); + } + std.debug.print("\n", .{}); return try list.toOwnedSlice(allocator); } }; -pub fn parse_t(allocator: std.mem.Allocator, template: []const u8) ParserError![]Node { +pub fn parse(allocator: std.mem.Allocator, template: []const u8) ![]Node { var p = Parser.init(template); - return p.parse(allocator); -} - -pub fn parse_template(template: []const u8) ParserError![]Node { - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - const alloc = arena.allocator(); - defer arena.deinit(); - - var p = Parser.init(template); - return p.parse(alloc); + return try p.parse(allocator); } diff --git a/src/parser_bkp.zig b/src/parser_bkp.zig new file mode 100644 index 0000000..97a2bb7 --- /dev/null +++ b/src/parser_bkp.zig @@ -0,0 +1,1208 @@ +const std = @import("std"); + +pub const NodeType = enum { + text, + variable, + tag, + if_block, + for_block, + include, + with_block, + now, + extends, + block, + super, + filter_block, + autoescape, + spaceless, +}; + +pub const AutoescapeNode = struct { + body: []Node, + raw_open: []const u8, + raw_close: []const u8, + enabled: bool, +}; + +pub const SpacelessNode = struct { + body: []Node, + raw_open: []const u8, + raw_close: []const u8, +}; + +pub const FilterBlockNode = struct { + filters: []const u8, // "upper|escape|truncatewords:30" + body: []Node, + raw_open: []const u8, + raw_close: []const u8, +}; + +pub const Filter = struct { + name: []const u8, + arg: ?[]const u8 = null, // null ou string com argumento +}; + +pub const VariableNode = struct { + expr: []const u8, + filters: []const Filter, +}; + +pub const NowNode = struct { + format: []const u8, +}; + +pub const ExtendsNode = struct { + parent_name: []const u8, +}; + +pub const BlockNode = struct { + name: []const u8, + body: []Node, + raw_open: []const u8, + raw_close: []const u8, +}; + +pub const Assignment = struct { + key: []const u8, + value_expr: []const u8, + is_literal: bool, +}; + +pub const WithNode = struct { + assignments: []const Assignment, + body: []Node, + raw_open: []const u8, + raw_close: []const u8, +}; + +pub const IncludeNode = struct { + template_name: []const u8, +}; + +pub const TextNode = struct { + content: []const u8, +}; + +pub const TagNode = struct { + name: []const u8, + args: []const u8, + raw: []const u8, +}; + +pub const IfNode = struct { + condition: []const u8, // dupe esse sim + true_body: []Node, + false_body: []Node, + raw_open: []const u8, // slice original, NÃO free + raw_close: []const u8, // slice original, NÃO free +}; + +pub const ForNode = struct { + loop_var: []const u8, + iterable: []const u8, + body: []Node, + empty_body: []Node, + raw_open: []const u8, + raw_close: []const u8, +}; + +pub const Node = struct { + type: NodeType, + text: ?TextNode = null, + variable: ?VariableNode = null, + tag: ?TagNode = null, + @"if": ?IfNode = null, + @"for": ?ForNode = null, + include: ?IncludeNode = null, + with: ?WithNode = null, + now: ?NowNode = null, + extends: ?ExtendsNode = null, + block: ?BlockNode = null, + super: bool = false, // para {{ block.super }} + filter_block: ?FilterBlockNode = null, + autoescape: ?AutoescapeNode = null, + spaceless: ?SpacelessNode = null, + + pub fn deinit(self: Node, allocator: std.mem.Allocator) void { + switch (self.type) { + .text => if (self.text) |t| allocator.free(t.content), + // .variable => if (self.variable) |v| allocator.free(v.content), + .variable => if (self.variable) |v| { + allocator.free(v.expr); + for (v.filters) |f| { + allocator.free(f.name); + if (f.arg) |a| allocator.free(a); + } + allocator.free(v.filters); + }, + .tag => if (self.tag) |t| { + allocator.free(t.name); + allocator.free(t.args); + }, + .if_block => if (self.@"if") |ib| { + allocator.free(ib.condition); + // NÃO free ib.raw_open + // NÃO free ib.raw_close + for (ib.true_body) |n| n.deinit(allocator); + allocator.free(ib.true_body); + for (ib.false_body) |n| n.deinit(allocator); + allocator.free(ib.false_body); + }, + .for_block => if (self.@"for") |fb| { + allocator.free(fb.loop_var); + allocator.free(fb.iterable); + for (fb.body) |n| n.deinit(allocator); + allocator.free(fb.body); + for (fb.empty_body) |n| n.deinit(allocator); + allocator.free(fb.empty_body); + // raw_open e raw_close são slices originais — não free + }, + .include => if (self.include) |inc| { + allocator.free(inc.template_name); + }, + .with_block => if (self.with) |w| { + for (w.assignments) |a| { + allocator.free(a.key); + allocator.free(a.value_expr); + } + allocator.free(w.assignments); + for (w.body) |n| n.deinit(allocator); + allocator.free(w.body); + // raw_open e raw_close são slices originais — não free + }, + .now => if (self.now) |n| { + allocator.free(n.format); + }, + .extends => if (self.extends) |e| { + allocator.free(e.parent_name); + }, + .block => if (self.block) |b| { + allocator.free(b.name); + for (b.body) |n| n.deinit(allocator); + allocator.free(b.body); + // raw_open e raw_close são slices originais — não free + }, + .super => {}, + .filter_block => if (self.filter_block) |fb| { + allocator.free(fb.filters); + for (fb.body) |n| n.deinit(allocator); + allocator.free(fb.body); + }, + .autoescape => if (self.autoescape) |ae| { + for (ae.body) |n| n.deinit(allocator); + allocator.free(ae.body); + // raw_open e raw_close são slices originais — não free + }, + .spaceless => if (self.spaceless) |sl| { + const body_copy = sl.body; + for (body_copy) |n| { + n.deinit(allocator); + } + allocator.free(sl.body); + }, + } + } +}; + +pub const Parser = struct { + template: []const u8, + pos: usize = 0, + + pub fn init(template: []const u8) Parser { + return .{ .template = template }; + } + + fn advance(self: *Parser, n: usize) void { + self.pos += n; + if (self.pos > self.template.len) self.pos = self.template.len; + } + + fn peek(self: Parser, comptime n: usize) ?[]const u8 { + if (self.pos + n > self.template.len) return null; + return self.template[self.pos .. self.pos + n]; + } + + fn skipWhitespace(self: *Parser) void { + while (self.pos < self.template.len and std.ascii.isWhitespace(self.template[self.pos])) : (self.advance(1)) {} + } + + fn parseAssignments( + self: *Parser, + allocator: std.mem.Allocator, + args: []const u8, + ) ![]const Assignment { + var list = std.ArrayList(Assignment){}; + defer list.deinit(allocator); + + _ = self; + var i: usize = 0; + while (i < args.len) { + // Pula whitespaces iniciais + while (i < args.len and std.mem.indexOfScalar(u8, " \t\r\n", args[i]) != null) : (i += 1) {} + + if (i >= args.len) break; + + // Parse key (até '=') + const key_start = i; + while (i < args.len and args[i] != '=') : (i += 1) {} + if (i >= args.len or args[i] != '=') return error.InvalidAssignmentSyntax; + const key = std.mem.trim(u8, args[key_start..i], " \t\r\n"); + if (key.len == 0) return error.InvalidAssignmentSyntax; + i += 1; // Pula '=' + + // Pula whitespaces após '=' + while (i < args.len and std.mem.indexOfScalar(u8, " \t\r\n", args[i]) != null) : (i += 1) {} + + // Parse value: se começa com ", parse até próximo " não escapado; senão, até próximo espaço + const value_start = i; + var in_quote = false; + if (i < args.len and args[i] == '"') { + in_quote = true; + i += 1; // Pula aspa inicial + } + while (i < args.len) { + if (in_quote) { + if (args[i] == '"' and (i == 0 or args[i - 1] != '\\')) break; // Fecha aspa não escapada + } else { + if (std.mem.indexOfScalar(u8, " \t\r\n", args[i]) != null) break; // Fim sem quote + } + i += 1; + } + const value_end = i; + var value = args[value_start..value_end]; + if (in_quote) { + if (i >= args.len or args[i] != '"') return error.UnclosedQuoteInAssignment; + i += 1; // Pula aspa final + value = args[value_start + 1 .. value_end]; // Remove aspas + // TODO: Se precisar, handle escapes como \" aqui (remova \\ antes de ") + } else { + value = std.mem.trim(u8, value, " \t\r\n"); + } + + try list.append(allocator, .{ + .key = try allocator.dupe(u8, key), + .value_expr = try allocator.dupe(u8, value), + .is_literal = in_quote, + }); + } + + return try list.toOwnedSlice(allocator); + } + + fn parseAutoescapeBlock(self: *Parser, allocator: std.mem.Allocator, enabled: bool, raw_open: []const u8) !Node { + var body = std.ArrayList(Node){}; + defer body.deinit(allocator); + + var depth: usize = 1; + + while (self.pos < self.template.len and depth > 0) { + if (try self.parseText(allocator)) |node| { + try body.append(allocator, node); + continue; + } + + if (try self.parseVariable(allocator)) |node| { + try body.append(allocator, node); + continue; + } + + if (try self.parseTag(allocator)) |tag_node| { + const tag_name = tag_node.tag.?.name; + + if (std.mem.eql(u8, tag_name, "autoescape")) { + depth += 1; + try body.append(allocator, tag_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "endautoescape")) { + depth -= 1; + const raw_close = tag_node.tag.?.raw; + + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); + + if (depth == 0) { + return Node{ + .type = .autoescape, + .autoescape = .{ + .enabled = enabled, + .body = try body.toOwnedSlice(allocator), + .raw_open = raw_open, + .raw_close = raw_close, + }, + }; + } + + try body.append(allocator, tag_node); + continue; + } + + try body.append(allocator, tag_node); + } else { + self.advance(1); + } + } + + return error.UnclosedBlock; + } + + fn parseSpacelessBlock(self: *Parser, allocator: std.mem.Allocator, raw_open: []const u8) !Node { + var body = std.ArrayList(Node){}; + defer body.deinit(allocator); + + var depth: usize = 1; + + while (self.pos < self.template.len and depth > 0) { + if (try self.parseText(allocator)) |node| { + try body.append(allocator, node); + continue; + } + + if (try self.parseVariable(allocator)) |node| { + try body.append(allocator, node); + continue; + } + + if (try self.parseTag(allocator)) |tag_node| { + const tag_name = tag_node.tag.?.name; + + if (std.mem.eql(u8, tag_name, "spaceless")) { + depth += 1; + try body.append(allocator, tag_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "endspaceless")) { + depth -= 1; + const raw_close = tag_node.tag.?.raw; + + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); + + if (depth == 0) { + return Node{ + .type = .spaceless, + .spaceless = .{ + .body = try body.toOwnedSlice(allocator), + .raw_open = raw_open, + .raw_close = raw_close, + }, + }; + } + + try body.append(allocator, tag_node); + continue; + } + + try body.append(allocator, tag_node); + } else { + self.advance(1); + } + } + + return error.UnclosedBlock; + } + + fn parseFilterBlock(self: *Parser, allocator: std.mem.Allocator, filters_raw: []const u8, raw_open: []const u8) !Node { + var body = std.ArrayList(Node){}; + defer body.deinit(allocator); + + var depth: usize = 1; + + while (self.pos < self.template.len and depth > 0) { + if (try self.parseText(allocator)) |node| { + try body.append(allocator, node); + continue; + } + + if (try self.parseVariable(allocator)) |node| { + try body.append(allocator, node); + continue; + } + + if (try self.parseTag(allocator)) |tag_node| { + const tag_name = tag_node.tag.?.name; + + if (std.mem.eql(u8, tag_name, "filter")) { + depth += 1; + try body.append(allocator, tag_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "endfilter")) { + depth -= 1; + const raw_close = tag_node.tag.?.raw; + + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); + + if (depth == 0) { + // const filters = try allocator.dupe(u8, filters_raw); + + return Node{ + .type = .filter_block, + .filter_block = .{ + .filters = filters_raw, // já duped + .body = try body.toOwnedSlice(allocator), + .raw_open = raw_open, + .raw_close = raw_close, + }, + }; + } + + try body.append(allocator, tag_node); + continue; + } + + try body.append(allocator, tag_node); + } else { + self.advance(1); + } + } + + return error.UnclosedBlock; + } + + fn parseBlockBlock(self: *Parser, allocator: std.mem.Allocator, name: []const u8, raw_open: []const u8) !Node { + var body = std.ArrayList(Node){}; + defer body.deinit(allocator); + + var depth: usize = 1; + + while (self.pos < self.template.len and depth > 0) { + if (try self.parseText(allocator)) |node| { + try body.append(allocator, node); + continue; + } + + if (try self.parseVariable(allocator)) |node| { + if (node.variable) |v| { + if (std.mem.eql(u8, v.expr, "block.super")) { + try body.append(allocator, Node{ .type = .super, .super = true }); + allocator.free(v.expr); + continue; + } + } + try body.append(allocator, node); + continue; + } + + if (try self.parseTag(allocator)) |tag_node| { + const tag_name = tag_node.tag.?.name; + + if (std.mem.eql(u8, tag_name, "block")) { + depth += 1; + try body.append(allocator, tag_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "endblock")) { + depth -= 1; + const raw_close = tag_node.tag.?.raw; + + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); + + if (depth == 0) { + return Node{ + .type = .block, + .block = .{ + .name = name, + .body = try body.toOwnedSlice(allocator), + .raw_open = raw_open, + .raw_close = raw_close, + }, + }; + } + + try body.append(allocator, tag_node); + continue; + } + + try body.append(allocator, tag_node); + } else { + self.advance(1); + } + } + + return error.UnclosedBlock; + } + fn parseWithBlock(self: *Parser, allocator: std.mem.Allocator, assignments: []const Assignment, raw_open: []const u8) !Node { + std.debug.print("Vou verificar se sou bloco with\n", .{}); + var body = std.ArrayList(Node){}; + defer body.deinit(allocator); + + var depth: usize = 1; + + while (self.pos < self.template.len and depth > 0) { + if (try self.parseText(allocator)) |node| { + std.debug.print("2.3 - Encontrei um texto: {s}\n", .{node.text.?.content}); + try body.append(allocator, node); + continue; + } + + if (try self.parseVariable(allocator)) |node| { + std.debug.print("2.3 - Encontrei uma variável: {s}\n", .{node.variable.?.expr}); + try body.append(allocator, node); + continue; + } + + if (try self.parseTag(allocator)) |tag_node| { + const tag_name = tag_node.tag.?.name; + + if (std.mem.eql(u8, tag_name, "with")) { + depth += 1; + try body.append(allocator, tag_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "endwith")) { + depth -= 1; + const raw_close = tag_node.tag.?.raw; + + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); + + if (depth == 0) { + // para fins de debug + std.debug.print("2.4 - Encontrei um bloco with:\n - assignments: {any}\n - body: {any}\n - raw_open: {s}\n - raw_close: {s}\n", .{ + assignments, + body.items, + raw_open, + raw_close, + }); + // fim para fins de debug + return Node{ + .type = .with_block, + .with = .{ + .assignments = assignments, + .body = try body.toOwnedSlice(allocator), + .raw_open = raw_open, + .raw_close = raw_close, + }, + }; + } + + try body.append(allocator, tag_node); + continue; + } + + try body.append(allocator, tag_node); + } else { + self.advance(1); + } + } + + return error.UnclosedBlock; + } + + fn parseComment(self: *Parser) !void { + // Consome a tag open {% comment %} + // Já estamos após o %}, então só avançamos + var depth: usize = 1; + + while (self.pos < self.template.len and depth > 0) { + if (self.peek(2)) |p| { + if (std.mem.eql(u8, p, "{%")) { + self.advance(2); + self.skipWhitespace(); + + const content_start = self.pos; + while (self.pos < self.template.len) : (self.advance(1)) { + if (self.peek(2)) |closing| { + if (std.mem.eql(u8, closing, "%}")) break; + } + } + + if (self.pos + 2 > self.template.len or !std.mem.eql(u8, self.template[self.pos .. self.pos + 2], "%}")) { + return error.UnclosedTag; + } + + const inner = std.mem.trim(u8, self.template[content_start..self.pos], " \t\r\n"); + + if (std.mem.eql(u8, inner, "comment")) { + depth += 1; + } else if (std.mem.eql(u8, inner, "endcomment")) { + depth -= 1; + if (depth == 0) { + self.advance(2); // consome %} + return; + } + } + + self.advance(2); + continue; + } + } + self.advance(1); + } + + return error.UnclosedComment; + } + + fn parseText(self: *Parser, allocator: std.mem.Allocator) !?Node { + std.debug.print("2.0 - Vou verificar se sou texto\n", .{}); + const start = self.pos; + + std.debug.print("2.1 - meu start é {d}\n", .{start}); + + while (self.pos < self.template.len) { + if (self.peek(2)) |p| { + if (std.mem.eql(u8, p, "{{") or std.mem.eql(u8, p, "{%")) { + std.debug.print("2.2 - fiz o peek de 2 em 2 até que achei {{{{ ou {{%, então parei\n", .{}); + break; + } + } + self.advance(1); + } + + if (self.pos == start) return null; + + const content = try allocator.dupe(u8, self.template[start..self.pos]); + std.debug.print("2.2 - meu content é \'{s}\'\n", .{content}); + return Node{ + .type = .text, + .text = .{ .content = content }, + }; + } + + fn parseVariable(self: *Parser, allocator: std.mem.Allocator) !?Node { + std.debug.print("2.0 - Vou verificar se sou variável\n", .{}); + std.debug.print("2.1 - meu start é {d}\n", .{self.pos}); + + if (self.peek(2)) |p| { + if (!std.mem.eql(u8, p, "{{")) return null; + } else return null; + + self.advance(2); + self.skipWhitespace(); + + const expr_start = self.pos; + while (self.pos < self.template.len) : (self.advance(1)) { + if (self.peek(2)) |p| { + if (std.mem.eql(u8, p, "}}")) break; + + std.debug.print("2.1 - fiz o peek de 2 em 2 até que achei {{{{\n", .{}); + } + } + + std.debug.print("2.2 - fiz o peek de 2 em 2 até que achei }}}}\n", .{}); + if (self.pos + 2 > self.template.len or !std.mem.eql(u8, self.template[self.pos .. self.pos + 2], "}}")) { + std.debug.print("2.3 - deu ruim achei uma variável que não fecha!\n", .{}); + return error.UnclosedVariable; + } + + const full_expr = std.mem.trim(u8, self.template[expr_start..self.pos], " \t\r\n"); + + self.advance(2); + + // Separar expr e filtros + var filters = std.ArrayList(Filter){}; + defer filters.deinit(allocator); + + var expr_end = full_expr.len; + var pipe_pos = std.mem.lastIndexOfScalar(u8, full_expr, '|'); + while (pipe_pos) |pos| { + const filter_part = std.mem.trim(u8, full_expr[pos + 1 .. expr_end], " \t\r\n"); + expr_end = pos; + + const colon_pos = std.mem.indexOfScalar(u8, filter_part, ':'); + const filter_name = if (colon_pos) |cp| std.mem.trim(u8, filter_part[0..cp], " \t\r\n") else filter_part; + const filter_arg = if (colon_pos) |cp| std.mem.trim(u8, filter_part[cp + 1 ..], " \"") else null; + + try filters.append(allocator, .{ + .name = try allocator.dupe(u8, filter_name), + .arg = if (filter_arg) |a| try allocator.dupe(u8, a) else null, + }); + + pipe_pos = std.mem.lastIndexOfScalar(u8, full_expr[0..expr_end], '|'); + } + + const base_expr = std.mem.trim(u8, full_expr[0..expr_end], " \t\r\n"); + const duped_expr = try allocator.dupe(u8, base_expr); + + // Inverte os filters (porque usamos lastIndexOf) + std.mem.reverse(Filter, filters.items); + + std.debug.print("2.3 - meu conteúdo:\n - expr: \'{s}\' \n - filters: {any}\n", .{ duped_expr, filters }); + return Node{ + .type = .variable, + .variable = .{ + .expr = duped_expr, + .filters = try filters.toOwnedSlice(allocator), + }, + }; + } + + fn parseTag(self: *Parser, allocator: std.mem.Allocator) !?Node { + std.debug.print("2.0 - Vou verificar se sou uma tag\n", .{}); + std.debug.print("2.1 - meu start é {d}\n", .{self.pos}); + if (self.peek(2)) |p| { + if (!std.mem.eql(u8, p, "{%")) return null; + std.debug.print("2.1 - fiz o peek de 2 em 2 até que achei {{%\n", .{}); + } else return null; + + const raw_start = self.pos; + self.advance(2); + self.skipWhitespace(); + + const content_start = self.pos; + while (self.pos < self.template.len) : (self.advance(1)) { + if (self.peek(2)) |p| { + if (std.mem.eql(u8, p, "%}")) break; + } + } + + std.debug.print("2.2 - fiz o peek de 2 em 2 até que achei %}}\n", .{}); + if (self.pos + 2 > self.template.len or !std.mem.eql(u8, self.template[self.pos .. self.pos + 2], "%}")) { + return error.UnclosedTag; + } + + const raw_slice = self.template[raw_start .. self.pos + 2]; + const inner = std.mem.trim(u8, self.template[content_start..self.pos], " \t\r\n"); + + const space_idx = std.mem.indexOfScalar(u8, inner, ' ') orelse inner.len; + const name_raw = inner[0..space_idx]; + const args_raw = if (space_idx < inner.len) std.mem.trim(u8, inner[space_idx + 1 ..], " \t\r\n") else ""; + + const name = try allocator.dupe(u8, name_raw); + const args = try allocator.dupe(u8, args_raw); + + std.debug.print("2.3 - meu node:\n - nome: {s}\n - args: {s}\n - raw: {s}\n", .{ name, args, raw_slice }); + self.advance(2); + + return Node{ + .type = .tag, + .tag = .{ + .name = name, + .args = args, + .raw = raw_slice, // slice original, sem dupe + }, + }; + } + + fn parseIfBlock(self: *Parser, allocator: std.mem.Allocator, condition: []const u8, raw_open: []const u8) !Node { + std.debug.print("Vou verificar se sou bloco\n", .{}); + var true_body = std.ArrayList(Node){}; + defer true_body.deinit(allocator); + var false_body = std.ArrayList(Node){}; + defer false_body.deinit(allocator); + + var current_body = &true_body; + var depth: usize = 1; + + while (self.pos < self.template.len and depth > 0) { + if (try self.parseText(allocator)) |node| { + std.debug.print("2.3 - Encontrei um texto: {s}\n", .{node.text.?.content}); + try current_body.append(allocator, node); + continue; + } + + if (try self.parseVariable(allocator)) |node| { + std.debug.print("2.3 - Encontrei uma variável: {s}\n", .{node.variable.?.expr}); + try current_body.append(allocator, node); + continue; + } + + if (try self.parseTag(allocator)) |tag_node| { + const tag_name = tag_node.tag.?.name; + + if (std.mem.eql(u8, tag_name, "if")) { + depth += 1; + try current_body.append(allocator, tag_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "endif")) { + depth -= 1; + const raw_close = tag_node.tag.?.raw; + + // Libera name e args da tag endif + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); + + if (depth == 0) { + std.debug.print("2.4 - Encontrei um bloco if:\n - condition: {s}\n - true_body: {any}\n - false_body: {any}\n - raw_open: {s}\n - raw_close: {s}\n", .{ + condition, + true_body.items, + false_body.items, + raw_open, + raw_close, + }); + return Node{ + .type = .if_block, + .@"if" = .{ + .condition = condition, + .true_body = try true_body.toOwnedSlice(allocator), + .false_body = try false_body.toOwnedSlice(allocator), + .raw_open = raw_open, + .raw_close = raw_close, + }, + }; + } + + // Se depth > 0, é endif aninhado — adiciona como tag normal + try current_body.append(allocator, tag_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "else") and depth == 1) { + current_body = &false_body; + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); + continue; + } + + // Qualquer outra tag + try current_body.append(allocator, tag_node); + } else { + self.advance(1); + } + } + + return error.UnclosedBlock; + } + + fn parseForBlock(self: *Parser, allocator: std.mem.Allocator, loop_var: []const u8, iterable: []const u8, raw_open: []const u8) !Node { + var body = std.ArrayList(Node){}; + defer body.deinit(allocator); + var empty_body = std.ArrayList(Node){}; + defer empty_body.deinit(allocator); + + var current_body = &body; + var depth: usize = 1; + + while (self.pos < self.template.len and depth > 0) { + if (try self.parseText(allocator)) |node| { + std.debug.print("2.3 - Encontrei um texto: {s}\n", .{node.text.?.content}); + try current_body.append(allocator, node); + continue; + } + + if (try self.parseVariable(allocator)) |node| { + std.debug.print("2.3 - Encontrei uma variável: {s}\n", .{node.variable.?.expr}); + try current_body.append(allocator, node); + continue; + } + + if (try self.parseTag(allocator)) |tag_node| { + const tag_name = tag_node.tag.?.name; + + if (std.mem.eql(u8, tag_name, "for")) { + depth += 1; + try current_body.append(allocator, tag_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "endfor")) { + depth -= 1; + const raw_close = tag_node.tag.?.raw; + + if (depth == 0) { + // Libera name e args — essa é a tag de fechamento final + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); + + std.debug.print("2.4 - Encontrei um bloco for:\n - loop_var: {s}\n - iterable: {s}\n - body: {any}\n - empty_body: {any}\n - raw_open: {s}\n - raw_close: {s}\n", .{ + loop_var, + iterable, + body.items, + empty_body.items, + raw_open, + raw_close, + }); + return Node{ + .type = .for_block, + .@"for" = .{ + .loop_var = loop_var, + .iterable = iterable, + .body = try body.toOwnedSlice(allocator), + .empty_body = try empty_body.toOwnedSlice(allocator), + .raw_open = raw_open, + .raw_close = raw_close, + }, + }; + } + + // depth > 0: endfor aninhado — adiciona como tag normal + try current_body.append(allocator, tag_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "empty") and depth == 1) { + current_body = &empty_body; + allocator.free(tag_node.tag.?.name); + allocator.free(tag_node.tag.?.args); + continue; + } + + try current_body.append(allocator, tag_node); + } else { + self.advance(1); + } + } + + return error.UnclosedBlock; + } + + pub fn parse(self: *Parser, allocator: std.mem.Allocator) ![]Node { + var list = std.ArrayList(Node){}; + defer list.deinit(allocator); + + std.debug.print("O template recebido é:\n\n{s}\n\n", .{self.template}); + while (self.pos < self.template.len) { + std.debug.print("1.0 - minha posição ainda é menor que o tamanho do template\n", .{}); + if (try self.parseTag(allocator)) |node| { + std.debug.print("3.0 - na real sou uma tag\n", .{}); + const tag_name = node.tag.?.name; + + std.debug.print("3.1 - meu tag name é: {s}\n", .{tag_name}); + + if (std.mem.eql(u8, tag_name, "autoescape")) { + const args = std.mem.trim(u8, node.tag.?.args, " \t\r\n"); + const raw_open = node.tag.?.raw; + + const enabled = if (std.mem.eql(u8, args, "on")) + true + else if (std.mem.eql(u8, args, "off")) + false + else + return error.InvalidAutoescapeArgument; + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um autoescape\n", .{}); + std.debug.print("===================================\n", .{}); + + const ae_node = try self.parseAutoescapeBlock(allocator, enabled, raw_open); + try list.append(allocator, ae_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "spaceless")) { + const raw_open = node.tag.?.raw; + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um spaceless\n", .{}); + std.debug.print("===================================\n", .{}); + + const spaceless_node = try self.parseSpacelessBlock(allocator, raw_open); + try list.append(allocator, spaceless_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "comment")) { + std.debug.print("3.0 - na real sou um comentário\n", .{}); + std.debug.print("===================================\n", .{}); + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + try self.parseComment(); + continue; + } + + if (std.mem.eql(u8, tag_name, "filter")) { + const filters_raw = node.tag.?.args; + const raw_open = node.tag.?.raw; + + // DUPE O FILTERS IMEDIATAMENTE + const duped_filters = try allocator.dupe(u8, filters_raw); + + // Agora libera a tag open + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + std.debug.print("3.0 - na real sou um filter\n", .{}); + std.debug.print("===================================\n", .{}); + + const filter_node = try self.parseFilterBlock(allocator, duped_filters, raw_open); + try list.append(allocator, filter_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "extends")) { + const parent = std.mem.trim(u8, node.tag.?.args, " \t\""); + + const duped = try allocator.dupe(u8, parent); + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um extends\n", .{}); + std.debug.print("===================================\n", .{}); + try list.append(allocator, Node{ + .type = .extends, + .extends = .{ .parent_name = duped }, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "block")) { + const block_name_raw = node.tag.?.args; + const raw_open = node.tag.?.raw; + + const block_name = std.mem.trim(u8, block_name_raw, " \t\r\n"); + + // DUPE O NOME ANTES DE LIBERAR A TAG + const duped_name = try allocator.dupe(u8, block_name); + + // Agora libera a tag open + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um block\n", .{}); + std.debug.print("===================================\n", .{}); + + const block_node = try self.parseBlockBlock(allocator, duped_name, raw_open); + try list.append(allocator, block_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "now")) { + const args = node.tag.?.args; + + // Remove aspas se existirem + var format_str = args; + if (format_str.len >= 2 and format_str[0] == '"' and format_str[format_str.len - 1] == '"') { + format_str = format_str[1 .. format_str.len - 1]; + } + + const duped_format = try allocator.dupe(u8, format_str); + + // Libera a tag now + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou uma tag now\n", .{}); + std.debug.print("===================================\n", .{}); + + try list.append(allocator, Node{ + .type = .now, + .now = .{ .format = duped_format }, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "with")) { + const args = node.tag.?.args; + const raw_open = node.tag.?.raw; + + const assignments = try self.parseAssignments(allocator, args); + + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + const with_node = try self.parseWithBlock(allocator, assignments, raw_open); + std.debug.print("3.0 - na real sou um bloco with\n", .{}); + std.debug.print("===================================\n", .{}); + try list.append(allocator, with_node); + continue; + } + + if (std.mem.eql(u8, tag_name, "include")) { + const args = node.tag.?.args; + + // Remove aspas se existirem + var template_name = args; + if (template_name.len >= 2 and template_name[0] == '"' and template_name[template_name.len - 1] == '"') { + template_name = template_name[1 .. template_name.len - 1]; + } + + const duped_name = try allocator.dupe(u8, template_name); + + // Libera a tag include + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + std.debug.print("3.0 - na real sou um include\n", .{}); + std.debug.print("===================================\n", .{}); + try list.append(allocator, Node{ + .type = .include, + .include = .{ .template_name = duped_name }, + }); + continue; + } + + if (std.mem.eql(u8, tag_name, "if")) { + const condition_raw = node.tag.?.args; + const raw_open = node.tag.?.raw; + + const condition = try allocator.dupe(u8, condition_raw); + + // Libera apenas name e args da tag open + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + // NÃO chame node.deinit aqui — raw_open ainda é usado + + const if_node = try self.parseIfBlock(allocator, condition, raw_open); + try list.append(allocator, if_node); + + std.debug.print("===================================\n", .{}); + continue; + } + + if (std.mem.eql(u8, tag_name, "for")) { + const args = node.tag.?.args; + const raw_open = node.tag.?.raw; + + const in_pos = std.mem.indexOf(u8, args, " in ") orelse return error.InvalidForSyntax; + const loop_var_raw = std.mem.trim(u8, args[0..in_pos], " \t"); + const iterable_raw = std.mem.trim(u8, args[in_pos + 4 ..], " \t"); + + // DUPE ANTES DE LIBERAR! + const loop_var = try allocator.dupe(u8, loop_var_raw); + const iterable = try allocator.dupe(u8, iterable_raw); + + // Agora sim, libera a tag open + allocator.free(node.tag.?.name); + allocator.free(node.tag.?.args); + + const for_node = try self.parseForBlock(allocator, loop_var, iterable, raw_open); + + std.debug.print("===================================\n", .{}); + try list.append(allocator, for_node); + continue; + } + + // Para tags normais + std.debug.print("===================================\n", .{}); + try list.append(allocator, node); + continue; + } + + if (try self.parseVariable(allocator)) |node| { + std.debug.print("3.0 - na real sou variável\n", .{}); + std.debug.print("4.0 - content: \'{s}\'\n", .{node.variable.?.expr}); + std.debug.print("4.1 - filters: \'{any}\'\n", .{node.variable.?.filters}); + std.debug.print("===================================\n", .{}); + try list.append(allocator, node); + continue; + } + + if (try self.parseText(allocator)) |node| { + std.debug.print("3.0 - na real sou texto\n", .{}); + std.debug.print("4.0 - content: \'{s}\'\n", .{node.text.?.content}); + std.debug.print("===================================\n", .{}); + try list.append(allocator, node); + continue; + } + + self.advance(1); + } + + std.debug.print("\nO resultado disso foi esse:\n", .{}); + for (list.items) |item| { + std.debug.print(" -> type: {s}\n", .{@tagName(item.type)}); + } + std.debug.print("\n", .{}); + return try list.toOwnedSlice(allocator); + } +}; + +pub fn parse(allocator: std.mem.Allocator, template: []const u8) ![]Node { + var p = Parser.init(template); + return try p.parse(allocator); +} diff --git a/src/parser_test.zig b/src/parser_test.zig index 02fef3b..9a57ddb 100644 --- a/src/parser_test.zig +++ b/src/parser_test.zig @@ -2,13 +2,10 @@ const std = @import("std"); const testing = std.testing; const parser = @import("parser.zig"); -test "parse simple text" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("1 - parse simple text\n", .{}); +test "parse texto simples" { const allocator = testing.allocator; const template = "Olá mundo!"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); + const nodes = try parser.parse(allocator, template); defer { for (nodes) |node| { node.deinit(allocator); @@ -21,13 +18,10 @@ test "parse simple text" { try testing.expectEqualStrings("Olá mundo!", nodes[0].text.?.content); } -test "parse simple var" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("2 - parse simple var\n", .{}); +test "parse variável simples" { const allocator = testing.allocator; const template = "Olá {{ nome }}!"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); + const nodes = try parser.parse(allocator, template); defer { for (nodes) |node| { node.deinit(allocator); @@ -47,13 +41,10 @@ test "parse simple var" { try testing.expectEqualStrings("!", nodes[2].text.?.content); } -test "parse var with spaces" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("3 - parse var with spaces\n", .{}); +test "parse variável com espaços" { const allocator = testing.allocator; const template = "{{ espacos }}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); + const nodes = try parser.parse(allocator, template); defer { for (nodes) |node| { node.deinit(allocator); @@ -66,353 +57,331 @@ test "parse var with spaces" { try testing.expectEqualStrings("espacos", nodes[0].variable.?.expr); } -test "parse autoescape on" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("4 - parse autoescape on\n", .{}); +test "parse tag simples" { const allocator = testing.allocator; - const template = "{% autoescape on %}Texto negrito{% endautoescape %}"; - const expected = "Texto negrito"; - - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); + const template = "Antes {% minha_tag %} Depois"; + const nodes = try parser.parse(allocator, template); defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - if (nodes[0].tag) |tag| { - try testing.expect(tag.body.autoescape.enabled == true); - try testing.expectEqual(@as(usize, 1), tag.body.autoescape.body.len); - try testing.expectEqualStrings(expected, tag.body.autoescape.body[0].text.?.content); - } -} - -test "parse autoescape off" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("5 - parse autoescape off\n", .{}); - const allocator = testing.allocator; - const template = "{% autoescape off %}Texto negrito{% endautoescape %}"; - const expected = "Texto negrito"; - - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - if (nodes[0].tag) |tag| { - try testing.expect(tag.body.autoescape.enabled == false); - try testing.expectEqual(@as(usize, 1), tag.body.autoescape.body.len); - try testing.expectEqualStrings(expected, tag.body.autoescape.body[0].text.?.content); - } -} -test "parse simple block" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("6 - parse simple block\n", .{}); - const allocator = testing.allocator; - const template = "{% block titulo %}Meu Título{% endblock %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expectEqual(nodes[0].tag.?.kind, .block); - try testing.expectEqualStrings("titulo", nodes[0].tag.?.body.block.name); - const b = nodes[0].tag.?.body.block.body; - try testing.expectEqual(@as(usize, 1), b.len); - try testing.expectEqual(b[0].type, .text); - try testing.expectEqualStrings("Meu Título", b[0].text.?.content); -} - -test "parse block with super" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("7 - parse block with super\n", .{}); - - const allocator = testing.allocator; - const template = "{% block conteudo %}{{ block.super }} Conteúdo filho{% endblock %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expectEqualStrings("conteudo", nodes[0].tag.?.body.block.name); - const b = nodes[0].tag.?.body.block.body; - try testing.expectEqual(@as(usize, 2), b.len); - try testing.expect(b[0].type == .tag); - try testing.expect(b[0].tag.?.kind == .super); - try testing.expect(b[1].type == .text); - try testing.expectEqualStrings(" Conteúdo filho", b[1].text.?.content); -} - -// TODO: check it -test "parse block with block inside" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("8 - parse block with block inside\n", .{}); - - const allocator = testing.allocator; - const template = "{% block conteudo %}{% block inner %}Conteúdo filho{% endblock %}{% endblock %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expectEqualStrings("conteudo", nodes[0].tag.?.body.block.name); - const b = nodes[0].tag.?.body.block.body; - try testing.expectEqual(@as(usize, 3), b.len); - try testing.expect(b[0].type == .tag); - try testing.expect(b[0].tag.?.kind == .block); - try testing.expect(b[1].type == .text); - try testing.expectEqualStrings("Conteúdo filho", b[1].text.?.content); - try testing.expect(b[2].type == .tag); - // std.debug.print("block:\n {any}",.{nodes[0].tag.?.body.block}); - // for(b) |node| std.debug.print("block:\n {any}\n",.{node}); - // try testing.expectEqualStrings("inner",b[0].tag.?.body.block.name); - // try testing.expect(b[1].type == .text); - // try testing.expectEqualStrings(" Conteúdo filho",b[1].text.?.content); -} - -test "parse simple csrf_token" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("9 - parse simple csrf_token\n", .{}); - - const allocator = testing.allocator; - const template = "
    {% csrf_token %}
    "; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); + for (nodes) |node| { + node.deinit(allocator); + } allocator.free(nodes); } try testing.expectEqual(@as(usize, 3), nodes.len); - try testing.expect(nodes[0].type == .text); - try testing.expectEqualStrings("
    ", nodes[0].text.?.content); - try testing.expect(nodes[1].type == .tag); - try testing.expect(nodes[1].tag.?.kind == .csrf_token); - - try testing.expect(nodes[2].type == .text); - try testing.expectEqualStrings("
    ", nodes[2].text.?.content); -} - -test "parse just csrf_token" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("10 - parse just csrf_token\n", .{}); - - const allocator = testing.allocator; - const template = "{% csrf_token %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .csrf_token); -} - -test "parse comment" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("11 - parse comment\n", .{}); - - const allocator = testing.allocator; - const template = "Bazinga! {% comment %}{% for item in lista %}Tem{% empty %}Vazio{% endfor %}{% endcomment %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .text); - try testing.expectEqualStrings("Bazinga! ", nodes[0].text.?.content); -} - -test "parse comment inside block" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("12 - parse comment\n", .{}); - - const allocator = testing.allocator; - const template = "{% block conteudo %}Bazinga!{% comment %}{% for item in lista %}Tem{% empty %}Vazio{% endfor %}{% endcomment %} haha{% endblock %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - - const child_nodes = nodes[0].tag.?.body.block.body; - - try testing.expectEqual(@as(usize, 2), child_nodes.len); - - try testing.expect(child_nodes[0].type == .text); - try testing.expect(child_nodes[1].type == .text); - try testing.expectEqualStrings("Bazinga!", child_nodes[0].text.?.content); - try testing.expectEqualStrings(" haha", child_nodes[1].text.?.content); -} - -test "parse simple cycle" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("13 - parse simple cycle\n", .{}); - - const allocator = testing.allocator; - const template = "{% cycle 'row1' 'row2' %}"; - - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - - const args = nodes[0].tag.?.body.cycle.values; - try testing.expectEqual(@as(usize, 2), args.len); - try testing.expectEqualStrings("row1", args[0]); - try testing.expectEqualStrings("row2", args[1]); -} - -test "parse cycle values without quotes" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("14 - parse cycle values without quotes\n", .{}); - - const allocator = testing.allocator; - const template = "{% cycle row1 row2 row3 %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - const args = nodes[0].tag.?.body.cycle.values; - try testing.expectEqual(@as(usize, 3), args.len); - try testing.expectEqualStrings("row1", args[0]); - try testing.expectEqualStrings("row2", args[1]); - try testing.expectEqualStrings("row3", args[2]); -} - -test "parse simple debug" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("15 - parse simple debug\n", .{}); - - const allocator = testing.allocator; - const template = "Antes {% debug %} Depois"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 3), nodes.len); try testing.expect(nodes[0].type == .text); try testing.expectEqualStrings("Antes ", nodes[0].text.?.content); try testing.expect(nodes[1].type == .tag); - try testing.expect(nodes[1].tag.?.kind == .debug); + try testing.expectEqualStrings("minha_tag", nodes[1].tag.?.name); + try testing.expectEqualStrings("", nodes[1].tag.?.args); try testing.expect(nodes[2].type == .text); try testing.expectEqualStrings(" Depois", nodes[2].text.?.content); } -test "parse extends" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("16 - parse extends\n", .{}); - +test "parse if block básico" { const allocator = testing.allocator; - const template = "{% extends \"base.html\" %}"; - - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); + const template = "{% if usuario.logado %}Bem-vindo!{% endif %}"; + const nodes = try parser.parse(allocator, template); defer { - for (nodes) |node| node.deinit(allocator); + for (nodes) |node| { + node.deinit(allocator); + } allocator.free(nodes); } try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .extends); - try testing.expectEqualStrings("base.html", nodes[0].tag.?.body.extends.parent_name); + try testing.expect(nodes[0].type == .if_block); + + const ib = nodes[0].@"if".?; + try testing.expectEqualStrings("usuario.logado", ib.condition); + try testing.expectEqual(@as(usize, 1), ib.true_body.len); + try testing.expect(nodes[0].@"if".?.true_body[0].type == .text); + try testing.expectEqualStrings("Bem-vindo!", ib.true_body[0].text.?.content); + try testing.expectEqual(@as(usize, 0), ib.false_body.len); } -test "parse simple filter block" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("17 - parse simple filter block\n", .{}); - +test "parse if block sem else" { const allocator = testing.allocator; - const template = "{% filter upper %}olá mundo{% endfilter %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); + const template = "{% if cond %}Verdadeiro{% endif %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| { + node.deinit(allocator); + } + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .if_block); + const ib = nodes[0].@"if".?; + try testing.expectEqualStrings("cond", ib.condition); + try testing.expectEqual(@as(usize, 1), ib.true_body.len); + try testing.expectEqualStrings("Verdadeiro", ib.true_body[0].text.?.content); + try testing.expectEqual(@as(usize, 0), ib.false_body.len); +} + +test "parse if block com else" { + const allocator = testing.allocator; + const template = "{% if cond %}Verdadeiro{% else %}Falso{% endif %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| { + node.deinit(allocator); + } + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .if_block); + const ib = nodes[0].@"if".?; + try testing.expectEqualStrings("cond", ib.condition); + try testing.expectEqual(@as(usize, 1), ib.true_body.len); + try testing.expectEqualStrings("Verdadeiro", ib.true_body[0].text.?.content); + try testing.expectEqual(@as(usize, 1), ib.false_body.len); + try testing.expectEqualStrings("Falso", ib.false_body[0].text.?.content); +} + +test "parse for block sem empty" { + const allocator = testing.allocator; + const template = "{% for item in lista %}Item: {{ item }}{% endfor %}"; + const nodes = try parser.parse(allocator, template); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - const fb = nodes[0].tag.?.body.filter_block; - try testing.expectEqualStrings("upper", fb.filters); + try testing.expect(nodes[0].type == .for_block); + const fb = nodes[0].@"for".?; + try testing.expectEqualStrings("item", fb.loop_var); + try testing.expectEqualStrings("lista", fb.iterable); + try testing.expectEqual(@as(usize, 2), fb.body.len); // <--- corrigido: 2 nós + try testing.expectEqual(@as(usize, 0), fb.empty_body.len); + + try testing.expect(fb.body[0].type == .text); + try testing.expectEqualStrings("Item: ", fb.body[0].text.?.content); + try testing.expect(fb.body[1].type == .variable); + try testing.expectEqualStrings("item", fb.body[1].variable.?.expr); +} + +test "parse for block com empty" { + const allocator = testing.allocator; + const template = "{% for item in lista %}Tem{% empty %}Vazio{% endfor %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .for_block); + const fb = nodes[0].@"for".?; + try testing.expectEqual(@as(usize, 1), fb.body.len); + try testing.expectEqualStrings("Tem", fb.body[0].text.?.content); + try testing.expectEqual(@as(usize, 1), fb.empty_body.len); + try testing.expectEqualStrings("Vazio", fb.empty_body[0].text.?.content); +} + +test "parse comment" { + const allocator = testing.allocator; + const template = "Bazinga! {% comment %}{% for item in lista %}Tem{% empty %}Vazio{% endfor %}{% endcomment %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .text); + try testing.expectEqualStrings("Bazinga! ", nodes[0].text.?.content); +} + +test "parse include simples" { + const allocator = testing.allocator; + const template = "Cabeçalho {% include \"header.zdt\" %} Conteúdo {% include \"footer.zdt\" %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 4), nodes.len); + + try testing.expect(nodes[0].type == .text); + try testing.expectEqualStrings("Cabeçalho ", nodes[0].text.?.content); + + try testing.expect(nodes[1].type == .include); + try testing.expectEqualStrings("header.zdt", nodes[1].include.?.template_name); + + try testing.expect(nodes[2].type == .text); + try testing.expectEqualStrings(" Conteúdo ", nodes[2].text.?.content); + + try testing.expect(nodes[3].type == .include); + try testing.expectEqualStrings("footer.zdt", nodes[3].include.?.template_name); + +} + +test "parse include sem aspas (erro esperado no futuro, mas por enquanto aceita)" { + const allocator = testing.allocator; + const template = "{% include header.zdt %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .include); + try testing.expectEqualStrings("header.zdt", nodes[0].include.?.template_name); +} + +test "parse with simples" { + const allocator = testing.allocator; + const template = "{% with nome=\"Lucas\" idade=30 %}Olá {{ nome }}, você tem {{ idade }} anos.{% endwith %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .with_block); + const w = nodes[0].with.?; + try testing.expectEqual(@as(usize, 2), w.assignments.len); + try testing.expectEqual(@as(usize, 5), w.body.len); + try testing.expect(w.body[0].type == .text); +} + +test "parse now simples" { + const allocator = testing.allocator; + const template = "Data atual: {% now \"Y-m-d\" %} às {% now \"H:i\" %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 4), nodes.len); + + try testing.expect(nodes[0].type == .text); + try testing.expectEqualStrings("Data atual: ", nodes[0].text.?.content); + + try testing.expect(nodes[1].type == .now); + try testing.expectEqualStrings("Y-m-d", nodes[1].now.?.format); + + try testing.expect(nodes[2].type == .text); + try testing.expectEqualStrings(" às ", nodes[2].text.?.content); + + try testing.expect(nodes[3].type == .now); + try testing.expectEqualStrings("H:i", nodes[3].now.?.format); + +} + +test "parse now sem aspas" { + const allocator = testing.allocator; + const template = "{% now Y-m-d %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .now); + try testing.expectEqualStrings("Y-m-d", nodes[0].now.?.format); +} + +test "parse extends" { + const allocator = testing.allocator; + const template = "{% extends \"base.zdt\" %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .extends); + try testing.expectEqualStrings("base.zdt", nodes[0].extends.?.parent_name); +} + +test "parse block simples" { + const allocator = testing.allocator; + const template = "{% block titulo %}Meu Título{% endblock %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .block); + const b = nodes[0].block.?; + try testing.expectEqualStrings("titulo", b.name); + try testing.expectEqual(@as(usize, 1), b.body.len); + try testing.expectEqualStrings("Meu Título", b.body[0].text.?.content); +} + +test "parse block com super" { + const allocator = testing.allocator; + const template = "{% block conteudo %}{{ block.super }} Conteúdo filho{% endblock %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .block); + const b = nodes[0].block.?; + try testing.expectEqual(@as(usize, 2), b.body.len); + try testing.expect(b.body[0].type == .super); + // try testing.expectEqualStrings("conteudo", b.name); + try testing.expectEqualStrings(" Conteúdo filho", b.body[1].text.?.content); +} + + +test "parse filter block simples" { + const allocator = testing.allocator; + const template = "{% filter upper %}olá mundo{% endfilter %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .filter_block); + const fb = nodes[0].filter_block.?; + try testing.expectEqualStrings("upper", fb.filters); // correto try testing.expectEqual(@as(usize, 1), fb.body.len); try testing.expectEqualStrings("olá mundo", fb.body[0].text.?.content); } -test "parse filter block multiple filters" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("18 - parse filter block multiple filters\n", .{}); - +test "parse filter block com múltiplos filtros" { const allocator = testing.allocator; const template = "{% filter upper|escape %}Conteúdo negrito{% endfilter %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); + const nodes = try parser.parse(allocator, template); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .filter_block); - const fb = nodes[0].tag.?.body.filter_block; + try testing.expect(nodes[0].type == .filter_block); + const fb = nodes[0].filter_block.?; try testing.expectEqualStrings("upper|escape", fb.filters); try testing.expectEqual(@as(usize, 1), fb.body.len); try testing.expectEqualStrings("Conteúdo negrito", fb.body[0].text.?.content); } -test "parse variable with filters" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("19 - parse variable with filters\n", .{}); - +test "parse variable com filtros" { const allocator = testing.allocator; const template = "{{ nome|upper|default:\"Visitante\" }}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); + const nodes = try parser.parse(allocator, template); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); @@ -429,842 +398,521 @@ test "parse variable with filters" { try testing.expectEqualStrings("Visitante", v.filters[1].arg.?); } -test "parse simple firstof" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("20 - parse simple firstof\n", .{}); - +test "parse autoescape on" { const allocator = testing.allocator; - const template = "Valor: {% firstof var1 var2 var3 %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 2), nodes.len); - - try testing.expect(nodes[0].type == .text); - try testing.expectEqualStrings("Valor: ", nodes[0].text.?.content); - - try testing.expect(nodes[1].type == .tag); - const fo = nodes[1].tag.?.body.firstof.values; - try testing.expectEqual(@as(usize, 3), fo.len); - try testing.expectEqualStrings("var1", fo[0]); - try testing.expectEqualStrings("var2", fo[1]); - try testing.expectEqualStrings("var3", fo[2]); - try testing.expectEqualStrings("", nodes[1].tag.?.body.firstof.fallback); -} - -test "parse firstof with fallback" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("21 - parse firstof with fallback\n", .{}); - - const allocator = testing.allocator; - const template = "{% firstof var1 var2 \"Nenhum valor\" %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); + const template = "{% autoescape on %}Texto negrito{% endautoescape %}"; + const nodes = try parser.parse(allocator, template); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - const fo = nodes[0].tag.?.body.firstof.values; - try testing.expectEqual(@as(usize, 2), fo.len); - try testing.expectEqualStrings("var1", fo[0]); - try testing.expectEqualStrings("var2", fo[1]); - try testing.expectEqualStrings("Nenhum valor", nodes[0].tag.?.body.firstof.fallback); + try testing.expect(nodes[0].type == .autoescape); + const ae = nodes[0].autoescape.?; + try testing.expect(ae.enabled == true); + try testing.expectEqual(@as(usize, 1), ae.body.len); + try testing.expectEqualStrings("Texto negrito", ae.body[0].text.?.content); } -test "parse for block whithout empty" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("22 - parse for block whithout empty\n", .{}); - +test "parse autoescape off" { const allocator = testing.allocator; - const template = "{% for item in lista %}Item: {{ item }}{% endfor %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); + const template = "{% autoescape off %}Texto negrito{% endautoescape %}"; + const nodes = try parser.parse(allocator, template); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - const fb = nodes[0].tag.?.body.@"for"; - try testing.expectEqualStrings("item", fb.loop_var); - try testing.expectEqualStrings("lista", fb.iterable); - try testing.expectEqual(@as(usize, 2), fb.body.len); - try testing.expectEqual(@as(usize, 0), fb.empty_body.len); - - try testing.expect(fb.body[0].type == .text); - try testing.expectEqualStrings("Item: ", fb.body[0].text.?.content); - try testing.expect(fb.body[1].type == .variable); - try testing.expectEqualStrings("item", fb.body[1].variable.?.expr); + try testing.expect(nodes[0].type == .autoescape); + const ae = nodes[0].autoescape.?; + try testing.expect(ae.enabled == false); + try testing.expectEqual(@as(usize, 1), ae.body.len); } -test "parse for block with empty" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("23 - parse for block with empty\n", .{}); - +test "parse spaceless simples" { const allocator = testing.allocator; - const template = "{% for item in lista %}Tem{% empty %}Vazio{% endfor %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); + const template = "{% spaceless %}

    Texto com espaços

    {% endspaceless %}"; + const nodes = try parser.parse(allocator, template); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - const fb = nodes[0].tag.?.body.@"for"; - try testing.expectEqual(@as(usize, 1), fb.body.len); - try testing.expectEqualStrings("Tem", fb.body[0].text.?.content); - try testing.expectEqual(@as(usize, 1), fb.empty_body.len); - try testing.expectEqualStrings("Vazio", fb.empty_body[0].text.?.content); -} - -test "parse basic if block" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("24 - parse basic if block\n", .{}); - - const allocator = testing.allocator; - const template = "{% if user.logged %}Welcome!{% endif %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| { - node.deinit(allocator); - } - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - - const ib = nodes[0].tag.?.body.@"if"; - try testing.expectEqualStrings("user.logged", ib.condition); - try testing.expectEqual(@as(usize, 1), ib.true_body.len); - try testing.expect(ib.true_body[0].type == .text); - try testing.expectEqualStrings("Welcome!", ib.true_body[0].text.?.content); - try testing.expectEqual(@as(usize, 0), ib.false_body.len); -} - -test "parse if block whithout else" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("25 - parse if block whithout else\n", .{}); - - const allocator = testing.allocator; - const template = "{% if cond %}Verdadeiro{% endif %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| { - node.deinit(allocator); - } - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - const ib = nodes[0].tag.?.body.@"if"; - try testing.expectEqualStrings("cond", ib.condition); - try testing.expectEqual(@as(usize, 1), ib.true_body.len); - try testing.expectEqualStrings("Verdadeiro", ib.true_body[0].text.?.content); - try testing.expectEqual(@as(usize, 0), ib.false_body.len); -} - -test "parse if block with else" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("26 - parse if block with else\n", .{}); - - const allocator = testing.allocator; - const template = "{% if cond %}Verdadeiro{% else %}Falso{% endif %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| { - node.deinit(allocator); - } - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .if_block); - const ib = nodes[0].tag.?.body.@"if"; - try testing.expectEqualStrings("cond", ib.condition); - try testing.expectEqual(@as(usize, 1), ib.true_body.len); - try testing.expectEqualStrings("Verdadeiro", ib.true_body[0].text.?.content); - try testing.expectEqual(@as(usize, 1), ib.false_body.len); - try testing.expectEqualStrings("Falso", ib.false_body[0].text.?.content); -} - -test "parse simple include" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("27 - parse simple include\n", .{}); - - const allocator = testing.allocator; - const template = "Cabeçalho {% include \"header.html\" %} Conteúdo {% include \"footer.html\" %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 4), nodes.len); - - try testing.expect(nodes[0].type == .text); - try testing.expectEqualStrings("Cabeçalho ", nodes[0].text.?.content); - - try testing.expect(nodes[1].type == .tag); - try testing.expect(nodes[1].tag.?.kind == .include); - try testing.expectEqualStrings("header.html", nodes[1].tag.?.body.include.template_path); - - try testing.expect(nodes[2].type == .text); - try testing.expectEqualStrings(" Conteúdo ", nodes[2].text.?.content); - - try testing.expect(nodes[3].type == .tag); - try testing.expectEqualStrings("footer.html", nodes[3].tag.?.body.include.template_path); -} - -test "parse include without quotes (it is ok for now)" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("28 - parse include without quotes (it is ok for now)\n", .{}); - - const allocator = testing.allocator; - const template = "{% include header.html %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .include); - try testing.expectEqualStrings("header.html", nodes[0].tag.?.body.include.template_path); -} - -test "parse simple load" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("29 - parse simple load\n", .{}); - - const allocator = testing.allocator; - const template = "{% load i18n %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .load); - const l = nodes[0].tag.?.body.load; - try testing.expectEqual(@as(usize, 1), l.libraries.len); - try testing.expectEqualStrings("i18n", l.libraries[0]); -} - -test "parse load with multiple libraries" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("30 - parse load with multiple libraries\n", .{}); - - const allocator = testing.allocator; - const template = "{% load admin_urls static %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .load); - const l = nodes[0].tag.?.body.load; - try testing.expectEqual(@as(usize, 2), l.libraries.len); - try testing.expectEqualStrings("admin_urls", l.libraries[0]); - try testing.expectEqualStrings("static", l.libraries[1]); -} -test "parse simple lorem" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("31 - parse simple lorem\n", .{}); - - const allocator = testing.allocator; - const template = "{% lorem %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .lorem); - const l = nodes[0].tag.?.body.lorem; - try testing.expect(l.count == null); - try testing.expect(l.method == null); - try testing.expect(l.random == false); -} - -test "parse lorem with arguments" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("32 - parse lorem with arguments\n", .{}); - - const allocator = testing.allocator; - const template = "{% lorem 5 p true %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .lorem); - const l = nodes[0].tag.?.body.lorem; - try testing.expectEqualStrings("5", l.count.?); - try testing.expectEqualStrings("p", l.method.?); - try testing.expect(l.random == true); -} - -test "parse simple now" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("33 - parse simple now\n", .{}); - - const allocator = testing.allocator; - const template = "Data atual: {% now \"Y-m-d\" %} às {% now \"H:i\" %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 4), nodes.len); - - try testing.expect(nodes[0].type == .text); - try testing.expectEqualStrings("Data atual: ", nodes[0].text.?.content); - - try testing.expect(nodes[1].type == .tag); - try testing.expect(nodes[1].tag.?.kind == .now); - try testing.expectEqualStrings("Y-m-d", nodes[1].tag.?.body.now.format); - - try testing.expect(nodes[2].type == .text); - try testing.expectEqualStrings(" às ", nodes[2].text.?.content); - - try testing.expect(nodes[3].type == .tag); - try testing.expect(nodes[3].tag.?.kind == .now); - try testing.expectEqualStrings("H:i", nodes[3].tag.?.body.now.format); -} - -test "parse now whithout quotes" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("34 - parse now whithout quotes\n", .{}); - - const allocator = testing.allocator; - const template = "{% now Y-m-d %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .now); - try testing.expectEqualStrings("Y-m-d", nodes[0].tag.?.body.now.format); -} - -test "parse simple partial" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("35 - parse simple partial\n", .{}); - - const allocator = testing.allocator; - const template = "Início {% partial \"cabecalho\" %} Fim"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 3), nodes.len); - try testing.expect(nodes[0].type == .text); - try testing.expectEqualStrings("Início ", nodes[0].text.?.content); - - try testing.expect(nodes[1].type == .tag); - try testing.expect(nodes[1].tag.?.kind == .partial); - try testing.expectEqualStrings("cabecalho", nodes[1].tag.?.body.partial.name); - - try testing.expect(nodes[2].type == .text); - try testing.expectEqualStrings(" Fim", nodes[2].text.?.content); -} - -test "parse partial whithout quotes" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("36 - parse partial whithout quotes\n", .{}); - - const allocator = testing.allocator; - const template = "Início {% partial cabecalho %} Fim"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 3), nodes.len); - try testing.expect(nodes[0].type == .text); - try testing.expectEqualStrings("Início ", nodes[0].text.?.content); - - try testing.expect(nodes[1].type == .tag); - try testing.expect(nodes[1].tag.?.kind == .partial); - try testing.expectEqualStrings("cabecalho", nodes[1].tag.?.body.partial.name); - - try testing.expect(nodes[2].type == .text); - try testing.expectEqualStrings(" Fim", nodes[2].text.?.content); -} - -test "parse simple partialdef" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("37 - parse simple partialdef\n", .{}); - - const allocator = testing.allocator; - const template = "{% partialdef cabecalho %}Cabeçalho{% endpartialdef %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .partialdef); - const pd = nodes[0].tag.?.body.partialdef; - try testing.expectEqualStrings("cabecalho", pd.name); - try testing.expectEqual(@as(usize, 1), pd.body.len); - try testing.expectEqualStrings("Cabeçalho", pd.body[0].text.?.content); -} - -test "parse simple querystring" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("38 - parse simple querystring\n", .{}); - - const allocator = testing.allocator; - const template = "Link: {% querystring \"page=2\" %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 2), nodes.len); - try testing.expect(nodes[1].type == .tag); - try testing.expect(nodes[1].tag.?.kind == .querystring); - const qs = nodes[1].tag.?.body.querystring; - try testing.expectEqual(@as(usize, 1), qs.modifications.len); - try testing.expectEqualStrings("page=2", qs.modifications[0]); -} - -test "parse querystring with multiple modifications" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("39 - parse querystring with multiple modifications\n", .{}); - - const allocator = testing.allocator; - const template = "{% querystring \"ordenar=-nome\" \"pagina\" None %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .querystring); - const qs = nodes[0].tag.?.body.querystring; - try testing.expectEqual(@as(usize, 3), qs.modifications.len); - try testing.expectEqualStrings("ordenar=-nome", qs.modifications[0]); - try testing.expectEqualStrings("pagina", qs.modifications[1]); - try testing.expectEqualStrings("None", qs.modifications[2]); -} - -test "parse simple regroup" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("40 - parse simple regroup\n", .{}); - - const allocator = testing.allocator; - const template = "{% regroup pessoas by cidade as grupos_cidade %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .regroup); - const r = nodes[0].tag.?.body.regroup; - try testing.expectEqualStrings("pessoas", r.source); - try testing.expectEqualStrings("cidade", r.by); - try testing.expectEqualStrings("grupos_cidade", r.as_var); -} - -test "parse simple resetcycle" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("41 - parse simple resetcycle\n", .{}); - - const allocator = testing.allocator; - const template = "{% resetcycle %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .resetcycle); - const rc = nodes[0].tag.?.body.resetcycle; - try testing.expect(rc.cycle_name == null); -} - -test "parse resetcycle with name" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("42 - parse resetcycle with name\n", .{}); - - const allocator = testing.allocator; - const template = "{% resetcycle rowclass %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .resetcycle); - const rc = nodes[0].tag.?.body.resetcycle; - try testing.expect(rc.cycle_name != null); - try testing.expectEqualStrings("rowclass", rc.cycle_name.?); -} -test "parse simple spaceless" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("43 - parse simple spaceless\n", .{}); - - const allocator = testing.allocator; - const template = "{% spaceless %}

    Foo

    {% endspaceless %}"; - - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .spaceless); - const sl = nodes[0].tag.?.body.spaceless; + try testing.expect(nodes[0].type == .spaceless); + const sl = nodes[0].spaceless.?; try testing.expectEqual(@as(usize, 1), sl.body.len); - try testing.expectEqualStrings("

    Foo

    ", sl.body[0].text.?.content); + try testing.expectEqualStrings("

    Texto com espaços

    ", sl.body[0].text.?.content); } -// TODO: check nested spaceless -test "parse spaceless nested" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("44 - parse spaceless nested\n", .{}); - +test "parse spaceless aninhado" { const allocator = testing.allocator; const template = "{% spaceless %}Outer {% spaceless %}Inner{% endspaceless %} Outer{% endspaceless %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); + const nodes = try parser.parse(allocator, template); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .spaceless); - const sl = nodes[0].tag.?.body.spaceless; - - try testing.expectEqual(@as(usize, 4), sl.body.len); -} -test "parse templatetag openblock" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("45 - parse templatetag openblock\n", .{}); - - const allocator = testing.allocator; - const template = "{% templatetag openblock %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); + try testing.expect(nodes[0].type == .spaceless); + const sl = nodes[0].spaceless.?; + for (sl.body) |b| { + std.debug.print("type: {s}\n", .{@tagName(b.type)}); + if (b.type == .spaceless) { + std.debug.print(" - tag -> spaceless\n", .{}); + } + if(b.type == .text) { + std.debug.print(" - text -> {s}\n", .{b.text.?.content}); + } + std.debug.print("----------\n", .{}); } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .templatetag); - try testing.expect(nodes[0].tag.?.body.templatetag.kind == .openblock); + try testing.expectEqual(@as(usize, 3), sl.body.len); } -test "parse templatetag closevariable" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("46 - parse templatetag closevariable\n", .{}); - +test "parse verbatim simples" { const allocator = testing.allocator; - const template = "{% templatetag closevariable %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .templatetag); - try testing.expect(nodes[0].tag.?.body.templatetag.kind == .closevariable); -} - -test "parse simple url" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("47 - parse simple url\n", .{}); - - const allocator = testing.allocator; - const template = "Link: {% url 'home' %} Fim"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); + const template = "Texto {% verbatim %}{{ variável }}{% endblock %}{% endverbatim %} Texto"; + const nodes = try parser.parse(allocator, template); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 3), nodes.len); - - try testing.expect(nodes[0].type == .text); - try testing.expectEqualStrings("Link: ", nodes[0].text.?.content); - - try testing.expect(nodes[1].type == .tag); - try testing.expect(nodes[1].tag.?.kind == .url); - const u = nodes[1].tag.?.body.url; - try testing.expectEqualStrings("home", u.name); - try testing.expectEqual(@as(usize, 0), u.args.len); - - try testing.expect(nodes[2].type == .text); - try testing.expectEqualStrings(" Fim", nodes[2].text.?.content); + try testing.expectEqualStrings("Texto ", nodes[0].text.?.content); + try testing.expectEqualStrings("{{ variável }}{% endblock %}", nodes[1].text.?.content); + try testing.expectEqualStrings(" Texto", nodes[2].text.?.content); } -test "parse url with arguments" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("48 - parse url with arguments\n", .{}); - +test "parse verbatim aninhado" { const allocator = testing.allocator; - const template = "{% url 'post_detail' post.id \"comentario\" %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); + const template = "{% verbatim %}Outer {% verbatim %}Inner{% endverbatim %} Outer{% endverbatim %}"; + const nodes = try parser.parse(allocator, template); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .url); - const u = nodes[0].tag.?.body.url; + try testing.expect(nodes[0].type == .text); + try testing.expectEqualStrings("Outer {% verbatim %}Inner{% endverbatim %} Outer", nodes[0].text.?.content); +} + +test "parse url simples" { + const allocator = testing.allocator; + const template = "Link: {% url 'home' %} Fim"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 3), nodes.len); + try testing.expect(nodes[1].type == .url); + const u = nodes[1].url.?; + try testing.expectEqualStrings("home", u.name); + try testing.expectEqual(@as(usize, 0), u.args.len); +} + +test "parse url com argumentos" { + const allocator = testing.allocator; + const template = "{% url 'post_detail' post.id \"comentario\" %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .url); + const u = nodes[0].url.?; try testing.expectEqualStrings("post_detail", u.name); try testing.expectEqual(@as(usize, 2), u.args.len); try testing.expectEqualStrings("post.id", u.args[0]); try testing.expectEqualStrings("\"comentario\"", u.args[1]); } -test "parse simple widthratio" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("49 - parse simple widthratio\n", .{}); - +test "parse cycle simples" { const allocator = testing.allocator; - const template = "Progresso: {% widthratio progresso 0 100 %}%"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); + const template = "{% cycle 'row1' 'row2' %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .cycle); + const c = nodes[0].cycle.?; + try testing.expectEqual(@as(usize, 2), c.values.len); + try testing.expectEqualStrings("row1", c.values[0]); + try testing.expectEqualStrings("row2", c.values[1]); +} + +test "parse cycle com valores sem aspas" { + const allocator = testing.allocator; + const template = "{% cycle row1 row2 row3 %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .cycle); + const c = nodes[0].cycle.?; + try testing.expectEqual(@as(usize, 3), c.values.len); + try testing.expectEqualStrings("row1", c.values[0]); + try testing.expectEqualStrings("row2", c.values[1]); + try testing.expectEqualStrings("row3", c.values[2]); +} + +test "parse firstof simples" { + const allocator = testing.allocator; + const template = "Valor: {% firstof var1 var2 var3 %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 2), nodes.len); // corrigido + + try testing.expect(nodes[0].type == .text); + try testing.expectEqualStrings("Valor: ", nodes[0].text.?.content); + + try testing.expect(nodes[1].type == .firstof); + const fo = nodes[1].firstof.?; + try testing.expectEqual(@as(usize, 3), fo.values.len); + try testing.expectEqualStrings("var1", fo.values[0]); + try testing.expectEqualStrings("var2", fo.values[1]); + try testing.expectEqualStrings("var3", fo.values[2]); +} + +test "parse firstof com fallback" { + const allocator = testing.allocator; + const template = "{% firstof var1 var2 \"Nenhum valor\" %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .firstof); + const fo = nodes[0].firstof.?; + try testing.expectEqual(@as(usize, 3), fo.values.len); + try testing.expectEqualStrings("var1", fo.values[0]); + try testing.expectEqualStrings("var2", fo.values[1]); + try testing.expectEqualStrings("Nenhum valor", fo.values[2]); +} + +test "parse load simples" { + const allocator = testing.allocator; + const template = "{% load i18n humanize %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .load); + const l = nodes[0].load.?; + try testing.expectEqual(@as(usize, 2), l.libraries.len); + try testing.expectEqualStrings("i18n", l.libraries[0]); + try testing.expectEqualStrings("humanize", l.libraries[1]); +} + +test "parse load com múltiplas" { + const allocator = testing.allocator; + const template = "{% load admin_urls static %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .load); + const l = nodes[0].load.?; + try testing.expectEqual(@as(usize, 2), l.libraries.len); + try testing.expectEqualStrings("admin_urls", l.libraries[0]); + try testing.expectEqualStrings("static", l.libraries[1]); +} + +test "parse csrf_token simples" { + const allocator = testing.allocator; + const template = "
    {% csrf_token %}
    "; + const nodes = try parser.parse(allocator, template); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 3), nodes.len); - try testing.expect(nodes[0].type == .text); - try testing.expectEqualStrings("Progresso: ", nodes[0].text.?.content); + try testing.expectEqualStrings("
    ", nodes[0].text.?.content); - try testing.expect(nodes[1].type == .tag); - try testing.expect(nodes[1].tag.?.kind == .widthratio); - const wr = nodes[1].tag.?.body.widthratio; + try testing.expect(nodes[1].type == .csrf_token); + + try testing.expect(nodes[2].type == .text); + try testing.expectEqualStrings("
    ", nodes[2].text.?.content); +} + +test "parse csrf_token sozinho" { + const allocator = testing.allocator; + const template = "{% csrf_token %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .csrf_token); +} + +test "parse lorem padrão" { + const allocator = testing.allocator; + const template = "{% lorem %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .lorem); + const l = nodes[0].lorem.?; + try testing.expect(l.count == null); + try testing.expect(l.method == null); + try testing.expect(l.format == null); +} + +test "parse lorem com argumentos" { + const allocator = testing.allocator; + const template = "{% lorem 5 p html %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .lorem); + const l = nodes[0].lorem.?; + try testing.expectEqualStrings("5", l.count.?); + try testing.expectEqualStrings("p", l.method.?); + try testing.expectEqualStrings("html", l.format.?); +} + +test "parse debug simples" { + const allocator = testing.allocator; + const template = "Antes {% debug %} Depois"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 3), nodes.len); + try testing.expect(nodes[0].type == .text); + try testing.expectEqualStrings("Antes ", nodes[0].text.?.content); + + try testing.expect(nodes[1].type == .debug); + + try testing.expect(nodes[2].type == .text); + try testing.expectEqualStrings(" Depois", nodes[2].text.?.content); +} + +test "parse debug sozinho" { + const allocator = testing.allocator; + const template = "{% debug %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .debug); +} + +test "parse partialdef simples" { + const allocator = testing.allocator; + const template = "{% partialdef cabecalho %}Cabeçalho{% endpartialdef %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .partialdef); + const pd = nodes[0].partialdef.?; + try testing.expectEqualStrings("cabecalho", pd.name); + try testing.expectEqual(@as(usize, 1), pd.body.len); + try testing.expectEqualStrings("Cabeçalho", pd.body[0].text.?.content); +} + +test "parse partial uso" { + const allocator = testing.allocator; + const template = "Início {% partial \"cabecalho\" %} Fim"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 3), nodes.len); + try testing.expect(nodes[1].type == .partial); + try testing.expectEqualStrings("cabecalho", nodes[1].partial.?.name); +} + +test "parse querystring simples" { + const allocator = testing.allocator; + const template = "Link: {% querystring \"page=2\" %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 2), nodes.len); + try testing.expect(nodes[1].type == .querystring); + const qs = nodes[1].querystring.?; + try testing.expectEqual(@as(usize, 1), qs.modifications.len); + try testing.expectEqualStrings("page=2", qs.modifications[0]); +} + +test "parse querystring múltiplos" { + const allocator = testing.allocator; + const template = "{% querystring \"ordenar=-nome\" \"pagina\" None %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .querystring); + const qs = nodes[0].querystring.?; + try testing.expectEqual(@as(usize, 3), qs.modifications.len); + try testing.expectEqualStrings("ordenar=-nome", qs.modifications[0]); + try testing.expectEqualStrings("pagina", qs.modifications[1]); + try testing.expectEqualStrings("None", qs.modifications[2]); +} + +test "parse regroup simples" { + const allocator = testing.allocator; + const template = "{% regroup pessoas by cidade as grupos_cidade %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .regroup); + const r = nodes[0].regroup.?; + try testing.expectEqualStrings("pessoas", r.source); + try testing.expectEqualStrings("cidade", r.by); + try testing.expectEqualStrings("grupos_cidade", r.as_var); +} + +test "parse resetcycle simples" { + const allocator = testing.allocator; + const template = "{% resetcycle %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .resetcycle); + const rc = nodes[0].resetcycle.?; + try testing.expect(rc.cycle_name == null); +} + +test "parse resetcycle com nome" { + const allocator = testing.allocator; + const template = "{% resetcycle rowclass %}"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .resetcycle); + const rc = nodes[0].resetcycle.?; + try testing.expect(rc.cycle_name != null); + try testing.expectEqualStrings("rowclass", rc.cycle_name.?); +} + +test "parse widthratio simples" { + const allocator = testing.allocator; + const template = "Progresso: {% widthratio progresso 0 100 %}%"; + const nodes = try parser.parse(allocator, template); + defer { + for (nodes) |node| node.deinit(allocator); + allocator.free(nodes); + } + + try testing.expectEqual(@as(usize, 3), nodes.len); + try testing.expect(nodes[1].type == .widthratio); + const wr = nodes[1].widthratio.?; try testing.expectEqualStrings("progresso", wr.value); try testing.expectEqualStrings("0", wr.max_value); try testing.expectEqualStrings("100", wr.divisor.?); - - try testing.expect(nodes[2].type == .text); - try testing.expectEqualStrings("%", nodes[2].text.?.content); } -test "parse simple widthratio without divisor" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("50 - parse simple widthratio without divisor\n", .{}); - +test "parse widthratio sem divisor" { const allocator = testing.allocator; - const template = "Progresso: {% widthratio progresso 0 %}%"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); + const template = "{% widthratio valor 100 %}"; + const nodes = try parser.parse(allocator, template); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } - try testing.expectEqual(@as(usize, 3), nodes.len); - - try testing.expect(nodes[0].type == .text); - try testing.expectEqualStrings("Progresso: ", nodes[0].text.?.content); - - try testing.expect(nodes[1].type == .tag); - try testing.expect(nodes[1].tag.?.kind == .widthratio); - const wr = nodes[1].tag.?.body.widthratio; - try testing.expectEqualStrings("progresso", wr.value); - try testing.expectEqualStrings("0", wr.max_value); + try testing.expectEqual(@as(usize, 1), nodes.len); + try testing.expect(nodes[0].type == .widthratio); + const wr = nodes[0].widthratio.?; + try testing.expectEqualStrings("valor", wr.value); + try testing.expectEqualStrings("100", wr.max_value); try testing.expect(wr.divisor == null); - - try testing.expect(nodes[2].type == .text); - try testing.expectEqualStrings("%", nodes[2].text.?.content); } -test "parse simple verbatim" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("51 - parse simple verbatim\n", .{}); - +test "parse templatetag openblock" { const allocator = testing.allocator; - const template = "Texto {% verbatim %}{{ variável }}{% endblock %}{% endverbatim %} Texto"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - - try testing.expectEqual(@as(usize, 3), nodes.len); - - try testing.expect(nodes[0].type == .text); - try testing.expectEqualStrings("Texto ", nodes[0].text.?.content); - - try testing.expect(nodes[1].type == .tag); - try testing.expect(nodes[1].tag.?.kind == .verbatim); - const vb = nodes[1].tag.?.body.verbatim; - try testing.expectEqualStrings("{{ variável }}{% endblock %}", vb.content); - try testing.expect(std.mem.eql(u8, vb.name.?, "")); - - try testing.expect(nodes[2].type == .text); - try testing.expectEqualStrings(" Texto", nodes[2].text.?.content); -} - -test "parse verbatim nested" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("52 - parse verbatim nested\n", .{}); - - const allocator = testing.allocator; - const template = "{% verbatim baz %}Outer {% verbatim %}Inner{% endverbatim %} Outer{% endverbatim baz %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); + const template = "{% templatetag openblock %}"; + const nodes = try parser.parse(allocator, template); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .verbatim); - const vb = nodes[0].tag.?.body.verbatim; - try testing.expectEqualStrings("Outer {% verbatim %}Inner{% endverbatim %} Outer", vb.content); - try testing.expect(std.mem.eql(u8, vb.name.?, "baz")); + try testing.expect(nodes[0].type == .templatetag); + try testing.expect(nodes[0].templatetag.?.kind == .openblock); } -test "parse simple with block" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("53 - parse with block\n", .{}); - +test "parse templatetag closevariable" { const allocator = testing.allocator; - const template = "{% with nome=\"Lucas\" idade=30 %}Olá {{ nome }}, você tem {{ idade }} anos.{% endwith %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); + const template = "{% templatetag closevariable %}"; + const nodes = try parser.parse(allocator, template); defer { for (nodes) |node| node.deinit(allocator); allocator.free(nodes); } try testing.expectEqual(@as(usize, 1), nodes.len); - try testing.expect(nodes[0].type == .tag); - try testing.expect(nodes[0].tag.?.kind == .with_block); - const w = nodes[0].tag.?.body.with; - try testing.expectEqual(@as(usize, 2), w.assignments.len); - try testing.expectEqual(@as(usize, 5), w.body.len); - try testing.expect(w.body[0].type == .text); + try testing.expect(nodes[0].type == .templatetag); + try testing.expect(nodes[0].templatetag.?.kind == .closevariable); } - - -test "parse svg" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("54 - parse svg\n", .{}); - - const allocator = testing.allocator; - const template = "{% svg \"material\" \"account_arrow_left\" %}"; - var p = parser.Parser.init(template); - const nodes = try p.parse(allocator); - defer { - for (nodes) |node| node.deinit(allocator); - allocator.free(nodes); - } - -} - -// test "parse simple tag" { -// std.debug.print("____________________________________________________\n", .{}); -// std.debug.print("54 - parse simple tag\n", .{}); -// -// const allocator = testing.allocator; -// const template = "Antes {% minha_tag %} Depois"; -// var p = parser.Parser.init(template); -// const nodes = try p.parse(allocator); -// defer { -// for (nodes) |node| { -// node.deinit(allocator); -// } -// allocator.free(nodes); -// } -// -// try testing.expectEqual(@as(usize, 3), nodes.len); -// -// try testing.expect(nodes[0].type == .text); -// try testing.expectEqualStrings("Antes ", nodes[0].text.?.content); -// -// try testing.expect(nodes[1].type == .tag); -// try testing.expectEqualStrings("minha_tag", nodes[1].tag.?.name); -// try testing.expectEqualStrings("", nodes[1].tag.?.args); -// -// try testing.expect(nodes[2].type == .text); -// try testing.expectEqualStrings(" Depois", nodes[2].text.?.content); -// } diff --git a/src/renderer.zig b/src/renderer.zig index c899961..415de94 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -10,19 +10,37 @@ const builtin_filters = @import("filters.zig").builtin_filters; const FilterError = @import("filters.zig").FilterError; const parser = @import("parser.zig"); const TemplateCache = @import("cache.zig").TemplateCache; -const time = @import("time.zig"); -const lorem = @import("lorem.zig"); -const icons = @import("svg/icons.zig"); -pub const RenderError = error{ - InvalidCharacter, - InvalidSyntax, +pub const RenderError = FilterError || error{ OutOfMemory, - Overflow, + InvalidSyntax, + UnknownVariable, + UnknownFilter, + InvalidTemplate, + BlockNotFound, + CircularExtends, + FileNotFound, + AccessDenied, + FileTooBig, + NoSpaceLeft, Unexpected, - UnsupportedExpression, - // } || FilterError || parser.ParserError || icons.SvgError || std.fs.File.OpenError; -} || FilterError || parser.ParserError || std.fs.File.OpenError; + UnclosedTag, + InvalidAutoescapeArgument, + UnclosedVariable, + UnclosedBlock, + UnclosedComment, + InvalidAssignmentSyntax, + UnclosedQuoteInAssignment, + InvalidForSyntax, + UnclosedVerbatim, + InvalidUrlSyntax, + UnclosedQuoteInUrl, + InvalidDebugArgs, + InvalidRegroupSyntax, + InvalidWidthRatioSyntax, + InvalidTemplateTag, + InvalidCsrfTokenArgs, +}; pub const Renderer = struct { context: *Context, @@ -40,23 +58,19 @@ pub const Renderer = struct { fn checkForExtends(self: *const Renderer, nodes: []parser.Node) ?parser.Node { _ = self; for (nodes) |n| { - // if (n.type == .extends) return n; - if (n.type == .tag and n.tag.?.kind == .extends) return n; + if (n.type == .extends) return n; } return null; } - fn readTemplateFile(self: *const Renderer, path: []const u8) ![]const u8 { + fn readTemplateFile(self: *const Renderer, path: []const u8) RenderError![]const u8 { const max_size = 10 * 1024 * 1024; - - const full_path = try std.fs.path.join(self.allocator, &.{ self.cache.default_path.?, path }); - defer self.allocator.free(full_path); - - const file = try std.fs.cwd().openFile(full_path, .{}); - defer file.close(); - - // return std.fs.cwd().readFileAlloc(self.allocator, path, max_size) catch |err| switch (err) { - return file.readToEndAlloc(self.allocator, max_size) catch |err| switch (err) { + return std.fs.cwd().readFileAlloc(self.allocator, path, max_size) catch |err| switch (err) { + error.FileNotFound => return RenderError.FileNotFound, + error.AccessDenied => return RenderError.AccessDenied, + error.FileTooBig => return RenderError.FileTooBig, + error.NoSpaceLeft => return RenderError.NoSpaceLeft, + error.OutOfMemory => return RenderError.OutOfMemory, else => return RenderError.Unexpected, }; } @@ -65,8 +79,7 @@ pub const Renderer = struct { const extends_node = self.checkForExtends(nodes); if (extends_node) |ext| { - // const base_template = try self.readTemplateFile(ext.extends.?.parent_name); - const base_template = try self.readTemplateFile(ext.tag.?.body.extends.parent_name); + const base_template = try self.readTemplateFile(ext.extends.?.parent_name); defer self.allocator.free(base_template); var base_parser = parser.Parser.init(base_template); @@ -84,17 +97,16 @@ pub const Renderer = struct { } fn renderTemplate(self: *const Renderer, template: []const u8, writer: anytype, cache_key: ?[]const u8) RenderError!void { - _ = cache_key; var arena = std.heap.ArenaAllocator.init(self.allocator); defer arena.deinit(); const alloc = arena.allocator(); - // if (cache_key) |ck| { - // if (self.cache.get(ck)) |cached_nodes| { - // try self.renderNodes(alloc, cached_nodes, writer); - // return; - // } - // } + if (cache_key) |ck| { + if (self.cache.get(ck)) |cached_nodes| { + try self.renderNodes(alloc, cached_nodes, writer); + return; + } + } var p = parser.Parser.init(template); const nodes = try p.parse(alloc); @@ -103,15 +115,16 @@ pub const Renderer = struct { alloc.free(nodes); } - // if (cache_key) |ck| { - // var alc = self.cache.allocator(); - // var cached_nodes = try alc.alloc(parser.Node, nodes.len); - // errdefer alc.free(cached_nodes); - // for (nodes, 0..) |node, i| { - // cached_nodes[i] = try node.clone(self.allocator); - // } - // try self.cache.add(ck, nodes); - // } + if (cache_key) |ck| { + var alc = self.cache.allocator(); + var cached_nodes = try alc.alloc(parser.Node, nodes.len); + errdefer alc.free(cached_nodes); + for (nodes, 0..) |node, i| { + cached_nodes[i] = try node.clone(self.allocator); + std.debug.print("clonou {any}\n", .{cached_nodes[i]}); + } + try self.cache.add(ck, nodes); + } return try self.renderNodes(alloc, nodes, writer); } @@ -129,22 +142,24 @@ pub const Renderer = struct { fn renderWithInheritance(self: *const Renderer, alloc: Allocator, base_nodes: []parser.Node, child_nodes: []parser.Node, writer: anytype) RenderError!void { for (base_nodes) |base_node| { - if (base_node.type == .tag and base_node.tag.?.kind == .block) { - const block_name = base_node.tag.?.body.block.name; + if (base_node.type == .block) { + const block_name = base_node.block.?.name; + // Procura no filho const child_block = self.findChildBlock(child_nodes, block_name); if (child_block) |child| { // Renderiza o filho, passando o conteúdo do pai para block.super for (child.body) |child_node| { - try self.renderNode(alloc, child_nodes, child_node, writer, null, base_node.tag.?.body.block.body); + try self.renderNode(alloc, child_nodes, child_node, writer, null, base_node.block.?.body); } } else { // Renderiza o do pai - for (base_node.tag.?.body.block.body) |child| { + for (base_node.block.?.body) |child| { try self.renderNode(alloc, child_nodes, child, writer, null, null); } } } else { + // Qualquer outra coisa no base try self.renderNode(alloc, child_nodes, base_node, writer, null, null); } } @@ -167,14 +182,7 @@ pub const Renderer = struct { for (node.variable.?.filters) |f| { const filter_fn = builtin_filters.get(f.name) orelse return error.UnknownFilter; - // const arg = if (f.arg) |a| Value{ .string = a } else null; - var arg: Value = Value.null; - if (f.arg) |a| { - arg = Value{ .string = a }; - const result = try std.fmt.parseInt(i64, a, 10); - if (std.math.maxInt(i64) < result) return error.Overflow; - arg = Value{ .int = result }; - } + const arg = if (f.arg) |a| Value{ .string = a } else null; value = try filter_fn(alloc, value, arg); if (std.mem.eql(u8, f.name, "safe")) is_safe = true; @@ -192,292 +200,90 @@ pub const Renderer = struct { try self.valueToString(alloc, &buf, value); try writer.writeAll(buf.items); }, - .tag => { - switch (node.tag.?.kind) { - .if_block => { - const condition = try self.evaluateCondition(alloc, node.tag.?.body.@"if".condition, context); + .if_block => { + const condition = self.evaluateCondition(node.@"if".?.condition) catch return false; - if (condition) { - for (node.tag.?.body.@"if".true_body) |child| { - try self.renderNode(alloc, nodes, child, writer, context, null); - } - } else { - for (node.tag.?.body.@"if".false_body) |child| { - try self.renderNode(alloc, nodes, child, writer, context, null); - } - } - }, - .include => { - const included_template = try self.readTemplateFile(node.tag.?.body.include.template_path); - defer alloc.free(included_template); - - var included_parser = parser.Parser.init(included_template); - const included_nodes = try included_parser.parse(alloc); - defer { - for (included_nodes) |n| n.deinit(alloc); - alloc.free(included_nodes); - } - - // Renderiza o include no contexto atual (sem novo contexto) - for (included_nodes) |included_node| { - try self.renderNode(alloc, nodes, included_node, writer, context, null); - } - }, - .for_block => { - const list_value = self.context.get(node.tag.?.body.@"for".iterable) orelse Value.null; - const list = switch (list_value) { - .list => |l| l, - else => return, - }; - - for (list, 0..) |item, i| { - var ctx = Context.init(alloc); - defer ctx.deinit(); - - try ctx.set(node.tag.?.body.@"for".loop_var, item); - try ctx.set("forloop.counter", i + 1); - try ctx.set("forloop.counter0", i); - try ctx.set("forloop.revcounter", (list.len - i)); - try ctx.set("forloop.revcounter0", (list.len - i) - 1); - try ctx.set("forloop.first", i == 0); - try ctx.set("forloop.last", i == (list.len - 1)); - try ctx.set("forloop.length", list.len); - // forloop.counter - // The current iteration of the loop (1-indexed) - // forloop.counter0 - // The current iteration of the loop (0-indexed) - // forloop.revcounter - // The number of iterations from the end of the loop (1-indexed) - // forloop.revcounter0 - // The number of iterations from the end of the loop (0-indexed) - // forloop.first - // True if this is the first time through the loop - // forloop.last - // True if this is the last time through the loop - // forloop.length - - for (node.tag.?.body.@"for".body) |child| { - try self.renderNode(alloc, nodes, child, writer, &ctx, null); - } - - if (node.tag.?.body.@"for".body.len == 0) { - for (node.tag.?.body.@"for".empty_body) |child| { - try self.renderNode(alloc, nodes, child, writer, &ctx, null); - } - } - } - }, - .super => { - if (parent_block_nodes) |parent| { - for (parent) |child| { - try self.renderNode(alloc, nodes, child, writer, null, null); - } - } - }, - .block => { - for (node.tag.?.body.block.body) |child| { - const parent_content = parent_block_nodes orelse node.tag.?.body.block.body; - try self.renderNode(alloc, nodes, child, writer, null, parent_content); - } - }, - .widthratio => { - var divisor: Value = Value{ .float = 1.0 }; - var float_divisor: f64 = 1.0; - - var value: Value = Value{ .float = 1.0 }; - var float_value: f64 = 1.0; - - var max_value: Value = Value{ .float = 1.0 }; - var float_max_value: f64 = 1.0; - - if (!std.mem.eql(u8, node.tag.?.body.widthratio.value, "")) { - value = Value{ .string = node.tag.?.body.widthratio.value }; - if (self.context.get(node.tag.?.body.widthratio.value)) |v| { - value = v; - } - float_value = switch (value) { - .int => @as(f64, @floatFromInt(value.int)), - .float => value.float, - .string => std.fmt.parseFloat(f64, value.string) catch 1.0, - else => 1.0, - }; - } - - if (!std.mem.eql(u8, node.tag.?.body.widthratio.max_value, "")) { - max_value = Value{ .string = node.tag.?.body.widthratio.max_value }; - if (self.context.get(node.tag.?.body.widthratio.max_value)) |v| { - max_value = v; - } - float_max_value = switch (max_value) { - .int => @as(f64, @floatFromInt(max_value.int)), - .float => max_value.float, - .string => std.fmt.parseFloat(f64, max_value.string) catch 1.0, - else => 1.0, - }; - } - - if (node.tag.?.body.widthratio.divisor) |div| { - divisor = Value{ .string = div }; - if (self.context.get(div)) |d| { - divisor = d; - } - float_divisor = switch (divisor) { - .int => @as(f64, @floatFromInt(divisor.int)), - .float => divisor.float, - .string => std.fmt.parseFloat(f64, divisor.string) catch 0.0, - else => 1.0, - }; - } - - const ratio = (float_value / float_max_value) * float_divisor; - - try writer.writeAll(std.fmt.allocPrint(alloc, "{d}", .{ratio}) catch "0"); - }, - .now => { - var format: []const u8 = node.tag.?.body.now.format; - if (format.len == 0) format = "Y-m-d H:i:s"; - const datetime = try time.Time.now().toStringAlloc(alloc, format); - try writer.writeAll(datetime); - }, - .csrf_token => { - const token = self.context.get("csrf_token"); - if (token == null) return; - try writer.writeAll(std.fmt.allocPrint(alloc, "", .{token.?.string}) catch ""); - }, - .firstof => { - const values = node.tag.?.body.firstof.values; - for (values) |value| { - if (self.context.get(value)) |v| { - if (!isTruthy(v)) continue; - - var buf = ArrayListUnmanaged(u8){}; - defer buf.deinit(alloc); - - try self.valueToString(alloc, &buf, v); - - try writer.writeAll(buf.items); - return; - } else { - const check_value = self.resolveStringVariable(value).?; - if (!isTruthy(check_value)) continue; - - var buf = ArrayListUnmanaged(u8){}; - defer buf.deinit(alloc); - - try self.valueToString(alloc, &buf, Value{ .string = value }); - try writer.writeAll(buf.items); - return; - } - } - try writer.writeAll(node.tag.?.body.firstof.fallback); - }, - .lorem => { - const count = node.tag.?.body.lorem.count; - const method = node.tag.?.body.lorem.method; - const random = node.tag.?.body.lorem.random; - - if (count == null and method == null) { - if (random == false) { - try writer.writeAll(lorem.LOREM_COMMON_P); - return; - } else { - try writer.writeAll(try lorem.sentence(alloc)); - return; - } - } - - const ncount: u32 = std.fmt.parseInt(u32, count.?, 10) catch 1; - if (std.mem.eql(u8, method.?, "p")) { - const lorem_ = try lorem.paragraphs(alloc, ncount, random); - try writer.writeAll(lorem_); - return; - } else { - const lorem_ = try lorem.words(alloc, ncount, random); - try writer.writeAll(lorem_); - return; - } - }, - .svg => { - const svg_kind = node.tag.?.body.svg.kind; - const svg_name = node.tag.?.body.svg.name; - - if (self.cache.icons.?.getIcon(alloc, svg_kind, svg_name)) |svg_content| { - try writer.writeAll("
    "); - try writer.writeAll(svg_content); - try writer.writeAll("
    "); - } else { - try writer.writeAll(icons.fallback_svg); - // Opcional: log ou comentário de debug - // try writer.print("", .{svg_kind, svg_name}); - } - return; - }, - else => { - std.debug.print("PANIC: unknown node type {d}\n", .{@intFromEnum(node.type)}); - // @panic("unknown node type"); - try writer.writeAll(""); - }, + if (condition) { + for (node.@"if".?.true_body) |child| { + std.debug.print("caí no true\n", .{}); + try self.renderNode(alloc, nodes, child, writer, null, null); + } + } else { + for (node.@"if".?.false_body) |child| { + std.debug.print("caí no false\n", .{}); + try self.renderNode(alloc, nodes, child, writer, null, null); + } } }, + .include => { + const included_template = try self.readTemplateFile(node.include.?.template_name); + defer alloc.free(included_template); + + var included_parser = parser.Parser.init(included_template); + const included_nodes = try included_parser.parse(alloc); + defer { + for (included_nodes) |n| n.deinit(alloc); + alloc.free(included_nodes); + } + + // Renderiza o include no contexto atual (sem novo contexto) + for (included_nodes) |included_node| { + try self.renderNode(alloc, nodes, included_node, writer, context, null); + } + }, + .for_block => { + const list_value = self.context.get(node.@"for".?.iterable) orelse Value.null; + const list = switch (list_value) { + .list => |l| l, + else => return, + }; + + for (list) |item| { + var ctx = Context.init(alloc); + defer ctx.deinit(); + + try ctx.set(node.@"for".?.loop_var, item); + + for (node.@"for".?.body) |child| { + try self.renderNode(alloc, nodes, child, writer, &ctx, null); + } + + for (node.@"for".?.empty_body) |child| { + try self.renderNode(alloc, nodes, child, writer, &ctx, null); + } + } + }, + .super => { + if (parent_block_nodes) |parent| { + for (parent) |child| { + try self.renderNode(alloc, nodes, child, writer, null, null); + } + } + }, + .block => { + for (node.block.?.body) |child| { + const parent_content = parent_block_nodes orelse node.block.?.body; + try self.renderNode(alloc, nodes, child, writer, null, parent_content); + } + }, + else => {}, } } - fn resolveStringVariable(self: *const Renderer, value: []const u8) ?Value { - _ = self; - if (std.mem.eql(u8, value, "true")) return Value{ .bool = true }; - if (std.mem.eql(u8, value, "false")) return Value{ .bool = false }; - const is_int = std.fmt.parseInt(i64, value, 10) catch |err| switch (err) { - error.InvalidCharacter => null, - error.Overflow => null, - }; - if (is_int != null) return Value{ .int = is_int.? }; - - const is_float = std.fmt.parseFloat(f64, value) catch |err| switch (err) { - error.InvalidCharacter => null, - }; - if (is_float != null) return Value{ .float = is_float.? }; - return Value{ .string = value }; - } - fn findChildBlock(self: *const Renderer, nodes: []parser.Node, name: []const u8) ?parser.BlockNode { _ = self; for (nodes) |n| { - if (n.type != .tag) continue; - if (n.tag.?.kind != .block) continue; - if (std.mem.eql(u8, n.tag.?.body.block.name, name)) return n.tag.?.body.block; + if (n.type != .block) continue; + if (std.mem.eql(u8, n.block.?.name, name)) return n.block.?; } return null; } - fn escapeHtml(self: *const Renderer, value: Value) !Value { - const s = switch (value) { - .string => |str| str, - else => return value, - }; - - var result = std.ArrayList(u8){}; - - for (s) |c| { - switch (c) { - '&' => try result.appendSlice(self.allocator, "&"), - '<' => try result.appendSlice(self.allocator, "<"), - '>' => try result.appendSlice(self.allocator, ">"), - '"' => try result.appendSlice(self.allocator, """), - '\'' => try result.appendSlice(self.allocator, "'"), - else => try result.append(self.allocator, c), - } - } - - return Value{ .string = try result.toOwnedSlice(self.allocator) }; - } - - fn valueToString(self: *const Renderer, alloc: Allocator, buf: *ArrayListUnmanaged(u8), value: Value) RenderError!void { + fn valueToString(self: *const Renderer, alloc: Allocator, buf: *ArrayListUnmanaged(u8), value: Value) !void { _ = self; var w = buf.writer(alloc); switch (value) { - .null => try w.writeAll(""), + .null => try w.writeAll("null"), .bool => |b| try w.print("{}", .{b}), .int => |n| try w.print("{d}", .{n}), .float => |f| try w.print("{d}", .{f}), @@ -487,7 +293,7 @@ pub const Renderer = struct { } } - fn evaluateCondition_bkp(self: *const Renderer, expr: []const u8) RenderError!bool { + fn evaluateCondition(self: *const Renderer, expr: []const u8) !bool { const value = self.context.get(expr) orelse Value.null; return switch (value) { .bool => |b| b, @@ -499,123 +305,4 @@ pub const Renderer = struct { .null => false, }; } - - fn evaluateCondition(self: *const Renderer, allocator: Allocator, expr: []const u8, context: ?*Context) RenderError!bool { - const trimmed = std.mem.trim(u8, expr, " \t\r\n"); - if (trimmed.len == 0) return false; - - var parts = std.mem.splitScalar(u8, trimmed, ' '); - - // Coleta tokens não vazios - var tokens = std.ArrayList([]const u8){}; - defer tokens.deinit(allocator); - - while (parts.next()) |part| { - const t = std.mem.trim(u8, part, " \t\r\n"); - if (t.len > 0) try tokens.append(allocator, t); - } - - // Caso simples: só nome de variável - if (tokens.items.len == 1) { - const value = self.context.get(tokens.items[0]) orelse Value.null; - return isTruthy(value); - } - - // Caso especial: "not variavel" - if (tokens.items.len == 2 and std.mem.eql(u8, tokens.items[0], "not")) { - const value = self.context.get(tokens.items[1]) orelse Value.null; - return !isTruthy(value); - } - - // Caso com operadores de comparação: var op valor - if (tokens.items.len == 3) { - const left = tokens.items[0]; - const op = tokens.items[1]; - const right_str = tokens.items[2]; - - var left_value: Value = Value.null; - if (context) |ctx| { - left_value = ctx.get(left) orelse Value.null; - } - if (left_value == Value.null) left_value = self.context.get(left) orelse Value.null; - - const right_value = parseLiteral(right_str); - - if (std.mem.eql(u8, op, ">")) return compare(left_value, right_value, .gt); - if (std.mem.eql(u8, op, "<")) return compare(left_value, right_value, .lt); - if (std.mem.eql(u8, op, ">=")) return compare(left_value, right_value, .ge); - if (std.mem.eql(u8, op, "<=")) return compare(left_value, right_value, .le); - if (std.mem.eql(u8, op, "==")) return compare(left_value, right_value, .eq); - if (std.mem.eql(u8, op, "!=")) return compare(left_value, right_value, .ne); - - return error.InvalidSyntax; - } - - // Caso mais complexo (and/or/not composto) - por enquanto erro - return error.UnsupportedExpression; - } - - // Função auxiliar: converte string literal pra Value - fn parseLiteral(str: []const u8) Value { - const trimmed = std.mem.trim(u8, str, " \t\r\n\"'"); - if (std.mem.eql(u8, trimmed, "true")) return Value{ .bool = true }; - if (std.mem.eql(u8, trimmed, "false")) return Value{ .bool = false }; - if (std.mem.eql(u8, trimmed, "null")) return Value.null; - - if (std.fmt.parseInt(i64, trimmed, 10)) |n| return Value{ .int = n } else |_| {} - if (std.fmt.parseFloat(f64, trimmed)) |f| return Value{ .float = f } else |_| {} - - return Value{ .string = trimmed }; - } - - // Função auxiliar: truthy check - fn isTruthy(v: Value) bool { - return switch (v) { - .null => false, - .bool => |b| b, - .int => |i| i != 0, - .float => |f| f != 0.0, - .string => |s| s.len > 0, - .list => |l| l.len > 0, - .dict => |d| d.count() > 0, - }; - } - - // Função auxiliar: comparação - fn compare(left: Value, right: Value, op: enum { gt, lt, ge, le, eq, ne, not }) bool { - // Implementação básica (expanda conforme necessário) - switch (left) { - .int => |l| switch (right) { - .int => |r| return switch (op) { - .gt => l > r, - .lt => l < r, - .ge => l >= r, - .le => l <= r, - .eq => l == r, - .ne, .not => l != r, - }, - else => return false, - }, - .float => |l| switch (right) { - .float => |r| return switch (op) { - .gt => l > r, - .lt => l < r, - .ge => l >= r, - .le => l <= r, - .eq => l == r, - .ne, .not => l != r, - }, - else => return false, - }, - .string => |l| switch (right) { - .string => |r| return switch (op) { - .eq => std.mem.eql(u8, l, r), - .ne, .not => !std.mem.eql(u8, l, r), - else => false, - }, - else => return false, - }, - else => return false, - } - } }; diff --git a/src/renderer_bkp.zig b/src/renderer_bkp.zig new file mode 100644 index 0000000..6e2f0eb --- /dev/null +++ b/src/renderer_bkp.zig @@ -0,0 +1,275 @@ +const std = @import("std"); + +const Allocator = std.mem.Allocator; +const ArrayListUnmanaged = std.ArrayListUnmanaged; + +const Context = @import("context.zig").Context; +const Value = @import("context.zig").Value; + +const builtin_filters = @import("filters.zig").builtin_filters; +const FilterError = @import("filters.zig").FilterError; +const parser = @import("parser.zig"); + +pub const RenderError = FilterError || error{ + OutOfMemory, + InvalidSyntax, + UnknownVariable, + UnknownFilter, + InvalidTemplate, + BlockNotFound, + CircularExtends, + FileNotFound, + AccessDenied, + FileTooBig, + NoSpaceLeft, + Unexpected, + UnclosedTag, + InvalidAutoescapeArgument, + UnclosedVariable, + UnclosedBlock, + UnclosedComment, + InvalidAssignmentSyntax, + UnclosedQuoteInAssignment, + InvalidForSyntax, + UnclosedVerbatim, + InvalidUrlSyntax, + UnclosedQuoteInUrl, + InvalidDebugArgs, + InvalidRegroupSyntax, + InvalidWidthRatioSyntax, + InvalidTemplateTag, + InvalidCsrfTokenArgs, +}; + +pub const Renderer = struct { + context: *Context, + allocator: Allocator, + + pub fn init(context: *Context) Renderer { + return .{ + .context = context, + .allocator = context.allocator(), + }; + } + + fn checkForExtends(self: *const Renderer, nodes: []parser.Node) bool { + _ = self; + for (nodes) |n| { + if (n.type == .extends) return true; + } + return false; + } + + fn readTemplateFile(self: *const Renderer, path: []const u8) RenderError![]const u8 { + const max_size = 10 * 1024 * 1024; + const base_template = std.fs.cwd().readFileAlloc(self.allocator, path, max_size) catch |err| switch (err) { + error.FileNotFound => return RenderError.FileNotFound, + error.AccessDenied => return RenderError.AccessDenied, + error.FileTooBig => return RenderError.FileTooBig, + error.NoSpaceLeft => return RenderError.NoSpaceLeft, + error.OutOfMemory => return RenderError.OutOfMemory, + else => return RenderError.Unexpected, + }; + return base_template; + } + + pub fn render(self: *const Renderer, template: []const u8, writer: anytype) RenderError!void { + const base_template = try self.readTemplateFile(template); + return self.renderString(base_template, writer); + } + + pub fn renderString_bkp(self: *const Renderer, template: []const u8, writer: anytype) RenderError!void { + var arena = std.heap.ArenaAllocator.init(self.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + var p = parser.Parser.init(template); + const nodes = try p.parse(alloc); + defer { + for (nodes) |node| node.deinit(alloc); + alloc.free(nodes); + } + + for (nodes) |node| { + try self.renderNode(alloc, nodes, node, writer, null); + } + } + + pub fn renderString(self: *const Renderer, template: []const u8, writer: anytype) RenderError!void { + var arena = std.heap.ArenaAllocator.init(self.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + var p = parser.Parser.init(template); + const nodes = try p.parse(alloc); + defer { + for (nodes) |node| node.deinit(alloc); + alloc.free(nodes); + } + + const has_extends = self.checkForExtends(nodes); + + if (has_extends) { + for (nodes) |node| { + if (node.type == .extends) { + const base_template = try self.readTemplateFile(node.extends.?.parent_name); + defer self.allocator.free(base_template); + + var base_parser = parser.Parser.init(base_template); + const base_nodes = try base_parser.parse(alloc); + defer { + for (base_nodes) |n| n.deinit(alloc); + alloc.free(base_nodes); + } + + try self.renderWithInheritance(alloc, base_nodes, nodes, writer); + return; + } + } + } else { + for (nodes) |node| { + try self.renderNode(alloc, nodes, node, writer, null); + } + } + } + + fn renderWithInheritance(self: *const Renderer, alloc: Allocator, base_nodes: []parser.Node, child_nodes: []parser.Node, writer: anytype) RenderError!void { + for (base_nodes) |base_node| { + if (base_node.type == .block) { + const block_name = base_node.block.?.name; + + // Procura no filho + const child_block = self.findChildBlock(child_nodes, block_name); + if (child_block) |child| { + // Renderiza só o filho + for (child.body) |child_node| { + try self.renderNode(alloc, child_nodes, child_node, writer, null); + } + } else { + // Renderiza o do pai + for (base_node.block.?.body) |child| { + try self.renderNode(alloc, child_nodes, child, writer, null); + } + } + } else { + // Qualquer outra coisa no base + try self.renderNode(alloc, child_nodes, base_node, writer, null); + } + } + } + + fn renderNode(self: *const Renderer, alloc: Allocator, nodes: []parser.Node, node: parser.Node, writer: anytype, context: ?*Context) RenderError!void { + // std.debug.print("Vou renderizar o node: {any}\n", .{node}); + switch (node.type) { + .text => try writer.writeAll(node.text.?.content), + .variable => { + const var_name = node.variable.?.expr; + var value: Value = Value.null; + if (context != null) { + value = context.?.get(var_name) orelse Value.null; + } else { + value = self.context.get(var_name) orelse Value.null; + } + + var is_safe = false; + + for (node.variable.?.filters) |f| { + const filter_fn = builtin_filters.get(f.name) orelse return error.UnknownFilter; + + const arg = if (f.arg) |a| Value{ .string = a } else null; + value = try filter_fn(alloc, value, arg); + + if (std.mem.eql(u8, f.name, "safe")) is_safe = true; + } + + if (!is_safe) { + if (builtin_filters.get("escape")) |escape_fn| { + value = try escape_fn(alloc, value, null); + } + } + + var buf = ArrayListUnmanaged(u8){}; + defer buf.deinit(alloc); + + try self.valueToString(alloc, &buf, value); + try writer.writeAll(buf.items); + }, + .if_block => { + const condition = self.evaluateCondition(node.@"if".?.condition) catch return false; + + if (condition) { + for (node.@"if".?.true_body) |child| { + try self.renderNode(alloc, nodes, child, writer, null); + } + } else { + for (node.@"if".?.false_body) |child| { + try self.renderNode(alloc, nodes, child, writer, null); + } + } + }, + .for_block => { + const list_value = self.context.get(node.@"for".?.iterable) orelse Value.null; + const list = switch (list_value) { + .list => |l| l, + else => return, + }; + + for (list) |item| { + var ctx = Context.init(alloc); + defer ctx.deinit(); + + try ctx.set(node.@"for".?.loop_var, item); + + for (node.@"for".?.body) |child| { + try self.renderNode(alloc, nodes, child, writer, &ctx); + } + for (node.@"for".?.empty_body) |child| { + try self.renderNode(alloc, nodes, child, writer, &ctx); + } + } + }, + .block => { + for (node.block.?.body) |child| { + try self.renderNode(alloc, nodes, child, writer, null); + } + }, + else => {}, + } + } + + fn findChildBlock(self: *const Renderer, nodes: []parser.Node, name: []const u8) ?parser.BlockNode { + _ = self; + for (nodes) |n| { + if (n.type != .block) continue; + if (std.mem.eql(u8, n.block.?.name, name)) return n.block.?; + } + return null; + } + + fn valueToString(self: *const Renderer, alloc: Allocator, buf: *ArrayListUnmanaged(u8), value: Value) !void { + _ = self; + var w = buf.writer(alloc); + switch (value) { + .null => try w.writeAll("null"), + .bool => |b| try w.print("{}", .{b}), + .int => |n| try w.print("{d}", .{n}), + .float => |f| try w.print("{d}", .{f}), + .string => |s| try w.writeAll(s), + .list => try w.writeAll("[list]"), + .dict => try w.writeAll("{dict}"), + } + } + + fn evaluateCondition(self: *const Renderer, expr: []const u8) !bool { + const value = self.context.get(expr) orelse Value.null; + return switch (value) { + .bool => |b| b, + .int => |i| i != 0, + .float => |f| f != 0.0, + .string => |s| s.len > 0, + .list => |l| l.len > 0, + .dict => |d| d.count() > 0, + .null => false, + }; + } +}; diff --git a/src/renderer_test.zig b/src/renderer_test.zig index c37560d..9062e05 100644 --- a/src/renderer_test.zig +++ b/src/renderer_test.zig @@ -8,489 +8,443 @@ const Context = @import("context.zig").Context; const Value = @import("context.zig").Value; const TemplateCache = @import("cache.zig").TemplateCache; -test "renderer: literal + variável simples" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("1 - renderer: literal + variável simples\n", .{}); - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - try ctx.set("nome", Value{ .string = "Fulana" }); - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - const template = - \\Olá, {{ nome }}! Bem-vinda. - ; - - try renderer.renderString(template, buf.writer(alloc)); - - // std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expectEqualStrings("Olá, Fulana! Bem-vinda.", buf.items); -} - -test "renderer: filtros + autoescape" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("2 - renderer: filtros + autoescape\n", .{}); - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - try ctx.set("html", Value{ .string = "" }); - try ctx.set("texto", Value{ .string = "maiusculo e slug" }); - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - const template = - \\Normal: {{ html }} - \\Safe: {{ html|safe }} - \\Filtrado: {{ texto|upper|slugify }} - ; - - try renderer.renderString(template, buf.writer(alloc)); - - const expected = - \\Normal: <script>alert()</script> - \\Safe: - \\Filtrado: maiusculo-e-slug - ; - - // std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expectEqualStrings(expected, buf.items); -} - -test "literal simples" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("3 - literal simples\n", .{}); - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - const template = "Texto literal com acentos: Olá, mundo!"; - - try renderer.renderString(template, buf.writer(alloc)); - - // std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expectEqualStrings("Texto literal com acentos: Olá, mundo!", buf.items); -} - -test "variável com filtro encadeado e autoescape" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("4 - variável com filtro encadeado e autoescape\n", .{}); - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - try ctx.set("texto", Value{ .string = "Exemplo de Texto" }); - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - const template = "Resultado: {{ texto|lower|upper }}"; - - try renderer.renderString(template, buf.writer(alloc)); - - // std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expectEqualStrings("Resultado: EXEMPLO DE TEXTO", buf.items); // assume lower then upper -} - -test "autoescape com safe" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("5 - autoescape com safe\n", .{}); - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - try ctx.set("html", Value{ .string = "
    conteúdo
    " }); - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - const template = "Escape: {{ html }} | Safe: {{ html|safe }}"; - - try renderer.renderString(template, buf.writer(alloc)); - - // std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expectEqualStrings("Escape: <div>conteúdo</div> | Safe:
    conteúdo
    ", buf.items); -} - -// // TODO: evaluationConditions more complex -test "renderer - if and for" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("6 - renderer - if and for\n", .{}); - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - try ctx.set("ativo", Value{ .bool = true }); - try ctx.set("nomes", Value{ .list = &[_]Value{ - Value{ .string = "Ana" }, - Value{ .string = "Bia" }, - Value{ .string = "Cris" }, - } }); - - const template = - \\{% if ativo %}Sim!{% endif %} - \\{% if not ativo %}Não{% endif %} - \\Lista: - \\{% for nome in nomes %} - \\- {{ nome }} - \\{% endfor %} - ; - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.renderString(template, buf.writer(alloc)); - - // std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expect(std.mem.indexOf(u8, buf.items, "Sim!") != null); - try testing.expect(std.mem.indexOf(u8, buf.items, "Não") == null); - try testing.expect(std.mem.indexOf(u8, buf.items, "- Ana") != null); - try testing.expect(std.mem.indexOf(u8, buf.items, "- Bia") != null); - try testing.expect(std.mem.indexOf(u8, buf.items, "- Cris") != null); -} - -test "renderer - block and extends" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("7 - renderer - block and extends\n", .{}); - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - cache.default_path = "."; - - const renderer = Renderer.init(&ctx, &cache); - - const base = - \\ - \\{% block title %}Título Padrão{% endblock %} - \\ - \\{% block content %}Conteúdo padrão{% endblock %} - \\ - \\ - ; - - const child = - \\{% extends "base.html" %} - \\{% block title %}Meu Título{% endblock %} - \\{% block content %} - \\Olá {{ nome }}! - \\{% endblock %} - ; - - const expected = - \\ - \\Meu Título - \\ - \\ - \\Olá Lucas! - \\ - \\ - \\ - ; - - // Simula arquivos (ou usa renderString com base se quiser simplificar) - try std.fs.cwd().writeFile(.{ .sub_path = "base.html", .data = base }); - try std.fs.cwd().writeFile(.{ .sub_path = "child.html", .data = child }); - defer std.fs.cwd().deleteFile("base.html") catch {}; - defer std.fs.cwd().deleteFile("child.html") catch {}; - - try ctx.set("nome", Value{ .string = "Lucas" }); - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.render("child.html", buf.writer(alloc)); - - const output = buf.items; - - // std.debug.print("OUTPUT:\n\n{s}\n", .{output}); - - try testing.expect(std.mem.indexOf(u8, output, "") != null); - try testing.expect(std.mem.indexOf(u8, output, "Meu Título") != null); - try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas!") != null); - try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") == null); - try testing.expectEqualStrings(expected, output); -} - -test "renderer - block and extends with super" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("8 - renderer - block and extends with super\n", .{}); - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - cache.default_path = "."; - - const renderer = Renderer.init(&ctx, &cache); - - const base = - \\ - \\{% block title %}Título Padrão{% endblock %} - \\ - \\{% block content %}Conteúdo padrão{% endblock %} - \\ - \\ - ; - - const child = - \\{% extends "base.html" %} - \\{% block title %}Meu Título{% endblock %} - \\{% block content %} - \\{{ block.super }} - \\Olá {{ nome }}! - \\{% endblock %} - ; - - const expected = - \\ - \\Meu Título - \\ - \\ - \\Conteúdo padrão - \\Olá Lucas! - \\ - \\ - \\ - ; - - // Simula arquivos (ou usa renderString com base se quiser simplificar) - try std.fs.cwd().writeFile(.{ .sub_path = "base.html", .data = base }); - try std.fs.cwd().writeFile(.{ .sub_path = "child.html", .data = child }); - defer std.fs.cwd().deleteFile("base.html") catch {}; - defer std.fs.cwd().deleteFile("child.html") catch {}; - - try ctx.set("nome", Value{ .string = "Lucas" }); - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.render("child.html", buf.writer(alloc)); - - const output = buf.items; - - // std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expect(std.mem.indexOf(u8, output, "") != null); - try testing.expect(std.mem.indexOf(u8, output, "Meu Título") != null); - try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas!") != null); - try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") != null); - try testing.expectEqualStrings(expected, output); -} - -test "renderer - include" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("9 - renderer - include\n", .{}); - const alloc = testing.allocator; - - const header = - \\
    - \\

    Bem-vindo

    - \\
    - ; - - const main = - \\{% include "header.html" %} - \\
    - \\

    Conteúdo principal

    - \\ Olá {{ nome }}! - \\
    - ; - - const expected = - \\
    - \\

    Bem-vindo

    - \\
    - \\
    - \\

    Conteúdo principal

    - \\ Olá Lucas! - \\
    - ; - - try std.fs.cwd().writeFile(.{ .sub_path = "header.html", .data = header }); - try std.fs.cwd().writeFile(.{ .sub_path = "main.html", .data = main }); - defer std.fs.cwd().deleteFile("header.html") catch {}; - defer std.fs.cwd().deleteFile("main.html") catch {}; - - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - cache.default_path = "."; - - const renderer = Renderer.init(&ctx, &cache); - - try ctx.set("nome", Value{ .string = "Lucas" }); - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.render("main.html", buf.writer(alloc)); - - const output = buf.items; - - // std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expect(std.mem.indexOf(u8, output, "

    Bem-vindo

    ") != null); - try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas!") != null); - try testing.expectEqualStrings(expected, output); -} - -test "renderer - comment" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("10 - renderer - comment\n", .{}); - const alloc = testing.allocator; - - const template = - \\Normal: Olá {{ nome }} - \\{% comment %} - \\ Isso é um comentário - \\ que não deve aparecer - \\ nem processar variáveis {{ nome }} - \\{% endcomment %} - \\Fim: {{ nome }} - ; - const expected = - \\Normal: Olá Lucas - \\ - \\Fim: Lucas - ; - - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - cache.default_path = "."; - - const renderer = Renderer.init(&ctx, &cache); - - try ctx.set("nome", Value{ .string = "Lucas" }); - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.renderString(template, buf.writer(alloc)); - - const output = buf.items; - - // std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas") != null); - try testing.expect(std.mem.indexOf(u8, output, "Fim: Lucas") != null); - try testing.expect(std.mem.indexOf(u8, output, "Isso é um comentário") == null); - try testing.expect(std.mem.indexOf(u8, output, "que não deve aparecer") == null); - try testing.expect(std.mem.indexOf(u8, output, "nem processar variáveis") == null); - try testing.expectEqualStrings(expected, output); -} - -test "renderer - full template with extends, super, include, comment" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("11 - renderer - full template with extends, super, include, comment\n", .{}); - const alloc = testing.allocator; - - const header = "
    Bem-vindo
    "; - const base = - \\{% include "header.html" %} - \\
    - \\ {% block content %} - \\ Conteúdo padrão - \\ {% endblock %} - \\
    - ; - - const child = - \\{% extends "base.html" %} - \\{% block content %} - \\ {{ block.super }} - \\ Conteúdo do filho - \\ {% comment %} Isso não aparece {% endcomment %} - \\{% endblock %} - \\{% comment %} bazinga {% endcomment %} - ; - - try std.fs.cwd().writeFile(.{ .sub_path = "header.html", .data = header }); - try std.fs.cwd().writeFile(.{ .sub_path = "base.html", .data = base }); - try std.fs.cwd().writeFile(.{ .sub_path = "child.html", .data = child }); - defer std.fs.cwd().deleteFile("header.html") catch {}; - defer std.fs.cwd().deleteFile("base.html") catch {}; - defer std.fs.cwd().deleteFile("child.html") catch {}; - - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - cache.default_path = "."; - - const renderer = Renderer.init(&ctx, &cache); - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.render("child.html", buf.writer(alloc)); - - const output = buf.items; - // std.debug.print("OUTPUT:\n\n{s}\n", .{output}); - - try testing.expect(std.mem.indexOf(u8, output, "
    Bem-vindo
    ") != null); - try testing.expect(std.mem.indexOf(u8, output, "
    ") != null); - try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") != null); - try testing.expect(std.mem.indexOf(u8, output, "Conteúdo do filho") != null); - try testing.expect(std.mem.indexOf(u8, output, "Isso não aparece") == null); - try testing.expect(std.mem.indexOf(u8, output, "bazinga") == null); -} +// test "renderer: literal + variável simples" { +// const alloc = testing.allocator; +// var ctx = Context.init(alloc); +// defer ctx.deinit(); +// +// var cache = TemplateCache.init(alloc); +// defer cache.deinit(); +// +// const renderer = Renderer.init(&ctx, &cache); +// +// try ctx.set("nome", Value{ .string = "Mariana" }); +// +// var buf = std.ArrayList(u8){}; +// defer buf.deinit(alloc); +// +// const template = +// \\Olá, {{ nome }}! Bem-vinda. +// ; +// +// try renderer.renderString(template, buf.writer(alloc)); +// +// try testing.expectEqualStrings("Olá, Mariana! Bem-vinda.", buf.items); +// } +// +// test "renderer: filtros + autoescape" { +// const alloc = testing.allocator; +// var ctx = Context.init(alloc); +// defer ctx.deinit(); +// +// var cache = TemplateCache.init(alloc); +// defer cache.deinit(); +// +// const renderer = Renderer.init(&ctx, &cache); +// +// try ctx.set("html", Value{ .string = "" }); +// try ctx.set("texto", Value{ .string = "maiusculo e slug" }); +// +// var buf = std.ArrayList(u8){}; +// defer buf.deinit(alloc); +// +// const template = +// \\Normal: {{ html }} +// \\Safe: {{ html|safe }} +// \\Filtrado: {{ texto|upper|slugify }} +// ; +// +// try renderer.renderString(template, buf.writer(alloc)); +// +// const expected = +// \\Normal: <script>alert()</script> +// \\Safe: +// \\Filtrado: maiusculo-e-slug +// ; +// +// try testing.expectEqualStrings(expected, buf.items); +// } +// +// test "literal simples" { +// const alloc = testing.allocator; +// var ctx = Context.init(alloc); +// defer ctx.deinit(); +// +// var cache = TemplateCache.init(alloc); +// defer cache.deinit(); +// +// const renderer = Renderer.init(&ctx, &cache); +// +// var buf = std.ArrayList(u8){}; +// defer buf.deinit(alloc); +// +// const template = "Texto literal com acentos: Olá, mundo!"; +// +// try renderer.renderString(template, buf.writer(alloc)); +// +// try testing.expectEqualStrings("Texto literal com acentos: Olá, mundo!", buf.items); +// } +// +// test "variável com filtro encadeado e autoescape" { +// const alloc = testing.allocator; +// var ctx = Context.init(alloc); +// defer ctx.deinit(); +// +// var cache = TemplateCache.init(alloc); +// defer cache.deinit(); +// +// const renderer = Renderer.init(&ctx, &cache); +// +// try ctx.set("texto", Value{ .string = "Exemplo de Texto" }); +// +// var buf = std.ArrayList(u8){}; +// defer buf.deinit(alloc); +// +// const template = "Resultado: {{ texto|lower|upper }}"; +// +// try renderer.renderString(template, buf.writer(alloc)); +// +// try testing.expectEqualStrings("Resultado: EXEMPLO DE TEXTO", buf.items); // assume lower then upper +// } +// +// test "autoescape com safe" { +// const alloc = testing.allocator; +// var ctx = Context.init(alloc); +// defer ctx.deinit(); +// +// var cache = TemplateCache.init(alloc); +// defer cache.deinit(); +// +// const renderer = Renderer.init(&ctx, &cache); +// +// try ctx.set("html", Value{ .string = "
    conteúdo
    " }); +// +// var buf = std.ArrayList(u8){}; +// defer buf.deinit(alloc); +// +// const template = "Escape: {{ html }} | Safe: {{ html|safe }}"; +// +// try renderer.renderString(template, buf.writer(alloc)); +// +// try testing.expectEqualStrings("Escape: <div>conteúdo</div> | Safe:
    conteúdo
    ", buf.items); +// } +// +// test "renderer - if and for" { +// const alloc = testing.allocator; +// var ctx = Context.init(alloc); +// defer ctx.deinit(); +// +// var cache = TemplateCache.init(alloc); +// defer cache.deinit(); +// +// const renderer = Renderer.init(&ctx, &cache); +// +// try ctx.set("ativo", Value{ .bool = true }); +// try ctx.set("nomes", Value{ .list = &[_]Value{ +// Value{ .string = "Ana" }, +// Value{ .string = "Bia" }, +// Value{ .string = "Cris" }, +// } }); +// +// const template = +// \\{% if ativo %}Sim!{% endif %} +// \\{% if not ativo %}Não{% endif %} +// \\Lista: +// \\{% for nome in nomes %} +// \\- {{ nome }} +// \\{% endfor %} +// ; +// +// var buf = std.ArrayList(u8){}; +// defer buf.deinit(alloc); +// +// try renderer.renderString(template, buf.writer(alloc)); +// +// try testing.expect(std.mem.indexOf(u8, buf.items, "Sim!") != null); +// try testing.expect(std.mem.indexOf(u8, buf.items, "Não") == null); +// try testing.expect(std.mem.indexOf(u8, buf.items, "- Ana") != null); +// try testing.expect(std.mem.indexOf(u8, buf.items, "- Bia") != null); +// try testing.expect(std.mem.indexOf(u8, buf.items, "- Cris") != null); +// } +// +// test "renderer - block and extends" { +// const alloc = testing.allocator; +// var ctx = Context.init(alloc); +// defer ctx.deinit(); +// +// var cache = TemplateCache.init(alloc); +// defer cache.deinit(); +// +// const renderer = Renderer.init(&ctx, &cache); +// +// const base = +// \\ +// \\{% block title %}Título Padrão{% endblock %} +// \\ +// \\{% block content %}Conteúdo padrão{% endblock %} +// \\ +// \\ +// ; +// +// const child = +// \\{% extends "base.html" %} +// \\{% block title %}Meu Título{% endblock %} +// \\{% block content %} +// \\Olá {{ nome }}! +// \\{% endblock %} +// ; +// +// const expected = +// \\ +// \\Meu Título +// \\ +// \\ +// \\Olá Lucas! +// \\ +// \\ +// \\ +// ; +// +// // Simula arquivos (ou usa renderString com base se quiser simplificar) +// try std.fs.cwd().writeFile(.{ .sub_path = "base.html", .data = base }); +// try std.fs.cwd().writeFile(.{ .sub_path = "child.html", .data = child }); +// defer std.fs.cwd().deleteFile("base.html") catch {}; +// defer std.fs.cwd().deleteFile("child.html") catch {}; +// +// try ctx.set("nome", Value{ .string = "Lucas" }); +// +// var buf = std.ArrayList(u8){}; +// defer buf.deinit(alloc); +// +// try renderer.render("child.html", buf.writer(alloc)); +// +// const output = buf.items; +// +// std.debug.print("OUTPUT:\n{s}\n", .{output}); +// +// try testing.expect(std.mem.indexOf(u8, output, "") != null); +// try testing.expect(std.mem.indexOf(u8, output, "Meu Título") != null); +// try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas!") != null); +// try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") == null); +// try testing.expectEqualStrings(expected, output); +// } +// +// test "renderer - block and extends with super" { +// const alloc = testing.allocator; +// var ctx = Context.init(alloc); +// defer ctx.deinit(); +// +// var cache = TemplateCache.init(alloc); +// defer cache.deinit(); +// +// const renderer = Renderer.init(&ctx, &cache); +// +// const base = +// \\ +// \\{% block title %}Título Padrão{% endblock %} +// \\ +// \\{% block content %}Conteúdo padrão{% endblock %} +// \\ +// \\ +// ; +// +// const child = +// \\{% extends "base.html" %} +// \\{% block title %}Meu Título{% endblock %} +// \\{% block content %} +// \\{{ block.super }} +// \\Olá {{ nome }}! +// \\{% endblock %} +// ; +// +// const expected = +// \\ +// \\Meu Título +// \\ +// \\ +// \\Conteúdo padrão +// \\Olá Lucas! +// \\ +// \\ +// \\ +// ; +// +// // Simula arquivos (ou usa renderString com base se quiser simplificar) +// try std.fs.cwd().writeFile(.{ .sub_path = "base.html", .data = base }); +// try std.fs.cwd().writeFile(.{ .sub_path = "child.html", .data = child }); +// defer std.fs.cwd().deleteFile("base.html") catch {}; +// defer std.fs.cwd().deleteFile("child.html") catch {}; +// +// try ctx.set("nome", Value{ .string = "Lucas" }); +// +// var buf = std.ArrayList(u8){}; +// defer buf.deinit(alloc); +// +// try renderer.render("child.html", buf.writer(alloc)); +// +// const output = buf.items; +// +// std.debug.print("{s}\n", .{output}); +// +// try testing.expect(std.mem.indexOf(u8, output, "") != null); +// try testing.expect(std.mem.indexOf(u8, output, "Meu Título") != null); +// try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas!") != null); +// try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") != null); +// try testing.expectEqualStrings(expected, output); +// } +// +// test "renderer - include" { +// const alloc = testing.allocator; +// +// const header = +// \\
    +// \\

    Bem-vindo

    +// \\
    +// ; +// +// const main = +// \\{% include "header.html" %} +// \\
    +// \\

    Conteúdo principal

    +// \\ Olá {{ nome }}! +// \\
    +// ; +// +// const expected = +// \\
    +// \\

    Bem-vindo

    +// \\
    +// \\
    +// \\

    Conteúdo principal

    +// \\ Olá Lucas! +// \\
    +// ; +// +// try std.fs.cwd().writeFile(.{ .sub_path = "header.html", .data = header }); +// try std.fs.cwd().writeFile(.{ .sub_path = "main.html", .data = main }); +// defer std.fs.cwd().deleteFile("header.html") catch {}; +// defer std.fs.cwd().deleteFile("main.html") catch {}; +// +// var ctx = Context.init(alloc); +// defer ctx.deinit(); +// +// var cache = TemplateCache.init(alloc); +// defer cache.deinit(); +// +// const renderer = Renderer.init(&ctx, &cache); +// +// try ctx.set("nome", Value{ .string = "Lucas" }); +// +// var buf = std.ArrayList(u8){}; +// defer buf.deinit(alloc); +// +// try renderer.render("main.html", buf.writer(alloc)); +// +// const output = buf.items; +// +// try testing.expect(std.mem.indexOf(u8, output, "

    Bem-vindo

    ") != null); +// try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas!") != null); +// try testing.expectEqualStrings(expected, output); +// } +// +// test "renderer - comment" { +// const alloc = testing.allocator; +// +// const template = +// \\Normal: Olá {{ nome }} +// \\{% comment %} +// \\ Isso é um comentário +// \\ que não deve aparecer +// \\ nem processar variáveis {{ nome }} +// \\{% endcomment %} +// \\Fim: {{ nome }} +// ; +// const expected = +// \\Normal: Olá Lucas +// \\ +// \\Fim: Lucas +// ; +// +// var ctx = Context.init(alloc); +// defer ctx.deinit(); +// +// var cache = TemplateCache.init(alloc); +// defer cache.deinit(); +// +// const renderer = Renderer.init(&ctx, &cache); +// +// try ctx.set("nome", Value{ .string = "Lucas" }); +// +// var buf = std.ArrayList(u8){}; +// defer buf.deinit(alloc); +// +// try renderer.renderString(template, buf.writer(alloc)); +// +// const output = buf.items; +// +// try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas") != null); +// try testing.expect(std.mem.indexOf(u8, output, "Fim: Lucas") != null); +// try testing.expect(std.mem.indexOf(u8, output, "Isso é um comentário") == null); +// try testing.expect(std.mem.indexOf(u8, output, "que não deve aparecer") == null); +// try testing.expect(std.mem.indexOf(u8, output, "nem processar variáveis") == null); +// try testing.expectEqualStrings(expected, output); +// } + +// FIX: comment inside block + +// test "renderer - full template with extends, super, include, comment" { +// const alloc = testing.allocator; +// +// const header = "
    Bem-vindo
    "; +// const base = +// \\{% include "header.html" %} +// \\
    +// \\ {% block content %} +// \\ Conteúdo padrão +// \\ {% endblock %} +// \\
    +// ; +// +// const child = +// \\{% extends "base.html" %} +// \\{% block content %} +// \\ {{ block.super }} +// \\ Conteúdo do filho +// \\ {% comment %} Isso não aparece {% endcomment %} +// \\{% endblock %} +// ; +// +// try std.fs.cwd().writeFile(.{ .sub_path = "header.html", .data = header }); +// try std.fs.cwd().writeFile(.{ .sub_path = "base.html", .data = base }); +// try std.fs.cwd().writeFile(.{ .sub_path = "child.html", .data = child }); +// defer std.fs.cwd().deleteFile("header.html") catch {}; +// defer std.fs.cwd().deleteFile("base.html") catch {}; +// defer std.fs.cwd().deleteFile("child.html") catch {}; +// +// var ctx = Context.init(alloc); +// defer ctx.deinit(); +// +// var cache = TemplateCache.init(alloc); +// defer cache.deinit(); +// +// const renderer = Renderer.init(&ctx, &cache); +// +// var buf = std.ArrayList(u8){}; +// defer buf.deinit(alloc); +// +// try renderer.render("child.html", buf.writer(alloc)); +// +// const output = buf.items; +// +// std.debug.print("OUTPUT:\n{s}\n", .{output}); +// +// try testing.expect(std.mem.indexOf(u8, output, "
    Bem-vindo
    ") != null); +// try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") != null); +// try testing.expect(std.mem.indexOf(u8, output, "Conteúdo do filho") != null); +// try testing.expect(std.mem.indexOf(u8, output, "Isso não aparece") == null); +// } test "renderer - if inside block" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("12 - render - if inside block\n", .{}); const alloc = testing.allocator; const base = @@ -524,7 +478,6 @@ test "renderer - if inside block" { var cache = TemplateCache.init(alloc); defer cache.deinit(); - cache.default_path = "."; const renderer = Renderer.init(&ctx, &cache); @@ -537,564 +490,9 @@ test "renderer - if inside block" { const output = buf.items; - // std.debug.print("OUTPUT:\n\n{s}\n", .{output}); + std.debug.print("OUTPUT:\n{s}\n", .{output}); try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") != null); try testing.expect(std.mem.indexOf(u8, output, "Conteúdo do filho") != null); try testing.expect(std.mem.indexOf(u8, output, "Oops") == null); - try testing.expect(std.mem.indexOf(u8, output, "Idade: 23") != null); -} - -test "renderer - if with operators" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("13 - render - if inside block\n", .{}); - - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - try ctx.set("idade", Value{ .int = 20 }); - try ctx.set("nome", Value{ .string = "Lucas" }); - try ctx.set("ativo", Value{ .bool = true }); - try ctx.set("order", 1); - - const template = - \\{% if idade > 18 %}Maior{% endif %} - \\{% if idade < 18 %}Menor{% endif %} - \\{% if nome == "Lucas" %}Olá Lucas{% endif %} - \\{% if ativo %}Ativo{% endif %} - \\{% if order >= 2 %}High Order{% else %}Low Order{% endif %} - ; - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.renderString(template, buf.writer(alloc)); - - // std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expect(std.mem.indexOf(u8, buf.items, "Maior") != null); - try testing.expect(std.mem.indexOf(u8, buf.items, "Olá Lucas") != null); - try testing.expect(std.mem.indexOf(u8, buf.items, "Ativo") != null); - try testing.expect(std.mem.indexOf(u8, buf.items, "Menor") == null); - try testing.expect(std.mem.indexOf(u8, buf.items, "Low Order") != null); -} - -test "renderer - widthratio inside block" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("14 - render - widthratio inside block\n", .{}); - const alloc = testing.allocator; - - const base = - \\
    - \\ {% block content %} - \\ Conteúdo padrão - \\ {% endblock %} - \\
    - ; - - const child = - \\{% extends "base.html" %} - \\{% block content %} - \\ Conteúdo do filho - \\Bar - \\ {{ block.super }} - \\{% endblock %} - ; - - try std.fs.cwd().writeFile(.{ .sub_path = "base.html", .data = base }); - try std.fs.cwd().writeFile(.{ .sub_path = "child.html", .data = child }); - defer std.fs.cwd().deleteFile("base.html") catch {}; - defer std.fs.cwd().deleteFile("child.html") catch {}; - - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - cache.default_path = "."; - - const renderer = Renderer.init(&ctx, &cache); - - try ctx.set("value", 50); - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.render("child.html", buf.writer(alloc)); - - const output = buf.items; - - // std.debug.print("OUTPUT:\n\n{s}\n", .{output}); - - try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") != null); - try testing.expect(std.mem.indexOf(u8, output, "Conteúdo do filho") != null); -} - -test "renderer - now" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("15 - render now\n", .{}); - - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - try ctx.set("idade", Value{ .int = 20 }); - - const template = - \\{% now %} - \\{% now \"Y-m-d H:i:s\" %} - \\{% now \"Y" %} - \\{% now \"m\" %} - \\{% now \"n\" %} - \\{% now \"d\" %} - \\{% now \"j\" %} - \\{% now \"F\" %} - \\{% now \"M\" %} - \\{% now \"l\" %} - \\{% now \"D\" %} - \\{% now \"H:i:s\" %} - \\{% now \"H\" %} - \\{% now \"G\" %} - \\{% now \"i\" %} - \\{% now \"s\" %} - \\{% now \"a\" %} - \\{% now \"A\" %} - \\{% now \"P\" %} - ; - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.renderString(template, buf.writer(alloc)); - - std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - // try testing.expect(std.mem.indexOf(u8, buf.items, "Maior") != null); -} - -test "renderer - csrf_token in context" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("15 - csrf_token in context\n", .{}); - - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - const token: []const u8 = "zh5fyUSICjXNsDTtJCjl9A3O2dDSHhYFlIngAEO6PXK9NX56Z1XLEy7doYuPcE0u"; - - try ctx.set("csrf_token", token); - const template = - \\{% csrf_token %} - ; - - const expected = - \\ - ; - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.renderString(template, buf.writer(alloc)); - - std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expectEqualStrings(expected, buf.items); -} - -test "renderer - csrf_token not in context" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("16 - csrf_token not in context\n", .{}); - - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - const template = - \\{% csrf_token %} - ; - - const expected = - \\ - ; - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.renderString(template, buf.writer(alloc)); - - // std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expectEqualStrings(expected, buf.items); -} - -// TODO: add parse filters to variables -test "renderer - firstof withtout fallback" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("17 - firstof without fallback\n", .{}); - - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - try ctx.set("var1", ""); - try ctx.set("var2", "baz"); - - const template = - \\{% firstof var1 var2 %} - ; - - const expected = - \\baz - ; - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.renderString(template, buf.writer(alloc)); - - // std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expectEqualStrings(expected, buf.items); -} - -test "renderer - firstof with fallback" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("18 - firstof with fallback\n", .{}); - - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - try ctx.set("var1", ""); - try ctx.set("var2", 0); - - const template = - \\{% firstof var1 var2 "Oops no value!" %} - ; - - const expected = - \\Oops no value! - ; - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.renderString(template, buf.writer(alloc)); - - // std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expectEqualStrings(expected, buf.items); -} - -test "renderer - firstof without value in context" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("19 - firstof without value in context\n", .{}); - - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - const template = - \\{% firstof 0 true "Oops no value!" %} - ; - - const expected = - \\true - ; - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.renderString(template, buf.writer(alloc)); - - // std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expectEqualStrings(expected, buf.items); -} - -test "renderer - firstof missing vars" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("20 - firstof missing vars\n", .{}); - - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - const template = - \\{% firstof %} - ; - - const expected = - \\ - ; - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.renderString(template, buf.writer(alloc)); - - // std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expectEqualStrings(expected, buf.items); -} - -test "renderer - firstof missing vars with fallback" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("21 - firstof missing vars with fallback\n", .{}); - - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - const template = - \\{% firstof "nothing here" %} - ; - - const expected = - \\nothing here - ; - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.renderString(template, buf.writer(alloc)); - - // std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expectEqualStrings(expected, buf.items); -} - -test "renderer - lorem" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("22 - lorem\n", .{}); - - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - const template = - \\{% lorem %} - ; - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.renderString(template, buf.writer(alloc)); - - std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expect(std.mem.indexOf(u8, buf.items, "Lorem ipsum") != null); -} - -test "renderer - lorem with count and method words" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("23 - lorem with count and method words\n", .{}); - - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - const template = - \\{% lorem 3 w %} - ; - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.renderString(template, buf.writer(alloc)); - - std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expectEqualStrings("lorem ipsum dolor", buf.items); -} - -test "renderer - lorem with count and method paragraphs" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("24 - lorem with count and method paragraphs\n", .{}); - - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - const template = - \\{% lorem 5 p %} - ; - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.renderString(template, buf.writer(alloc)); - - std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - const qty = std.mem.count(u8, buf.items, "

    "); - try testing.expect(std.mem.indexOf(u8, buf.items, "Lorem ipsum dolor") != null); - try testing.expect(qty == 5); -} - -test "renderer - lorem only random" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("25 - lorem only random\n", .{}); - - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - const template = - \\{% lorem true %} - ; - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.renderString(template, buf.writer(alloc)); - - std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expect(buf.items.len > 0); -} - -test "renderer - lorem words random" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("26 - lorem words random\n", .{}); - - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - const template = - \\{% lorem 6 w true %} - ; - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.renderString(template, buf.writer(alloc)); - - std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - const spaces = std.mem.count(u8, buf.items, " "); - try testing.expect(spaces == 5); -} - -test "renderer - lorem paragraphs random" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("26 - lorem paragraphs random\n", .{}); - - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - const template = - \\{% lorem 3 p true %} - ; - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.renderString(template, buf.writer(alloc)); - - std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - const spaces = std.mem.count(u8, buf.items, "

    "); - try testing.expect(spaces == 3); -} - -test "renderer - svg" { - std.debug.print("____________________________________________________\n", .{}); - std.debug.print("27 - svg\n", .{}); - - const alloc = testing.allocator; - var ctx = Context.init(alloc); - defer ctx.deinit(); - - var cache = TemplateCache.init(alloc); - try cache.initIcons(); - defer cache.deinit(); - - const renderer = Renderer.init(&ctx, &cache); - - const template = - \\{% svg material kangaroo %} - ; - - var buf = std.ArrayList(u8){}; - defer buf.deinit(alloc); - - try renderer.renderString(template, buf.writer(alloc)); - - std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items}); - - try testing.expect(std.mem.indexOf(u8, buf.items, "

    ") != null); - // const spaces = std.mem.count(u8, buf.items, "

    "); - // try testing.expect(spaces == 3); } diff --git a/src/root.zig b/src/root.zig index ff490a0..94c7cd0 100644 --- a/src/root.zig +++ b/src/root.zig @@ -1,11 +1,23 @@ -pub const cache = @import("cache.zig"); -pub const context = @import("context.zig"); -pub const delta = @import("delta.zig"); -pub const filters = @import("filters.zig"); -pub const icons = @import("svg/icons.zig"); -pub const lorem = @import("lorem.zig"); -pub const meta = @import("meta.zig"); -pub const parser = @import("parser.zig"); -pub const renderer = @import("renderer.zig"); -pub const time = @import("time.zig"); -pub const util = @import("util.zig"); +//! By convention, root.zig is the root source file when making a library. +const std = @import("std"); + +pub fn bufferedPrint() !void { + // Stdout is for the actual output of your application, for example if you + // are implementing gzip, then only the compressed bytes should be sent to + // stdout, not any debugging messages. + var stdout_buffer: [1024]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + const stdout = &stdout_writer.interface; + + try stdout.print("Run `zig build test` to run the tests.\n", .{}); + + try stdout.flush(); // Don't forget to flush! +} + +pub fn add(a: i32, b: i32) i32 { + return a + b; +} + +test "basic add functionality" { + try std.testing.expect(add(3, 7) == 10); +} diff --git a/src/svg/icons.zig b/src/svg/icons.zig deleted file mode 100644 index 5b4ad4f..0000000 --- a/src/svg/icons.zig +++ /dev/null @@ -1,117 +0,0 @@ -const std = @import("std"); - -pub const IconSet = enum { - bootstrap, - dripicons, - hero_outline, - hero_solid, - material, -}; - -pub const embedded_data = std.EnumMap(IconSet, []const u8).init(.{ - .bootstrap = @embedFile("bootstrap.svgs.bin"), - .dripicons = @embedFile("dripicons.svgs.bin"), - .hero_outline = @embedFile("hero_outline.svgs.bin"), - .hero_solid = @embedFile("hero_solid.svgs.bin"), - .material = @embedFile("material.svgs.bin"), -}); - -pub const SvgIcon = struct { - icon_map: std.StringHashMapUnmanaged([]const u8) = .{}, - - pub fn init(allocator: std.mem.Allocator) !SvgIcon { - var self = SvgIcon{}; - - inline for (std.meta.fields(IconSet)) |field| { - const set = @field(IconSet, field.name); - const data = embedded_data.get(set); - - try self.loadSet(allocator, set, data.?); - } - - return self; - } - - pub fn deinit(self: *SvgIcon, allocator: std.mem.Allocator) void { - var it = self.icon_map.iterator(); - while (it.next()) |entry| { - allocator.free(entry.key_ptr.*); - allocator.free(entry.value_ptr.*); - } - self.icon_map.deinit(allocator); - self.* = .{}; - } - - pub fn get(self: *const SvgIcon, key: []const u8) ?[]const u8 { - return self.icon_map.get(key); - } - - pub fn getIcon(self: *const SvgIcon,allocator: std.mem.Allocator, kind: []const u8, name: []const u8) ?[]const u8 { - const key = std.fmt.allocPrint(allocator, "{s}:{s}", .{ kind, name }) catch return null; - defer allocator.free(key); - return self.icon_map.get(key); - } - - pub fn count(self: *const SvgIcon) usize { - return self.icon_map.count(); - } - - fn loadSet( - self: *SvgIcon, - allocator: std.mem.Allocator, - set: IconSet, - data: []const u8, - ) !void { - if (data.len < 12) return error.InvalidEmbeddedData; - - var pos: usize = 0; - - const magic = std.mem.readInt(u32, data[pos..][0..4], .little); - pos += 4; - if (magic != 0x53564749) return error.InvalidMagic; - - const version = std.mem.readInt(u32, data[pos..][0..4], .little); - pos += 4; - if (version != 1) return error.UnsupportedVersion; - - const num_entries = std.mem.readInt(u32, data[pos..][0..4], .little); - pos += 4; - - const prefix = @tagName(set); - - var i: u32 = 0; - while (i < num_entries) : (i += 1) { - const name_len = std.mem.readInt(u32, data[pos..][0..4], .little); - pos += 4; - - if (pos + name_len > data.len) return error.CorruptedNameLength; - const name_slice = data[pos .. pos + name_len]; - pos += name_len; - - const svg_len = std.mem.readInt(u32, data[pos..][0..4], .little); - pos += 4; - - if (pos + svg_len > data.len) return error.CorruptedSvgLength; - const svg_slice = data[pos .. pos + svg_len]; - pos += svg_len; - - // Monta a chave com prefixo do set - const key = try std.fmt.allocPrint(allocator, "{s}:{s}", .{ prefix, name_slice }); - - // Duplica o conteúdo SVG (o map assume ownership) - const value = try allocator.dupe(u8, svg_slice); - - // Insere no mapa unmanaged - try self.icon_map.put(allocator, key, value); - } - } -}; - -pub const fallback_svg = - \\

    - \\ - \\ - \\ ? - \\ - \\
    -; diff --git a/src/time.zig b/src/time.zig deleted file mode 100644 index e27da58..0000000 --- a/src/time.zig +++ /dev/null @@ -1,878 +0,0 @@ -// https://github.com/cztomsik/tokamak -const std = @import("std"); -const util = @import("util.zig"); -const testing = std.testing; -const meta = @import("meta.zig"); -const dlt = @import("delta.zig"); - -const RATA_MIN = date_to_rata(Date.MIN); -const RATA_MAX = date_to_rata(Date.MAX); -const RATA_TO_UNIX = 719468; -const EOD = 86_400 - 1; - -pub const DAY_NAMES_SHORT = [_][]const u8{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; -pub const DAY_NAMES_LONG = [_][]const u8{ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; - -pub const MONTH_NAMES_SHORT = [_][]const u8{ "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; -pub const MONTH_NAMES_LONG = [_][]const u8{ "", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; - -pub const MONTH_DAYS = [12]u8{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - -pub const TIME_CHUNKS = [4]u32{ - 60 * 60 * 24 * 7, //week - 60 * 60 * 24, //day - 60 * 60, //hour - 60, //minute -}; - -pub const TIME_STRINGS = [6][]const u8{ "year", "month", "week", "day", "hour", "minute" }; - -// TODO: Decide if we want to use std.debug.assert(), @panic() or just throw an error -fn checkRange(num: anytype, min: @TypeOf(num), max: @TypeOf(num)) void { - if (util.lt(num, min) or util.gt(num, max)) { - // TODO: fix later (we can't use {f} and {any} is also wrong) - // std.log.warn("Value {} is not in range [{}, {}]", .{ num, min, max }); - std.log.warn("Value not in range", .{}); - } -} - -pub const TimeUnit = enum { second, minute, hour, day, month, year }; -pub const DateUnit = enum { day, month, year }; - -// pub const SECS_PER_DAY: i64 = 86_400; -// pub const SECS_PER_HOUR: i64 = 3_600; -// pub const SECS_PER_MIN: i64 = 60; - -pub const TimeError = error{ - Eof, - ExpectedNull, - ExpectedValue, - InvalidCharacter, - InvalidFormat, - Overflow, - OutOfMemory, -}; - -// https://www.youtube.com/watch?v=0s9F4QWAl-E&t=2120 -pub fn isLeapYear(year: i32) bool { - const d: i32 = if (@mod(year, 100) != 0) 4 else 16; - return (year & (d - 1)) == 0; -} - -// https://www.youtube.com/watch?v=0s9F4QWAl-E&t=2257 -pub fn daysInMonth(year: i32, month: u8) u8 { - if (month == 2) { - return if (isLeapYear(year)) 29 else 28; - } - - return 30 | (month ^ (month >> 3)); -} - -pub fn formatDateTime(alloc: std.mem.Allocator, t: Time, format_str: []const u8) ![]u8 { - var result = std.ArrayList(u8){}; - defer result.deinit(alloc); - var writer = result.writer(alloc); - - var i: usize = 0; - while (i < format_str.len) : (i += 1) { - const c = format_str[i]; - - if (c == '\\') { - i += 1; - if (i >= format_str.len) break; - try writer.writeByte(format_str[i]); - continue; - } - - // Todos os códigos do date + time que já implementamos - switch (c) { - // === Códigos de data (do filtro date) === - 'Y' => try writer.print("{d:0>4}", .{@as(u16, @intCast(t.year()))}), - 'm' => try writer.print("{d:0>2}", .{t.month()}), - 'n' => try writer.print("{d}", .{t.month()}), - 'd' => try writer.print("{d:0>2}", .{t.day()}), - 'j' => try writer.print("{d}", .{t.day()}), - 'F' => try writer.writeAll(t.monthNameLong()), - 'M' => try writer.writeAll(t.monthNameShort()), - 'l' => try writer.writeAll(t.weekdayNameLong()), - 'D' => try writer.writeAll(t.weekdayNameShort()), - - // === Códigos de tempo (do filtro time) === - 'H' => try writer.print("{d:0>2}", .{t.hour()}), - 'G' => try writer.print("{d}", .{t.hour()}), - 'i' => try writer.print("{d:0>2}", .{t.minute()}), - 's' => try writer.print("{d:0>2}", .{t.second()}), - 'a' => try writer.writeAll(if (t.hour() < 12) "a.m." else "p.m."), - 'A' => try writer.writeAll(if (t.hour() < 12) "AM" else "PM"), - 'P' => { - const hr24 = t.hour(); - const min = t.minute(); - if (hr24 == 0 and min == 0) { - try writer.writeAll("midnight"); - } else if (hr24 == 12 and min == 0) { - try writer.writeAll("noon"); - } else { - var hr12 = @mod(hr24, 12); - if (hr12 == 0) hr12 = 12; - try writer.print("{d}", .{hr12}); - if (min > 0) try writer.print(":{d:0>2}", .{min}); - try writer.writeAll(if (hr24 < 12) " a.m." else " p.m."); - } - }, - 'u' => try writer.writeAll("000000"), - - else => try writer.writeByte(c), - } - } - - return try result.toOwnedSlice(alloc); -} - -pub const Date = struct { - year: i32, - month: u8, - day: u8, - - pub const MIN = Date.ymd(-1467999, 1, 1); - pub const MAX = Date.ymd(1471744, 12, 31); - - pub fn cmp(a: Date, b: Date) std.math.Order { - if (a.year != b.year) return util.cmp(a.year, b.year); - if (a.month != b.month) return util.cmp(a.month, b.month); - return util.cmp(a.day, b.day); - } - - pub fn parse(str: []const u8) TimeError!Date { - var it = std.mem.splitScalar(u8, str, '-'); - return ymd( - try std.fmt.parseInt(i32, it.next() orelse return error.Eof, 10), - try std.fmt.parseInt(u8, it.next() orelse return error.Eof, 10), - try std.fmt.parseInt(u8, it.next() orelse return error.Eof, 10), - ); - } - - pub fn ymd(year: i32, month: u8, day: u8) Date { - return .{ - .year = year, - .month = month, - .day = day, - }; - } - - pub fn today() Date { - return Time.now().date(); - } - - pub fn yesterday() Date { - return today().add(.day, -1); - } - - pub fn tomorrow() Date { - return today().add(.day, 1); - } - - pub fn startOf(unit: DateUnit) Date { - return today().setStartOf(unit); - } - - pub fn endOf(unit: DateUnit) Date { - return today().setEndOf(unit); - } - - pub fn setStartOf(self: Date, unit: DateUnit) Date { - return switch (unit) { - .day => self, - .month => ymd(self.year, self.month, 1), - .year => ymd(self.year, 1, 1), - }; - } - - pub fn setEndOf(self: Date, unit: DateUnit) Date { - return switch (unit) { - .day => self, - .month => ymd(self.year, self.month, daysInMonth(self.year, self.month)), - .year => ymd(self.year, 12, 31), - }; - } - - pub fn add(self: Date, part: DateUnit, amount: i64) Date { - return switch (part) { - .day => Time.unix(0).setDate(self).add(.days, amount).date(), - .month => { - const total_months = @as(i32, self.month) + @as(i32, @intCast(amount)); - const new_year = self.year + @divFloor(total_months - 1, 12); - const new_month = @as(u8, @intCast(@mod(total_months - 1, 12) + 1)); - return ymd( - new_year, - new_month, - @min(self.day, daysInMonth(new_year, new_month)), - ); - }, - .year => { - const new_year = self.year + @as(i32, @intCast(amount)); - return ymd( - new_year, - self.month, - @min(self.day, daysInMonth(new_year, self.month)), - ); - }, - }; - } - - pub fn dayOfWeek(self: Date) u8 { - const rata_day = date_to_rata(self); - return @intCast(@mod(rata_day + 3, 7)); - } - - pub fn format(self: Date, writer: anytype) !void { - try writer.print("{d}-{d:0>2}-{d:0>2}", .{ - @as(u32, @intCast(self.year)), - self.month, - self.day, - }); - } - - pub fn ordinal(self: Date) usize { - const days_before_month = [_]u16{ 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; - - var days: usize = days_before_month[self.month]; - days += self.day; - - if (self.month > 2 and isLeapYear(self.year)) { - days += 1; - } - - return days; - } - - pub fn weekday(self: Date) u8 { - const y: i32 = self.year; - const m: u8 = self.month; - const d: u8 = self.day; - - const t = [_]i32{ 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 }; - var year_adj: i32 = y; - if (m < 3) year_adj -= 1; - - var wd: i32 = year_adj + @divFloor(year_adj, 4) - @divFloor(year_adj, 100) + @divFloor(year_adj, 400) + t[m - 1] + @as(i32, d); - wd = @mod(wd, 7); - if (wd < 0) wd += 7; - - return @intCast(if (wd == 6) 7 else wd + 1); - } - - pub fn isoWeek(self: Date) u8 { - const iso_y = self.isoWeekYear(); - const jan4 = Date{ .year = iso_y, .month = 1, .day = 4 }; - const jan4_ord: i32 = @intCast(jan4.ordinal()); - const self_ord: i32 = @intCast(self.ordinal()); - - const days_diff = self_ord - jan4_ord; - const week = @divFloor(days_diff + 4, 7) + 1; // +4 corrige o offset (testado em Python) - - return @intCast(@max(1, @min(53, week))); - } - - pub fn isoWeekYear(self: Date) i32 { - const wd = self.weekday(); // 1=Seg ... 7=Dom - const ord = @as(i32, @intCast(self.ordinal())); - const thursday_ord = ord + (4 - (wd - 1)); // quinta da semana - - var y = self.year; - - var days_in_year: i32 = 365; - if (isLeapYear(y)) days_in_year = 366; - - if (thursday_ord <= 0) { - y -= 1; - } else if (thursday_ord > days_in_year) { - y += 1; - } - return y; - } -}; - -pub const TimeTime = struct { - hour: u32, - minute: u32, - second: u32, - - fn cmp(a: TimeTime, b: TimeTime) std.math.Order { - if (a.hour < b.hour) return .lt; - if (a.hour > b.hour) return .gt; - if (a.minute < b.minute) return .lt; - if (a.minute > b.minute) return .gt; - if (a.second < b.second) return .lt; - if (a.second > b.second) return .gt; - return .eq; - } -}; - -pub const Time = struct { - epoch: i64, - - pub fn parse(str: []const u8) !Time { - if (std.mem.indexOfScalar(u8, str, ' ')) |space| { - // Datetime: "YYYY-MM-DD HH:MM:SS" - const date_str = str[0..space]; - const time_str = str[space + 1 ..]; - - const d = try Date.parse(date_str); - - var it = std.mem.splitScalar(u8, time_str, ':'); - const h = try std.fmt.parseInt(u8, it.next() orelse return error.InvalidFormat, 10); - const m = try std.fmt.parseInt(u8, it.next() orelse return error.InvalidFormat, 10); - const s = try std.fmt.parseInt(u8, it.next() orelse return error.InvalidFormat, 10); - - var t = Time.unix(0).setDate(d); - t = t.setHour(h).setMinute(m).setSecond(s); - return t; - } else { - const d = try Date.parse(str); - return Time.unix(0).setDate(d); - } - return Time.now(); - } - - pub fn unix(epoch: i64) Time { - return .{ .epoch = epoch }; - } - - pub fn new(y: i32, m: u8, d: u8, h: ?u32, min: ?u32, sec: ?u32) Time { - var t = unix(0).setDate(.ymd(y, m, d)); - if (h) |h_| t = t.setHour(h_); - if (min) |min_| t = t.setMinute(min_); - if (sec) |sec_| t = t.setSecond(sec_); - return t; - } - - pub fn now() Time { - return unix(std.time.timestamp()); - } - - pub fn now_offset(offset: i64) Time { - return unix(std.time.timestamp() + (offset * std.time.s_per_hour)); - } - - pub fn today() Time { - return unix(0).setDate(.today()); - } - - pub fn tomorrow() Time { - return unix(0).setDate(.tomorrow()); - } - - pub fn startOf(unit: TimeUnit) Time { - return Time.now().setStartOf(unit); - } - - pub fn endOf(unit: TimeUnit) Time { - return Time.now().setEndOf(unit); - } - - pub fn second(self: Time) u32 { - return @intCast(@mod(self.total(.seconds), 60)); - } - - pub fn setSecond(self: Time, sec: u32) Time { - return self.add(.seconds, @as(i64, sec) - self.second()); - } - - pub fn minute(self: Time) u32 { - return @intCast(@mod(self.total(.minutes), 60)); - } - - pub fn setMinute(self: Time, min: u32) Time { - return self.add(.minutes, @as(i64, min) - self.minute()); - } - - pub fn hour(self: Time) u32 { - return @intCast(@mod(self.total(.hours), 24)); - } - - pub fn setHour(self: Time, hr: u32) Time { - return self.add(.hours, @as(i64, hr) - self.hour()); - } - - pub fn date(self: Time) Date { - return rata_to_date(@divTrunc(self.epoch, std.time.s_per_day) + RATA_TO_UNIX); - } - - pub fn setDate(self: Time, dat: Date) Time { - var res: i64 = @mod(self.epoch, std.time.s_per_day); - res += (date_to_rata(dat) - RATA_TO_UNIX) * std.time.s_per_day; - return unix(res); - } - - pub fn time(self: Time) TimeTime { - return .{ - .hour = self.hour(), - .minute = self.minute(), - .second = self.second(), - }; - } - - pub fn setStartOf(self: Time, unit: TimeUnit) Time { - // TODO: continue :label? - return switch (unit) { - .second => self, - .minute => self.setSecond(0), - .hour => self.setSecond(0).setMinute(0), - .day => self.setSecond(0).setMinute(0).setHour(0), - .month => { - const d = self.date(); - return unix(0).setDate(.ymd(d.year, d.month, 1)); - }, - .year => { - const d = self.date(); - return unix(0).setDate(.ymd(d.year, 1, 1)); - }, - }; - } - - // TODO: rename to startOfNext? - pub fn next(self: Time, unit: enum { second, minute, hour, day }) Time { - return switch (unit) { - .second => self.add(.seconds, 1), - .minute => self.setSecond(0).add(.minutes, 1), - .hour => self.setSecond(0).setMinute(0).add(.hours, 1), - .day => self.setSecond(0).setMinute(0).setHour(0).add(.hours, 24), - }; - } - - pub fn setEndOf(self: Time, unit: TimeUnit) Time { - // TODO: continue :label? - return switch (unit) { - .second => self, - .minute => self.setSecond(59), - .hour => self.setSecond(59).setMinute(59), - .day => self.setSecond(59).setMinute(59).setHour(23), - .month => { - const d = self.date(); - return unix(EOD).setDate(.ymd(d.year, d.month, daysInMonth(d.year, d.month))); - }, - .year => { - const d = self.date(); - return unix(EOD).setDate(.ymd(d.year, 12, 31)); - }, - }; - } - - pub fn add(self: Time, part: enum { seconds, minutes, hours, days, months, years }, amount: i64) Time { - const n = switch (part) { - .seconds => amount, - .minutes => amount * std.time.s_per_min, - .hours => amount * std.time.s_per_hour, - .days => amount * std.time.s_per_day, - .months => return self.setDate(self.date().add(.month, amount)), - .years => return self.setDate(self.date().add(.year, amount)), - }; - - return .{ .epoch = self.epoch + n }; - } - - fn total(self: Time, part: enum { seconds, minutes, hours }) i64 { - return switch (part) { - .seconds => self.epoch, - .minutes => @divTrunc(self.epoch, std.time.s_per_min), - .hours => @divTrunc(self.epoch, std.time.s_per_hour), - }; - } - - fn year(self: Time) i32 { - return self.date().year; - } - - fn month(self: Time) u8 { - return self.date().month; - } - - fn day(self: Time) u8 { - return self.date().day; - } - - fn monthNameLong(self: Time) []const u8 { - return MONTH_NAMES_LONG[self.date().month]; - } - - fn monthNameShort(self: Time) []const u8 { - return MONTH_NAMES_SHORT[self.date().month]; - } - - fn weekdayNameLong(self: Time) []const u8 { - return DAY_NAMES_LONG[self.date().weekday()]; - } - - fn weekdayNameShort(self: Time) []const u8 { - return DAY_NAMES_SHORT[self.date().weekday()]; - } - - pub fn format(self: Time, writer: anytype) !void { - try writer.print("{f} {d:0>2}:{d:0>2}:{d:0>2} UTC", .{ - self.date(), - self.hour(), - self.minute(), - self.second(), - }); - } - - pub fn toStringAlloc(self: Time, alloc: std.mem.Allocator, format_str: ?[]const u8) TimeError![]u8 { - const fmt = format_str orelse "Y-m-d H:i:s"; - return try formatDateTime(alloc, self, fmt); - } - - pub fn toString(self: Time, format_str: ?[]const u8) TimeError![]const u8 { - return try self.toStringAlloc(std.heap.page_allocator, format_str); - } - - pub fn addRelative(self: Time, delta: dlt.RelativeDelta) Time { - var d = delta; - d.normalize(); // garante que meses/horas/etc estejam normalizados - - // 1. Parte calendáríca (anos + meses + dias) - var dt = self.date(); - - // anos primeiro (mais estável) - if (d.years != 0) { - dt = dt.add(.year, d.years); - } - - // depois meses (respeita dias-in-mês) - if (d.months != 0) { - dt = dt.add(.month, d.months); - } - - // por fim dias normais - if (d.days != 0) { - dt = dt.add(.day, d.days); - } - - // 2. Parte do relógio (horas, minutos, segundos) - var result = self.setDate(dt); - - // Podemos usar o .add() existente para segundos/minutos/horas - if (d.seconds != 0) { - result = result.add(.seconds, d.seconds); - } - if (d.minutes != 0) { - result = result.add(.minutes, d.minutes); - } - if (d.hours != 0) { - result = result.add(.hours, d.hours); - } - - return result; - } - - pub fn subRelative(self: Time, other: Time) dlt.RelativeDelta { - var delta = dlt.RelativeDelta{}; - - // Parte de tempo (horas, min, seg) – igual antes - var seconds_diff: i64 = self.epoch - other.epoch; - - delta.seconds = @as(i32, @intCast(@rem(seconds_diff, 60))); - seconds_diff = @divTrunc(seconds_diff, 60); - - delta.minutes = @as(i32, @intCast(@rem(seconds_diff, 60))); - seconds_diff = @divTrunc(seconds_diff, 60); - - delta.hours = @as(i32, @intCast(@rem(seconds_diff, 24))); - seconds_diff = @divTrunc(seconds_diff, 24); - - // Parte calendárica - var later = self.date(); - var earlier = other.date(); - - const swapped = later.cmp(earlier) == .lt; - - if (swapped) { - // const temp = later; - // later = earlier; - // earlier = temp; - std.mem.swap(i32, &later.year, &earlier.year); - } - - var years: i32 = later.year - earlier.year; - var months: i32 = @as(i32, later.month) - @as(i32, earlier.month); - - if (months < 0) { - years -= 1; - months += 12; - } - - var days: i32 = @as(i32, later.day) - @as(i32, earlier.day); - - // Ajuste rigoroso para borrow (com handling de bissexto) - if (days < 0) { - const days_in_target = daysInMonth(later.year, later.month); - if (later.day == days_in_target) { - // Caso especial (ex: 28/fev não-bissexto vs 29/fev bissexto): ignora borrow, trata como período completo - days = 0; - } else { - // Borrow normal, mas ajusta se earlier.day > dias no mês anterior (para casos como 29 > 28) - const prev_month = later.add(.month, -1); - var days_borrow: i32 = @as(i32, daysInMonth(prev_month.year, prev_month.month)); - if (earlier.day > @as(u8, @intCast(days_borrow))) { - days_borrow = @as(i32, earlier.day); - } - std.debug.print("days_borrow em subRelative {d}\n", .{days_borrow}); - days += days_borrow; - months -= 1; - if (months < 0) { - years -= 1; - months += 12; - } - } - } - - // Atribui com sinal - delta.years = if (swapped) -years else years; - delta.months = if (swapped) -months else months; - delta.days = if (swapped) -days else days; - - // Normaliza (já lida com excessos, mas aqui é só para meses/anos) - delta.normalize(); - - return delta; - } - - pub fn timeSince(self: Time, alloc: std.mem.Allocator, then: Time) TimeError![]u8 { - if (self.epoch >= then.epoch) { - return try alloc.dupe(u8, "0 minutes"); - } - var total_months: i64 = 0; - const delta_year: i64 = (then.year() - self.year()) * 12; - const delta_month: i32 = @as(i32, @intCast(then.month())) - @as(i32, @intCast(self.month())); - - total_months = delta_year + delta_month; - - if (self.day() > then.day() or (self.day() == then.day() and self.time().cmp(then.time()) == .gt)) { - total_months -= 1; - } - const months = @rem(total_months, 12); - const years = @divTrunc(total_months, 12); - var pivot_year: i64 = 0; - var pivot_month: i64 = 0; - var pivot: Time = undefined; - - if (years > 0 or months > 0) { - pivot_year = @as(i64, self.year()) + years; - pivot_month = @as(i64, self.month()) + months; - if (pivot_month > 12) { - pivot_year += 1; - pivot_month -= 12; - } - const d: u8 = @min(MONTH_DAYS[@intCast(pivot_month - 1)], self.day()); - pivot = Time.new( - @as(i32, @intCast(pivot_year)), - @as(u8, @intCast(pivot_month)), - d, - self.hour(), - self.minute(), - self.second(), - ); - } else { - pivot = self; - } - var remaining_time = then.epoch - pivot.epoch; - - var partials = std.ArrayList(i64){}; - errdefer partials.deinit(alloc); - - try partials.append(alloc, years); - try partials.append(alloc, months); - - for (TIME_CHUNKS) |chunk| { - const count: i32 = @intCast(@divFloor(remaining_time, chunk)); - try partials.append(alloc, count); - remaining_time -= count * @as(i32, @intCast(chunk)); - } - - const min: i64 = std.mem.min(i64, partials.items); - const max: i64 = std.mem.max(i64, partials.items); - - if (min == 0 and max == 0) { - return try alloc.dupe(u8, "0 minutes"); - } - - var buf = std.ArrayList(u8){}; - errdefer buf.deinit(alloc); - - var count: i32 = 0; - for (partials.items, 0..) |partial, i| { - if (partial > 0) { - if (count >= 2) break; - try buf.appendSlice(alloc, try std.fmt.allocPrint(alloc, "{d} {s}{s}", .{ partial, TIME_STRINGS[i], if (partial > 1) "s" else "" })); - if (count == 0 and i < partials.items.len - 1) try buf.appendSlice(alloc, ", "); - count += 1; - } - } - - return try buf.toOwnedSlice(alloc); - } -}; - -// https://github.com/cassioneri/eaf/blob/1509faf37a0e0f59f5d4f11d0456fd0973c08f85/eaf/gregorian.hpp#L42 -fn rata_to_date(N: i64) Date { - checkRange(N, RATA_MIN, RATA_MAX); - - // Century. - const N_1: i64 = 4 * N + 3; - const C: i64 = quotient(N_1, 146097); - const N_C: u32 = remainder(N_1, 146097) / 4; - - // Year. - const N_2 = 4 * N_C + 3; - const Z: u32 = N_2 / 1461; - const N_Y: u32 = N_2 % 1461 / 4; - const Y: i64 = 100 * C + Z; - - // Month and day. - const N_3: u32 = 5 * N_Y + 461; - const M: u32 = N_3 / 153; - const D: u32 = N_3 % 153 / 5; - - // Map. - const J: u32 = @intFromBool(M >= 13); - - return .{ - .year = @intCast(Y + J), - .month = @intCast(M - 12 * J), - .day = @intCast(D + 1), - }; -} - -// https://github.com/cassioneri/eaf/blob/1509faf37a0e0f59f5d4f11d0456fd0973c08f85/eaf/gregorian.hpp#L88 -fn date_to_rata(date: Date) i32 { - checkRange(date, Date.MIN, Date.MAX); - - // Map. - const J: u32 = @intFromBool(date.month <= 2); - const Y: i32 = date.year - @as(i32, @intCast(J)); - const M: u32 = date.month + 12 * J; - const D: u32 = date.day - 1; - const C: i32 = @intCast(quotient(Y, 100)); - - // Rata die. - const y_star: i32 = @intCast(quotient(1461 * @as(i64, Y), 4) - C + quotient(C, 4)); // n_days in all prev. years - const m_star: u32 = (153 * M - 457) / 5; // n_days in prev. months - - return y_star + @as(i32, @intCast(m_star)) + @as(i32, @intCast(D)); -} - -fn quotient(n: i64, d: u32) i64 { - return if (n >= 0) @divTrunc(n, d) else @divTrunc((n + 1), d) - 1; -} - -fn remainder(n: i64, d: u32) u32 { - return @intCast(if (n >= 0) @mod(n, d) else (n + d) - d * quotient((n + d), d)); -} - -// const testing = @import("testing.zig"); -/// Attempts to print `arg` into a buf and then compare those strings. -pub const allocator = std.testing.allocator; -pub fn expectFmt(arg: anytype, expected: []const u8) !void { - var wb = std.io.Writer.Allocating.init(allocator); - defer wb.deinit(); - - try wb.writer.print("{f}", .{arg}); - try std.testing.expectEqualStrings(expected, wb.written()); -} - -pub fn expectEqual(res: anytype, expected: meta.Const(@TypeOf(res))) TimeError!void { - if (meta.isOptional(@TypeOf(res))) { - if (expected) |e| return expectEqual(res orelse return error.ExpectedValue, e); - if (res != null) return error.ExpectedNull; - } - - // TODO: find all usages of expectEqualStrings and replace it with our expectEqual - if (meta.isString(@TypeOf(res))) { - return std.testing.expectEqualStrings(expected, res); - } - - return std.testing.expectEqual(expected, res); -} - -// test "basic usage" { -// const t1 = Time.unix(1234567890); -// try expectFmt(t1, "2009-02-13 23:31:30 UTC"); -// -// try expectEqual(t1.date(), .{ -// .year = 2009, -// .month = 2, -// .day = 13, -// }); -// -// try expectEqual(t1.hour(), 23); -// try expectEqual(t1.minute(), 31); -// try expectEqual(t1.second(), 30); -// -// const t2 = t1.setHour(10).setMinute(15).setSecond(45); -// try expectFmt(t2, "2009-02-13 10:15:45 UTC"); -// -// const t3 = t2.add(.hours, 14).add(.minutes, 46).add(.seconds, 18); -// try expectFmt(t3, "2009-02-14 01:02:03 UTC"); -// -// // t.next() -// try expectFmt(t3.next(.second), "2009-02-14 01:02:04 UTC"); -// try expectFmt(t3.next(.minute), "2009-02-14 01:03:00 UTC"); -// try expectFmt(t3.next(.hour), "2009-02-14 02:00:00 UTC"); -// try expectFmt(t3.next(.day), "2009-02-15 00:00:00 UTC"); -// -// // t.setStartOf() -// try expectFmt(t3.setStartOf(.minute), "2009-02-14 01:02:00 UTC"); -// try expectFmt(t3.setStartOf(.hour), "2009-02-14 01:00:00 UTC"); -// try expectFmt(t3.setStartOf(.day), "2009-02-14 00:00:00 UTC"); -// try expectFmt(t3.setStartOf(.month), "2009-02-01 00:00:00 UTC"); -// try expectFmt(t3.setStartOf(.year), "2009-01-01 00:00:00 UTC"); -// -// // t.setEndOf() -// try expectFmt(t3.setEndOf(.minute), "2009-02-14 01:02:59 UTC"); -// try expectFmt(t3.setEndOf(.hour), "2009-02-14 01:59:59 UTC"); -// try expectFmt(t3.setEndOf(.day), "2009-02-14 23:59:59 UTC"); -// try expectFmt(t3.setEndOf(.month), "2009-02-28 23:59:59 UTC"); -// try expectFmt(t3.setEndOf(.year), "2009-12-31 23:59:59 UTC"); -// } -// -// test "edge-cases" { -// const jan31 = Date.ymd(2023, 1, 31); -// try expectEqual(jan31.add(.month, 1), Date.ymd(2023, 2, 28)); -// try expectEqual(jan31.add(.month, 2), Date.ymd(2023, 3, 31)); -// try expectEqual(jan31.add(.month, -1), Date.ymd(2022, 12, 31)); -// try expectEqual(jan31.add(.month, -2), Date.ymd(2022, 11, 30)); -// try expectEqual(jan31.add(.year, 1).add(.month, 1), Date.ymd(2024, 2, 29)); -// -// const feb29 = Time.unix(951782400); // 2000-02-29 00:00:00 -// try expectFmt(feb29.setEndOf(.month), "2000-02-29 23:59:59 UTC"); -// try expectFmt(feb29.add(.years, 1), "2001-02-28 00:00:00 UTC"); -// try expectFmt(feb29.add(.years, 4), "2004-02-29 00:00:00 UTC"); -// } -// -// test isLeapYear { -// try testing.expect(!isLeapYear(1999)); -// try testing.expect(isLeapYear(2000)); -// try testing.expect(isLeapYear(2004)); -// } -// -// test daysInMonth { -// try expectEqual(daysInMonth(1999, 2), 28); -// try expectEqual(daysInMonth(2000, 2), 29); -// try expectEqual(daysInMonth(2000, 7), 31); -// try expectEqual(daysInMonth(2000, 8), 31); -// } -// -// test rata_to_date { -// try expectEqual(rata_to_date(RATA_MIN), Date.MIN); -// try expectEqual(rata_to_date(RATA_MAX), Date.MAX); -// -// try expectEqual(rata_to_date(0), .ymd(0, 3, 1)); -// try expectEqual(rata_to_date(RATA_TO_UNIX), .ymd(1970, 1, 1)); -// } -// -// test date_to_rata { -// try expectEqual(date_to_rata(Date.MIN), RATA_MIN); -// try expectEqual(date_to_rata(Date.MAX), RATA_MAX); -// -// try expectEqual(date_to_rata(.ymd(0, 3, 1)), 0); -// try expectEqual(date_to_rata(.ymd(1970, 1, 1)), RATA_TO_UNIX); -// } diff --git a/src/util.zig b/src/util.zig deleted file mode 100644 index a36f8ff..0000000 --- a/src/util.zig +++ /dev/null @@ -1,149 +0,0 @@ -// https://github.com/cztomsik/tokamak -const std = @import("std"); - -pub const whitespace = std.ascii.whitespace; - -pub fn plural(n: i64, singular: []const u8, plural_str: []const u8) []const u8 { - return if (n == 1) singular else plural_str; -} - -pub fn trim(slice: []const u8) []const u8 { - return std.mem.trim(u8, slice, &whitespace); -} - -pub fn truncateEnd(text: []const u8, width: usize) []const u8 { - return if (text.len <= width) text else text[text.len - width ..]; -} - -pub fn truncateStart(text: []const u8, width: usize) []const u8 { - return if (text.len <= width) text else text[0..width]; -} - -pub fn countScalar(comptime T: type, slice: []const T, value: T) usize { - var n: usize = 0; - for (slice) |c| { - if (c == value) n += 1; - } - return n; -} - -pub fn Cmp(comptime T: type) type { - return struct { - // TODO: can we somehow flatten the anytype? - // pub const cmp = if (std.meta.hasMethod(T, "cmp")) T.cmp else std.math.order; - - pub fn cmp(a: T, b: T) std.math.Order { - if (std.meta.hasMethod(T, "cmp")) { - return a.cmp(b); - } - - return std.math.order(a, b); - } - - pub fn lt(a: T, b: T) bool { - return @This().cmp(a, b) == .lt; - } - - pub fn eq(a: T, b: T) bool { - return @This().cmp(a, b) == .eq; - } - - pub fn gt(a: T, b: T) bool { - return @This().cmp(a, b) == .gt; - } - }; -} - -pub fn cmp(a: anytype, b: @TypeOf(a)) std.math.Order { - return Cmp(@TypeOf(a)).cmp(a, b); -} - -pub fn lt(a: anytype, b: @TypeOf(a)) bool { - return Cmp(@TypeOf(a)).lt(a, b); -} - -pub fn eq(a: anytype, b: @TypeOf(a)) bool { - return Cmp(@TypeOf(a)).eq(a, b); -} - -pub fn gt(a: anytype, b: @TypeOf(a)) bool { - return Cmp(@TypeOf(a)).gt(a, b); -} - -// /// --------------------------------------------------------------- -// /// 1️⃣ Detecta se um tipo é std.ArrayList(T) -// /// --------------------------------------------------------------- -// fn isArrayList(comptime T: type) bool { -// // @typeName devolve a string completa, por exemplo: -// // "std.ArrayList(i32)" ou "std.ArrayList([]const u8)" -// const name = @typeName(T); -// // Queremos garantir que o prefixo seja exatamente "std.ArrayList(" -// // (inclui o parêntese de abertura para evitar colisões com nomes -// // semelhantes, como "my_std.ArrayListHelper"). -// // return std.mem.startsWith(u8, name, "std.ArrayList("); -// return std.mem.startsWith(u8, name, "array_list"); -// } -// -// /// --------------------------------------------------------------- -// /// 2️⃣ Obtém o tipo dos elementos armazenados em std.ArrayList(T) -// /// --------------------------------------------------------------- -// fn arrayListElemType(comptime ListT: type) type { -// // Sabemos que ListT tem a forma std.ArrayList(T). O primeiro campo -// // interno do struct é `items: []T`. Vamos ler esse campo. -// const ti = @typeInfo(ListT); -// if (ti != .@"struct") @compileError("Esperado um struct"); -// -// // O campo `items` está na posição 0 da lista de fields: -// const items_field = ti.@"struct".fields[0]; -// // Seu tipo é []T (slice). Em Zig, slices são representados como -// // ponteiros (`*T`) com comprimento separado, mas o tipo declarado -// // aqui é exatamente `[]T`, que corresponde a .pointer com -// // .is_slice = true. -// const slice_type = items_field.type; -// const slice_info = @typeInfo(slice_type); -// if (slice_info != .pointer) @compileError("Campo `items` não é slice"); -// -// // O tipo filho da slice é o T que procuramos. -// return slice_info.pointer.child; -// } -// -// /// --------------------------------------------------------------- -// /// 3️⃣ Função genérica que aceita *qualquer* tipo e age -// /// de acordo se o argumento for um ArrayList ou não. -// /// --------------------------------------------------------------- -// pub fn handle(comptime ArgT: type, arg: ArgT) void { -// if (isArrayList(ArgT)) { -// // É um ArrayList – descobrimos o tipo dos elementos. -// const Elem = arrayListElemType(ArgT); -// -// // Exemplo de uso genérico: imprimir tamanho e tipo dos itens. -// std.debug.print( -// "Recebi um std.ArrayList<{s}> contendo {d} itens.\n", -// .{ @typeName(Elem), arg.items.len }, -// ); -// -// // // Iterar de forma genérica (não sabemos o tipo exato de Elem, -// // // então só fazemos operações que são válidas para *qualquer* tipo). -// // var it = arg.iterator(); -// // while (it.next()) |item| { -// // // `item` tem tipo `Elem`. Se precisar de lógica específica, -// // // pode fazer outra inspeção de tipo aqui. -// // _ = item; // evita warning de variável não usada -// // } -// for(arg.items) |item| { -// _=item; -// } -// } else { -// // Não é um ArrayList – apenas informamos o tipo real. -// std.debug.print( -// "O argumento NÃO é um std.ArrayList (é {s}).\n", -// .{@typeName(ArgT)}, -// ); -// } -// } - -test { - try std.testing.expect(lt(1, 2)); - try std.testing.expect(eq(2, 2)); - try std.testing.expect(gt(2, 1)); -} diff --git a/templates/base.html b/templates/base.html deleted file mode 100644 index bafa6a6..0000000 --- a/templates/base.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - {% block title %}Meu Site{% endblock %} - - - - - {% include "partials/header.html" %} -
    - {% block content %}

    Conteúdo padrão

    {% endblock %} -
    -
    -

    © 2025 - Feito com ❤️ e Zig

    - {% now "d/m/Y H:i:s" %} -
    - - diff --git a/templates/home.html b/templates/home.html deleted file mode 100644 index d38eebe..0000000 --- a/templates/home.html +++ /dev/null @@ -1,28 +0,0 @@ - -{% extends "base.html" %} - -{% block title %}Home - Meu Site{% endblock %} - -{% block menu %} -{{ block.super }} -
  • Blog
  • -{% endblock %} - -{% block content %} -

    Bem-vindo, {{ user.name }}!

    -{% now "d/m/Y H:i:s" %} -

    Você tem {{ user.notifications }} notificações pendentes.

    - -

    Últimos itens:

    - - -{% with msg="Olá mundo!" %} -

    Mensagem temporária: {{ msg|upper }}

    -{% endwith %} -{% endblock %} diff --git a/templates/partials/header.html b/templates/partials/header.html deleted file mode 100644 index b298c25..0000000 --- a/templates/partials/header.html +++ /dev/null @@ -1,12 +0,0 @@ -
    -

    {{ site_title|upper }}

    - -