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 = "";
- 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[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 = "";
+ 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[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 =
- \\
- ;
-
- const main =
- \\{% include "header.html" %}
- \\
- \\ Conteúdo principal
- \\ Olá {{ nome }}!
- \\
- ;
-
- const expected =
- \\
- \\
- \\ 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 = "";
- 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, "") != 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 =
+// \\
+// ;
+//
+// const main =
+// \\{% include "header.html" %}
+// \\
+// \\ Conteúdo principal
+// \\ Olá {{ nome }}!
+// \\
+// ;
+//
+// const expected =
+// \\
+// \\
+// \\ 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 = "";
+// 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, "") != 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
- \\
- \\ {{ 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 %}
-
-
-
-
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:
-
- {% for item in itens %}
- - {{ forloop.counter }}: {{ item }}
- {% empty %}
- - Nenhum item encontrado.
- {% endfor %}
-
-
-{% 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 }}
-
-