const std = @import("std"); const Value = @import("context.zig").Value; const std_time = std.time; pub const FilterError = error{ InvalidArgument, OutOfMemory, UnknownFilter, }; const DictEntry = struct { key: []const u8, val: Value, }; pub const FilterFn = fn (alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value; // ==================== FILTROS BUILTIN ==================== fn filter_add(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { _ = alloc; const addend = arg orelse return value; return switch (value) { .int => |i| switch (addend) { .int => |a| Value{ .int = i + a }, .float => |a| Value{ .float = @as(f64, @floatFromInt(i)) + a }, else => value, }, .float => |f| switch (addend) { .int => |a| Value{ .float = f + @as(f64, @floatFromInt(a)) }, .float => |a| Value{ .float = f + a }, else => value, }, else => value, }; } fn filter_addslashes(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { _ = arg; const s = switch (value) { .string => |str| str, else => return value, }; var result = std.ArrayList(u8){}; for (s) |c| { switch (c) { '\'', '"', '\\' => { try result.append(alloc, '\\'); try result.append(alloc, c); }, else => try result.append(alloc, c), } } return Value{ .string = try result.toOwnedSlice(alloc) }; } fn filter_capfirst(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { _ = arg; const s = switch (value) { .string => |str| str, else => return value, }; if (s.len == 0) return value; const capped = capFirst(alloc, s) catch s; return Value{ .string = capped }; } fn filter_center(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { const s = switch (value) { .string => |str| str, else => return value, }; const width = switch (arg orelse Value{ .int = 80 }) { .int => |i| @as(usize, @max(@as(i64, i), 0)), else => 80, }; if (s.len >= width) return value; const padding = width - s.len; const left = padding / 2; const right = padding - left; var result = std.ArrayList(u8){}; for (0..left) |_| try result.append(alloc, ' '); try result.appendSlice(alloc, s); for (0..right) |_| try result.append(alloc, ' '); return Value{ .string = try result.toOwnedSlice(alloc) }; } fn filter_cut(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { const s = switch (value) { .string => |str| str, else => return value, }; const to_cut = switch (arg orelse return value) { .string => |cut| cut, else => return value, }; var result = std.ArrayList(u8){}; var i: usize = 0; while (i < s.len) { if (i + to_cut.len <= s.len and std.mem.eql(u8, s[i .. i + to_cut.len], to_cut)) { i += to_cut.len; } else { try result.append(alloc, s[i]); i += 1; } } return Value{ .string = try result.toOwnedSlice(alloc) }; } // 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; const def = arg orelse return value; return switch (value) { .null => def, .string => |s| if (s.len == 0) def else value, .list => |l| if (l.len == 0) def else value, else => value, }; } fn filter_default_if_none(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { _ = alloc; const def = arg orelse return value; return if (value == .null) def else value; } fn filter_dictsort(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { _ = arg; const d = switch (value) { .dict => |dict| dict, else => return value, }; var entries = std.ArrayList(Value){}; var keys = std.ArrayList([]const u8){}; defer keys.deinit(alloc); var iter = d.iterator(); while (iter.next()) |entry| { const key_copy = try alloc.dupe(u8, entry.key_ptr.*); try keys.append(alloc, key_copy); } std.mem.sort([]const u8, keys.items, {}, struct { pub fn lessThan(ctx: void, a: []const u8, b: []const u8) bool { _ = ctx; return std.mem.order(u8, a, b) == .lt; } }.lessThan); for (keys.items) |key| { const val = d.get(key).?; const pair = std.StringHashMapUnmanaged(Value){}; var pair_map = pair; try pair_map.put(alloc, "key", Value{ .string = key }); try pair_map.put(alloc, "value", val); try entries.append(alloc, Value{ .dict = pair_map }); } return Value{ .list = try entries.toOwnedSlice(alloc) }; } fn filter_dictsortreversed(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { const sorted = try filter_dictsort(alloc, value, arg); const list = sorted.list; const reversed = try alloc.dupe(Value, list); std.mem.reverse(Value, reversed); return Value{ .list = reversed }; } fn filter_divisibleby(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { _ = alloc; const divisor = switch (arg orelse return Value{ .bool = false }) { .int => |i| if (i == 0) return Value{ .bool = false } else i, else => return Value{ .bool = false }, }; const n = switch (value) { .int => |i| i, .float => |f| @as(i64, @intFromFloat(f)), else => return Value{ .bool = false }, }; return Value{ .bool = @rem(n, divisor) == 0 }; } fn filter_escape(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { return filter_force_escape(alloc, value, arg); // alias de force_escape } fn filter_escapejs(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { _ = arg; const s = switch (value) { .string => |str| str, else => return value, }; var result = std.ArrayList(u8){}; for (s) |c| { switch (c) { '\'', '"', '\\' => { try result.append(alloc, '\\'); try result.append(alloc, c); }, '\n' => try result.appendSlice(alloc, "\\n"), '\r' => try result.appendSlice(alloc, "\\r"), '\t' => try result.appendSlice(alloc, "\\t"), '<' => try result.appendSlice(alloc, "\\<"), '>' => try result.appendSlice(alloc, "\\>"), '&' => try result.appendSlice(alloc, "\\&"), '=' => try result.appendSlice(alloc, "\\="), '-' => try result.appendSlice(alloc, "\\-"), ';' => try result.appendSlice(alloc, "\\-"), // Agora sem overlap: excluímos \n (0x0A), \r (0x0D), \t (0x09) 0x00...0x08, 0x0B...0x0C, 0x0E...0x1F, 0x7F => { try std.fmt.format(result.writer(alloc), "\\u{:0>4}", .{c}); }, else => try result.append(alloc, c), } } return Value{ .string = try result.toOwnedSlice(alloc) }; } fn filter_escapeseq(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { _ = arg; const s = switch (value) { .string => |str| str, else => return value, }; var result = std.ArrayList(u8){}; for (s) |c| { switch (c) { '\\' => try result.appendSlice(alloc, "\\\\"), '\n' => try result.appendSlice(alloc, "\\n"), '\r' => try result.appendSlice(alloc, "\\r"), '\t' => try result.appendSlice(alloc, "\\t"), '"' => try result.appendSlice(alloc, "\\\""), '\'' => try result.appendSlice(alloc, "\\'"), else => try result.append(alloc, c), } } return Value{ .string = try result.toOwnedSlice(alloc) }; } fn filter_filesizeformat(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { _ = arg; const bytes = switch (value) { .int => |i| @as(f64, @floatFromInt(i)), .float => |f| f, else => return value, }; const units = [_][]const u8{ "B", "KB", "MB", "GB", "TB" }; var size = bytes; var unit_index: usize = 0; while (size >= 1024 and unit_index < units.len - 1) { size /= 1024; unit_index += 1; } const formatted = if (size < 10) try std.fmt.allocPrint(alloc, "{d:.1} {s}", .{ size, units[unit_index] }) else try std.fmt.allocPrint(alloc, "{d:.0} {s}", .{ size, units[unit_index] }); return Value{ .string = formatted }; } fn filter_first(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { _ = alloc; _ = arg; return switch (value) { .list => |l| if (l.len > 0) l[0] else .null, .string => |s| if (s.len > 0) Value{ .string = s[0..1] } else .null, else => .null, }; } fn filter_floatformat(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { const f = switch (value) { .float => |f| f, .int => |i| @as(f64, @floatFromInt(i)), else => return value, }; const precision = switch (arg orelse Value{ .int = -1 }) { .int => |i| if (i < 0) null else @as(usize, @intCast(i)), else => null, }; var buf: [64]u8 = undefined; const formatted = if (precision) |p| blk: { // Usamos switch para evitar placeholder dinâmico const str = switch (p) { 0 => std.fmt.bufPrint(&buf, "{d:.0}", .{f}) catch unreachable, 1 => std.fmt.bufPrint(&buf, "{d:.1}", .{f}) catch unreachable, 2 => std.fmt.bufPrint(&buf, "{d:.2}", .{f}) catch unreachable, 3 => std.fmt.bufPrint(&buf, "{d:.3}", .{f}) catch unreachable, 4 => std.fmt.bufPrint(&buf, "{d:.4}", .{f}) catch unreachable, 5 => std.fmt.bufPrint(&buf, "{d:.5}", .{f}) catch unreachable, else => std.fmt.bufPrint(&buf, "{d}", .{f}) catch unreachable, }; break :blk try alloc.dupe(u8, str); } else try std.fmt.allocPrint(alloc, "{d}", .{f}); return Value{ .string = formatted }; } fn filter_force_escape(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { _ = arg; const s = switch (value) { .string => |str| str, else => return value, }; var result = std.ArrayList(u8){}; defer result.deinit(alloc); for (s) |c| { switch (c) { '&' => try result.appendSlice(alloc, "&"), '<' => try result.appendSlice(alloc, "<"), '>' => try result.appendSlice(alloc, ">"), '"' => try result.appendSlice(alloc, """), '\'' => try result.appendSlice(alloc, "'"), else => try result.append(alloc, c), } } return Value{ .string = try result.toOwnedSlice(alloc) }; } fn filter_get_digit(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { const n = switch (value) { .int => |i| i, else => return value, }; const pos = switch (arg orelse Value{ .int = 1 }) { .int => |p| p, else => 1, }; if (pos <= 0) return Value{ .int = 0 }; const abs = if (n < 0) -1 * n else n; const str = try std.fmt.allocPrint(alloc, "{d}", .{abs}); if (pos > str.len) return Value{ .int = 0 }; const pos_usize = @as(usize, @intCast(pos)); const npos = str.len - (pos_usize - 1); if (pos <= str.len) return Value{ .int = @intCast(npos) }; return Value{ .int = 0 }; } fn filter_iriencode(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { _ = arg; const s = switch (value) { .string => |str| str, else => return value, }; var result = std.ArrayList(u8){}; for (s) |c| { if (std.ascii.isAlphanumeric(c) or c == '-' or c == '.' or c == '_' or c == '~') { try result.append(alloc, c); } else { try std.fmt.format(result.writer(alloc), "%{X:0>2}", .{c}); } } return Value{ .string = try result.toOwnedSlice(alloc) }; } fn filter_join(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { const sep = switch (arg orelse Value{ .string = "," }) { .string => |s| s, else => ",", }; const list = switch (value) { .list => |l| l, else => return value, }; if (list.len == 0) return Value{ .string = "" }; var result = std.ArrayList(u8){}; defer result.deinit(alloc); for (list, 0..) |item, i| { if (i > 0) try result.appendSlice(alloc, sep); // const str = switch (item) { // .string => |s| s, // .int => |n| blk: { // const printed = try std.fmt.allocPrint(alloc, "{d}", .{n}); // break :blk printed; // }, // .float => |f| blk: { // const printed = try std.fmt.allocPrint(alloc, "{d}", .{f}); // break :blk printed; // }, // .bool => |b| if (b) "true" else "false", // .null => "None", // .list, .dict => blk: { // const printed = try std.fmt.allocPrint(alloc, "{}", .{item}); // break :blk printed; // }, // }; const str = try valueToSafeString(alloc, item); try result.appendSlice(alloc, str); } return Value{ .string = try result.toOwnedSlice(alloc) }; } // TODO: refactor fn filter_json_script(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value { _ = arg; // Simples: converte para string JSON-like e escapa para uso em