update: filters

This commit is contained in:
Lucas F. 2026-01-04 21:38:31 -03:00
parent e5305f85c9
commit d1c63cddda
5 changed files with 2401 additions and 57 deletions

View file

@ -40,7 +40,7 @@ pub const Context = struct {
.bool => Value{ .bool = value },
.int, .comptime_int => Value{ .int = @intCast(value) },
.float, .comptime_float => Value{ .float = @floatCast(value) },
.pointer => Value{ .string = std.fmt.allocPrint(self.allocator(), "{s}", .{value}) catch "" },
.pointer => Value{ .string = try std.fmt.allocPrint(self.allocator(), "{s}", .{value}) },
.@"struct" => blk: {
var dict = std.StringHashMapUnmanaged(Value){};
inline for (std.meta.fields(T)) |field| {
@ -59,13 +59,14 @@ pub const Context = struct {
},
.optional => if (value) |v| try self.toValue(v) else .null,
.null => .null,
// CASO ESPECIAL: o valor é um Value (ex: lista de Value)
.@"union" => if (T == Value) value else @compileError("Unsupported union type: " ++ @typeName(T)),
else => @compileError("Unsupported type: " ++ @typeName(T)),
};
}
pub fn set(self: *Context, key: []const u8, value: anytype) !void {
const v = try self.toValue(value);
const gop = try self.map.getOrPut(self.allocator(), key);
if (!gop.found_existing) {
gop.key_ptr.* = try self.allocator().dupe(u8, key);

View file

@ -18,8 +18,8 @@ test "context set amigável e get com ponto" {
const Person = struct { nome: []const u8, idade: i64 };
const p = Person{ .nome = "Ana", .idade = 25 };
try ctx.set("user", p);
//
// // list
// list
const numeros = [_]i64{ 1, 2, 3 };
try ctx.set("lista", numeros);

1517
src/filters.zig Normal file

File diff suppressed because it is too large Load diff

742
src/filters_test.zig Normal file
View file

@ -0,0 +1,742 @@
const std = @import("std");
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 FilterError = @import("filters.zig").FilterError;
const std_time = std.time;
test "filters upper/lower, capfirst" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("texto", "é ação é nóis, você çãõ");
const v = ctx.get("texto").?;
const lower = builtin_filters.get("lower").?;
const upper = builtin_filters.get("upper").?;
const capfirst = builtin_filters.get("capfirst").?;
const lowered = try lower(ctx.allocator(), v, null);
const uppered = try upper(ctx.allocator(), v, null);
const capped = try capfirst(ctx.allocator(), v, null);
try testing.expectEqualStrings("é ação é nóis, você çãõ", lowered.string);
try testing.expectEqualStrings("É AÇÃO É NÓIS, VOCÊ ÇÃÕ", uppered.string);
try testing.expectEqualStrings("É ação é nóis, você çãõ", capped.string);
}
test "builtin filters - add" {
const alloc = testing.allocator;
const add = builtin_filters.get("add").?;
const v_int = Value{ .int = 10 };
const v_float = Value{ .float = 5.5 };
const arg_int = Value{ .int = 20 };
const arg_float = Value{ .float = 4.5 };
const arg_int_to_float = Value{ .int = 5 };
const result_int = try add(alloc, v_int, arg_int);
const result_float_from_int = try add(alloc, v_int, arg_float);
const result_float_add_int = try add(alloc, v_float, arg_int_to_float);
try testing.expect(result_int.int == 30);
try testing.expectApproxEqAbs(14.5, result_float_from_int.float, 0.0001);
try testing.expectApproxEqAbs(10.5, result_float_add_int.float, 0.0001);
}
test "builtin filters - default and default_if_none" {
const alloc = testing.allocator;
const default_filter = builtin_filters.get("default").?;
const default_if_none = builtin_filters.get("default_if_none").?;
const empty_str = Value{ .string = "" };
const null_val = Value.null;
const non_empty = Value{ .string = "existe" };
const empty_list = Value{ .list = &.{} };
const def = Value{ .string = "padrão" };
// default
try testing.expectEqualStrings("padrão", (try default_filter(alloc, empty_str, def)).string);
try testing.expectEqualStrings("padrão", (try default_filter(alloc, empty_list, def)).string);
try testing.expectEqualStrings("existe", (try default_filter(alloc, non_empty, def)).string);
try testing.expectEqualStrings("padrão", (try default_filter(alloc, null_val, def)).string);
// default_if_none
try testing.expectEqualStrings("padrão", (try default_if_none(alloc, null_val, def)).string);
try testing.expectEqualStrings("", (try default_if_none(alloc, empty_str, def)).string);
try testing.expectEqualStrings("existe", (try default_if_none(alloc, non_empty, def)).string);
}
test "builtin filters - length" {
const alloc = testing.allocator;
const length = builtin_filters.get("length").?;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("abc", "abc");
try ctx.set("empty", "");
const numbers = [_]i64{ 1, 2, 3, 4 };
try ctx.set("list", numbers);
const str_val = ctx.get("abc").?;
const empty_str = ctx.get("empty").?;
const list_val = ctx.get("list").?;
try testing.expect((try length(ctx.allocator(), str_val, null)).int == 3);
try testing.expect((try length(ctx.allocator(), empty_str, null)).int == 0);
try testing.expect((try length(ctx.allocator(), list_val, null)).int == 4);
}
test "builtin filters - length com dict" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("config.chave1", "valor1");
try ctx.set("config.chave2", 42);
var dict = std.StringHashMapUnmanaged(Value){};
try dict.put(ctx.allocator(), "a", Value{ .int = 10 });
try dict.put(ctx.allocator(), "b", Value{ .bool = true });
const v = Value{ .dict = dict };
const length = builtin_filters.get("length").?;
const len = try length(ctx.allocator(), v, null);
try testing.expect(len.int == 2);
}
test "builtin filters - first and last" {
const alloc = testing.allocator;
const first = builtin_filters.get("first").?;
const last = builtin_filters.get("last").?;
var ctx = Context.init(alloc);
defer ctx.deinit();
const list = [_][]const u8{ "um", "dois", "três" };
try ctx.set("list", list);
const str_val = Value{ .string = "abc" };
const empty_str = Value{ .string = "" };
const empty_list = Value{ .list = &.{} };
const list_val = ctx.get("list").?;
// first
try testing.expectEqualStrings("um", (try first(alloc, list_val, null)).string);
try testing.expectEqualStrings("a", (try first(alloc, str_val, null)).string);
try testing.expect((try first(alloc, empty_str, null)) == .null);
try testing.expect((try first(alloc, empty_list, null)) == .null);
// last
try testing.expectEqualStrings("três", (try last(alloc, list_val, null)).string);
try testing.expectEqualStrings("c", (try last(alloc, str_val, null)).string);
try testing.expect((try last(alloc, empty_str, null)) == .null);
try testing.expect((try last(alloc, empty_list, null)) == .null);
}
test "builtin filters - join" {
const alloc = testing.allocator;
const join = builtin_filters.get("join").?;
var ctx = Context.init(alloc);
defer ctx.deinit();
const list = [_][]const u8{ "a", "b", "c" };
try ctx.set("list", list);
const list_val = ctx.get("list").?;
const sep_dash = Value{ .string = "-" };
const mixed = [_]Value{ Value{ .string = "x" }, Value{ .int = 42 }, Value{ .bool = true } };
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);
try testing.expectEqualStrings("a,b,c", default_join.string);
try testing.expectEqualStrings("a-b-c", custom_join.string);
try testing.expectEqualStrings("x,42,true", mixed_join.string);
}
test "builtin filters - yesno" {
const alloc = testing.allocator;
const yesno = builtin_filters.get("yesno").?;
var ctx = Context.init(alloc);
defer ctx.deinit();
const cases = [_]struct { val: Value, arg: []const u8, expected: []const u8 }{
.{ .val = Value{ .bool = true }, .arg = "sim,não,talvez", .expected = "sim" },
.{ .val = Value{ .bool = false }, .arg = "sim,não,talvez", .expected = "não" },
.{ .val = Value.null, .arg = "sim,não,talvez", .expected = "talvez" },
.{ .val = Value{ .string = "" }, .arg = "sim,não", .expected = "não" },
.{ .val = Value{ .string = "oi" }, .arg = "sim,não", .expected = "sim" },
};
for (cases) |case| {
const arg_val = Value{ .string = case.arg };
const result = try yesno(ctx.allocator(), case.val, arg_val);
try testing.expectEqualStrings(case.expected, result.string);
}
}
test "builtin filters - truncatechars and truncatechars_html" {
const alloc = testing.allocator;
const truncatechars = builtin_filters.get("truncatechars").?;
const truncatechars_html = builtin_filters.get("truncatechars_html").?;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("longo", "abcdefghijkl"); // 12 chars
try ctx.set("empty", "");
const v = ctx.map.get("longo").?;
const tc10 = try truncatechars(ctx.allocator(), v, Value{ .int = 10 });
const tc5 = try truncatechars(ctx.allocator(), v, Value{ .int = 5 });
const tc3 = try truncatechars(ctx.allocator(), v, Value{ .int = 3 });
try testing.expectEqualStrings("abcdefg...", tc10.string); // 7 + "..."
try testing.expectEqualStrings("ab...", tc5.string); // 2 + "..."
try testing.expectEqualStrings("", tc3.string); // n < 3 ""
// truncatechars_html similar por enquanto
const tc_html10 = try truncatechars_html(ctx.allocator(), v, Value{ .int = 10 });
try testing.expectEqualStrings("abcdefg...", tc_html10.string);
}
test "builtin filters - truncatewords" {
const alloc = testing.allocator;
const truncatewords = builtin_filters.get("truncatewords").?;
const truncatewords_html = builtin_filters.get("truncatewords_html").?;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("longo", "Este é um texto muito longo para ser truncado");
const v = ctx.get("longo").?;
const tw5 = try truncatewords(ctx.allocator(), v, Value{ .int = 5 });
const tw3 = try truncatewords(ctx.allocator(), v, Value{ .int = 3 });
try testing.expectEqualStrings("Este é um texto muito...", tw5.string);
try testing.expectEqualStrings("Este é um...", tw3.string);
//
// truncatechars_html similar por enquanto
const tc_html10 = try truncatewords_html(ctx.allocator(), v, Value{ .int = 6 });
try testing.expectEqualStrings("Este é um texto muito longo...", tc_html10.string);
}
test "builtin filters - slice" {
const alloc = testing.allocator;
const slice = builtin_filters.get("slice").?;
var ctx = Context.init(alloc);
defer ctx.deinit();
const list = try alloc.alloc(Value, 5);
defer alloc.free(list);
for (list, 0..) |*item, i| item.* = Value{ .int = @intCast(i + 1) };
try ctx.set("nums", Value{ .list = list });
try ctx.set("texto", "abcdef");
const v_list = ctx.get("nums").?;
const v_str = ctx.get("texto").?;
const slice2 = try slice(ctx.allocator(), v_list, Value{ .string = ":2" });
const slice13 = try slice(ctx.allocator(), v_str, Value{ .string = "1:3" });
try testing.expect(slice2.list.len == 2);
try testing.expect(slice2.list[0].int == 1);
try testing.expect(slice2.list[1].int == 2);
try testing.expectEqualStrings("bc", slice13.string);
}
test "builtin filters - safe and force_escape" {
const alloc = testing.allocator;
const safe = builtin_filters.get("safe").?;
const force_escape = builtin_filters.get("force_escape").?;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("html", "<script>alert('xss')</script>");
const v = ctx.get("html").?;
const escaped = try force_escape(ctx.allocator(), v, null);
try testing.expectEqualStrings("&lt;script&gt;alert(&#x27;xss&#x27;)&lt;/script&gt;", escaped.string);
// safe retorna o mesmo (o renderer vai tratar como seguro)
const safed = try safe(ctx.allocator(), v, null);
try testing.expectEqualStrings("<script>alert('xss')</script>", safed.string);
}
test "builtin filters - linebreaksbr and linebreaks" {
const alloc = testing.allocator;
const linebreaksbr = builtin_filters.get("linebreaksbr").?;
const linebreaks = builtin_filters.get("linebreaks").?;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("texto", "Linha 1\nLinha 2\n\nLinha 3");
const v = ctx.get("texto").?;
const br = try linebreaksbr(ctx.allocator(), v, null);
const p = try linebreaks(ctx.allocator(), v, null);
try testing.expect(std.mem.indexOf(u8, br.string, "<br>") != null);
try testing.expect(std.mem.indexOf(u8, p.string, "<p>") != null);
}
test "builtin filters - escape and force_escape" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("xss", "<script>alert('xss')</script>");
const v = ctx.get("xss").?;
const escape = builtin_filters.get("escape").?;
const force_escape = builtin_filters.get("force_escape").?;
const escaped = try escape(ctx.allocator(), v, null);
const forced = try force_escape(ctx.allocator(), v, null);
const expected = "&lt;script&gt;alert(&#x27;xss&#x27;)&lt;/script&gt;";
try testing.expectEqualStrings(expected, escaped.string);
try testing.expectEqualStrings(expected, forced.string);
}
test "builtin filters - striptags" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("html", "<p>Olá <b>mundo</b> legal!</p>");
const v = ctx.get("html").?;
const striptags = builtin_filters.get("striptags").?;
const stripped = try striptags(ctx.allocator(), v, null);
try testing.expectEqualStrings("Olá mundo legal!", stripped.string);
}
test "builtin filters - slugify" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("titulo", "Olá Mundo Legal!!! 2026 ações");
const v = ctx.get("titulo").?;
const slugify = builtin_filters.get("slugify").?;
const slugged = try slugify(ctx.allocator(), v, null);
try testing.expectEqualStrings("ola-mundo-legal-2026-acoes", slugged.string);
}
test "builtin filters - floatformat" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("pi", 3.14159);
try ctx.set("e", 2.71828);
try ctx.set("inteiro", 42);
const v_pi = ctx.get("pi").?;
const v_e = ctx.get("e").?;
const v_int = ctx.get("inteiro").?;
const floatformat = builtin_filters.get("floatformat").?;
const default_pi = try floatformat(ctx.allocator(), v_pi, null);
const pi_2 = try floatformat(ctx.allocator(), v_pi, Value{ .int = 2 });
const pi_0 = try floatformat(ctx.allocator(), v_pi, Value{ .int = 0 });
const int_1 = try floatformat(ctx.allocator(), v_int, Value{ .int = 1 });
const ve = try floatformat(ctx.allocator(), v_e, Value{ .int = 2 });
try testing.expectEqualStrings("3.14159", default_pi.string);
try testing.expectEqualStrings("3.14", pi_2.string);
try testing.expectEqualStrings("3", pi_0.string);
try testing.expectEqualStrings("42.0", int_1.string);
try testing.expectEqualStrings("2.72", ve.string);
}
test "builtin filters - stringformat" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("nome", "Lucas");
try ctx.set("idade", 30);
const v_nome = ctx.get("nome").?;
const v_idade = ctx.get("idade").?;
const stringformat = builtin_filters.get("stringformat").?;
const fmt_nome = try stringformat(ctx.allocator(), v_nome, Value{ .string = "Olá %s!" });
const fmt_idade = try stringformat(ctx.allocator(), v_idade, Value{ .string = "Idade: %d anos" });
try testing.expectEqualStrings("Olá Lucas!", fmt_nome.string);
try testing.expectEqualStrings("Idade: 30 anos", fmt_idade.string);
}
test "builtin filters - cut" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("texto", "Olá mundo cruel, mundo!");
const v = ctx.get("texto").?;
const cut = builtin_filters.get("cut").?;
const cut_mundo = try cut(ctx.allocator(), v, Value{ .string = "mundo" });
try testing.expectEqualStrings("Olá cruel, !", cut_mundo.string);
}
test "builtin filters - title" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("texto", "olá mundo cruel");
const v = ctx.get("texto").?;
const title = builtin_filters.get("title").?;
const titled = try title(ctx.allocator(), v, null);
try testing.expectEqualStrings("Olá Mundo Cruel", titled.string);
}
test "builtin filters - wordcount" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("frase", "Este é um texto com exatamente sete palavras");
const v = ctx.get("frase").?;
const wordcount = builtin_filters.get("wordcount").?;
const count = try wordcount(ctx.allocator(), v, null);
try testing.expect(count.int == 7);
}
test "builtin filters - urlencode" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("query", "busca=zig lang&pagina=1");
const v = ctx.get("query").?;
const urlencode = builtin_filters.get("urlencode").?;
const encoded = try urlencode(ctx.allocator(), v, null);
try testing.expectEqualStrings("busca%3Dzig%20lang%26pagina%3D1", encoded.string);
}
test "builtin filters - pluralize" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("voto", 1);
try ctx.set("votos", 5);
// try ctx.set("zero", 0);
const v1 = ctx.get("voto").?;
const v5 = ctx.get("votos").?;
// const v0 = ctx.get("zero").?;
const pluralize = builtin_filters.get("pluralize").?;
const s1 = try pluralize(ctx.allocator(), v1, null);
const s5 = try pluralize(ctx.allocator(), v5, null);
const custom = try pluralize(ctx.allocator(), v5, Value{ .string = "es" });
// const zero = try pluralize(ctx.allocator(), v0, null);
try testing.expectEqualStrings("", s1.string); // 1 singular (vazio)
try testing.expectEqualStrings("s", s5.string); // 5 plural
try testing.expectEqualStrings("es", custom.string);
// try testing.expectEqualStrings("", zero.string);
}
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");
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" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
var dict = std.StringHashMapUnmanaged(Value){};
try dict.put(ctx.allocator(), "b", Value{ .int = 2 });
try dict.put(ctx.allocator(), "a", Value{ .int = 1 });
try dict.put(ctx.allocator(), "c", Value{ .int = 3 });
const v = Value{ .dict = dict };
const dictsort = builtin_filters.get("dictsort").?;
const dictsortreversed = builtin_filters.get("dictsortreversed").?;
const sorted = try dictsort(ctx.allocator(), v, null);
const reversed = try dictsortreversed(ctx.allocator(), v, null);
const sorted_list = sorted.list;
try testing.expect(sorted_list.len == 3);
try testing.expectEqualStrings("a", sorted_list[0].dict.get("key").?.string);
try testing.expectEqualStrings("b", sorted_list[1].dict.get("key").?.string);
try testing.expectEqualStrings("c", sorted_list[2].dict.get("key").?.string);
const reversed_list = reversed.list;
try testing.expectEqualStrings("c", reversed_list[0].dict.get("key").?.string);
try testing.expectEqualStrings("b", reversed_list[1].dict.get("key").?.string);
try testing.expectEqualStrings("a", reversed_list[2].dict.get("key").?.string);
}
test "builtin filters - divisibleby, escapejs, filesizeformat, get_digit, json_script" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
const divisibleby = builtin_filters.get("divisibleby").?;
const escapejs = builtin_filters.get("escapejs").?;
const filesizeformat = builtin_filters.get("filesizeformat").?;
const get_digit = builtin_filters.get("get_digit").?;
const json_script = builtin_filters.get("json_script").?;
const div = try divisibleby(ctx.allocator(), Value{ .int = 10 }, Value{ .int = 5 });
const div_2 = try divisibleby(ctx.allocator(), Value{ .int = 10 }, Value{ .int = 3 });
try testing.expect(div.bool == true);
try testing.expect(div_2.bool == false);
const js = try escapejs(ctx.allocator(), Value{ .string = "<script>alert('oi')</script>" }, null);
try testing.expectEqualStrings("\\<script\\>alert(\\'oi\\')\\</script\\>", js.string);
const size = try filesizeformat(ctx.allocator(), Value{ .int = 1500000 }, null);
try testing.expect(std.mem.indexOf(u8, size.string, "MB") != null);
const digit = try get_digit(ctx.allocator(), Value{ .int = 12345 }, Value{ .int = 2 });
try testing.expect(digit.int == 4);
const digit_3 = try get_digit(ctx.allocator(), Value{ .int = 123456789 }, Value{ .int = 11 });
try testing.expect(digit_3.int == 0);
const digit_2 = try get_digit(ctx.allocator(), Value{ .string = "baz" }, Value{ .int = 2 });
try testing.expect(std.mem.eql(u8, digit_2.string, "baz"));
const json = try json_script(ctx.allocator(), Value{ .string = "<b>negrito</b>" }, null);
try testing.expect(std.mem.indexOf(u8, json.string, "\\u003C") != null);
}
test "builtin filters - escapeseq, iriencode, linenumbers" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("texto", "Hello \"world\"\\n");
const v = ctx.get("texto").?;
const escapeseq = builtin_filters.get("escapeseq").?;
const iriencode = builtin_filters.get("iriencode").?;
const linenumbers = builtin_filters.get("linenumbers").?;
const escaped = try escapeseq(ctx.allocator(), v, null);
const encoded = try iriencode(ctx.allocator(), v, null);
const numbered = try linenumbers(ctx.allocator(), Value{ .string = "linha1\nlinha2\nlinha3" }, null);
try testing.expect(std.mem.indexOf(u8, escaped.string, "\\\"") != null);
try testing.expect(std.mem.indexOf(u8, encoded.string, "%20") != null);
try testing.expect(std.mem.indexOf(u8, numbered.string, "1. linha1") != null);
}
test "builtin filters - ljust, rjust, center" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("texto", "zig");
const v = ctx.get("texto").?;
const ljust = builtin_filters.get("ljust").?;
const rjust = builtin_filters.get("rjust").?;
const center = builtin_filters.get("center").?;
const left = try ljust(ctx.allocator(), v, Value{ .int = 10 });
const right = try rjust(ctx.allocator(), v, Value{ .int = 10 });
const centered = try center(ctx.allocator(), v, Value{ .int = 10 });
try testing.expectEqualStrings("zig ", left.string);
try testing.expectEqualStrings(" zig", right.string);
try testing.expectEqualStrings(" zig ", centered.string);
}
test "builtin filters - make_list, phone2numeric, pprint" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("palavra", "zig");
try ctx.set("telefone", "1-800-FLOWERS");
const v_palavra = ctx.get("palavra").?;
const v_telefone = ctx.get("telefone").?;
const make_list = builtin_filters.get("make_list").?;
const phone2numeric = builtin_filters.get("phone2numeric").?;
const pprint = builtin_filters.get("pprint").?;
const list = try make_list(ctx.allocator(), v_palavra, null);
const numeric = try phone2numeric(ctx.allocator(), v_telefone, null);
const printed = try pprint(ctx.allocator(), Value{ .int = 42 }, null);
try testing.expect(list.list.len == 3);
try testing.expectEqualStrings("z", list.list[0].string);
try testing.expect(std.mem.indexOf(u8, numeric.string, "3569377") != null);
try testing.expect(std.mem.indexOf(u8, printed.string, "42") != null);
}
test "builtin filters - random, safeseq" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
var items = try alloc.alloc(Value, 3);
defer alloc.free(items);
items[0] = Value{ .string = "a" };
items[1] = Value{ .string = "b" };
items[2] = Value{ .string = "c" };
const v = Value{ .list = items };
const random = builtin_filters.get("random").?;
const safeseq = builtin_filters.get("safeseq").?;
const rand = try random(ctx.allocator(), v, null);
const safe = try safeseq(ctx.allocator(), v, null);
try testing.expect(rand == .string);
try testing.expect(safe == .list);
}
// 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" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("url", "Visite https://ziglang.org");
const v_url = ctx.get("url").?;
const urlize = builtin_filters.get("urlize").?;
const urlizetrunc = builtin_filters.get("urlizetrunc").?;
const wordwrap = builtin_filters.get("wordwrap").?;
const u = try urlize(ctx.allocator(), v_url, null);
try testing.expect(std.mem.indexOf(u8, u.string, "<a href=") != null);
const ut = try urlizetrunc(ctx.allocator(), v_url, Value{ .int = 10 });
try testing.expect(std.mem.indexOf(u8, ut.string, "...") != null);
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" {
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);
const v = ctx.get("lista").?;
const unordered_list = builtin_filters.get("unordered_list").?;
const ul = try unordered_list(ctx.allocator(), v, null);
const expected =
\\<ul>
\\<li>item1</li>
\\<li>item2</li>
\\</ul>
;
std.debug.print("lista gerada: {any}\n", .{ul.string});
try testing.expectEqualStrings(expected, ul.string);
}

190
todo.md
View file

@ -31,63 +31,63 @@
# Filters
- [ ] add
- [ ] addslashes
- [ ] capfirst
- [ ] center
- [ ] cut
- [x] add
- [x] addslashes
- [x] capfirst
- [x] center
- [x] cut
- [ ] date
- [ ] default
- [ ] default_if_none
- [ ] dictsort
- [ ] dictsortreversed
- [ ] divisibleby
- [ ] escape
- [ ] escapejs
- [ ] escapeseq
- [ ] filesizeformat
- [ ] first
- [ ] floatformat
- [ ] force_escape
- [ ] get_digit
- [ ] iriencode
- [ ] join
- [ ] json_script
- [ ] last
- [ ] length
- [ ] linebreaks
- [ ] linebreaksbr
- [ ] linenumbers
- [ ] ljust
- [ ] lower
- [ ] make_list
- [ ] phone2numeric
- [ ] pluralize
- [ ] pprint
- [ ] random
- [ ] rjust
- [ ] safe
- [ ] safeseq
- [ ] slice
- [ ] slugify
- [ ] stringformat
- [ ] striptags
- [x] default
- [x] default_if_none
- [x] dictsort
- [x] dictsortreversed
- [x] divisibleby
- [x] escape
- [x] escapejs
- [x] escapeseq
- [x] filesizeformat
- [x] first
- [x] floatformat
- [x] force_escape
- [x] get_digit
- [x] iriencode
- [x] join
- [x] json_script
- [x] last
- [x] length
- [x] linebreaks
- [x] linebreaksbr
- [x] linenumbers
- [x] ljust
- [x] lower
- [x] make_list
- [x] phone2numeric
- [x] pluralize
- [x] pprint
- [x] random
- [x] rjust
- [x] safe
- [x] safeseq
- [x] slice
- [x] slugify
- [x] stringformat
- [x] striptags
- [ ] time
- [ ] timesince
- [ ] timeuntil
- [ ] title
- [ ] truncatechars
- [ ] truncatechars_html
- [ ] truncatewords
- [ ] truncatewords_html
- [ ] unordered_list
- [ ] upper
- [ ] urlencode
- [ ] urlize
- [ ] urlizetrunc
- [ ] wordcount
- [ ] wordwrap
- [ ] yesno
- [x] title
- [x] truncatechars
- [-] truncatechars_html
- [x] truncatewords
- [-] truncatewords_html
- [x] unordered_list
- [x] upper
- [x] urlencode
- [x] urlize
- [x] urlizetrunc
- [x] wordcount
- [x] wordwrap
- [x] yesno
___
@ -131,3 +131,87 @@ ___
- Tudo testado
4 -Filtros
na verdade o pushScope não tava não, a versão final que vc me mandou depois que se inspirou no tokamak foi essa:
```
const std = @import("std");
pub const Value = union(enum) {
null,
bool: bool,
int: i64,
float: f64,
string: []const u8,
list: []const Value,
dict: std.StringHashMapUnmanaged(Value),
pub fn deinit(self: Value) void {
_ = self; // nada — a arena libera tudo
}
};
pub const Context = struct {
arena: std.heap.ArenaAllocator,
map: std.StringHashMapUnmanaged(Value),
pub fn init(child_allocator: std.mem.Allocator) Context {
const arena = std.heap.ArenaAllocator.init(child_allocator);
return .{
.arena = arena,
.map = .{},
};
}
pub fn allocator(self: *Context) std.mem.Allocator {
return self.arena.allocator();
}
pub fn deinit(self: *Context) void {
self.arena.deinit();
}
pub fn set(self: *Context, key: []const u8, value: Value) !void {
const gop = try self.map.getOrPut(self.allocator(), key);
if (gop.found_existing) {
// opcional: deinit value antigo se necessário
// mas como arena libera tudo, não precisa
} else {
gop.key_ptr.* = try self.allocator().dupe(u8, key);
}
gop.value_ptr.* = value;
}
pub fn get(self: *const Context, key: []const u8) ?Value {
return self.map.get(key);
}
};
```
ao colocar isso ordered_dict: []struct { key: []const u8, val: Value }, vc ferrou em todos os switches de Value que agora tem que prever o que fazer com orderdered_dict
Conclusão sobre o getA melhor abordagem, considerando Zig, é:
Manter o get(comptime T: type, key) — é seguro, performático, e em uso real fica natural
```zig
const idade: i64 = ctx.get("idade") orelse 0;
const nome: []const u8 = ctx.get("nome") orelse "Desconhecido";
```
É verboso no teste, mas em código real é claro e seguro.Ou podemos fazer uma versão com optional:
```zig
pub fn get(self: *const Context, comptime T: type, key: []const u8) ?T {
const value = self.map.get(key) orelse return null;
return value.as(T) catch null;
}
```
Aí fica:
```zig
const idade = ctx.get(i64, "idade") orelse 0;
```