1518 lines
46 KiB
Zig
1518 lines
46 KiB
Zig
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 <script>
|
|
const str = switch (value) {
|
|
.string => |s| s,
|
|
else => try std.fmt.allocPrint(alloc, "{}", .{value}),
|
|
};
|
|
errdefer if (@typeInfo(@TypeOf(str)) == .pointer) alloc.free(str);
|
|
|
|
var result = std.ArrayList(u8){};
|
|
|
|
for (str) |c| {
|
|
switch (c) {
|
|
'<' => try result.appendSlice(alloc, "\\u003C"),
|
|
'>' => try result.appendSlice(alloc, "\\u003E"),
|
|
'&' => try result.appendSlice(alloc, "\\u0026"),
|
|
else => try result.append(alloc, c),
|
|
}
|
|
}
|
|
|
|
return Value{ .string = try result.toOwnedSlice(alloc) };
|
|
}
|
|
|
|
fn filter_last(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
_ = alloc;
|
|
_ = arg;
|
|
return switch (value) {
|
|
.list => |l| if (l.len > 0) l[l.len - 1] else .null,
|
|
.string => |s| if (s.len > 0) Value{ .string = s[s.len - 1 ..] } else .null,
|
|
else => .null,
|
|
};
|
|
}
|
|
|
|
fn filter_length(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
_ = alloc;
|
|
_ = arg;
|
|
return Value{ .int = switch (value) {
|
|
.string => |s| @intCast(s.len),
|
|
.list => |l| @intCast(l.len),
|
|
.dict => |d| @intCast(d.count()),
|
|
else => 0,
|
|
} };
|
|
}
|
|
|
|
fn filter_linebreaks(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);
|
|
|
|
var lines = std.mem.splitScalar(u8, s, '\n');
|
|
var empty_line_count: usize = 0;
|
|
|
|
while (lines.next()) |line| {
|
|
const trimmed = std.mem.trim(u8, line, " \t\r");
|
|
|
|
if (trimmed.len == 0) {
|
|
empty_line_count += 1;
|
|
if (empty_line_count == 1) {
|
|
try result.appendSlice(alloc, "<p>");
|
|
} else if (empty_line_count == 2) {
|
|
try result.appendSlice(alloc, "</p><p>");
|
|
empty_line_count = 1;
|
|
}
|
|
} else {
|
|
if (empty_line_count >= 1) {
|
|
try result.appendSlice(alloc, "<p>");
|
|
empty_line_count = 0;
|
|
}
|
|
try result.appendSlice(alloc, line);
|
|
try result.appendSlice(alloc, "<br>");
|
|
}
|
|
}
|
|
|
|
if (empty_line_count >= 1) try result.appendSlice(alloc, "</p>");
|
|
|
|
return Value{ .string = try result.toOwnedSlice(alloc) };
|
|
}
|
|
|
|
fn filter_linebreaksbr(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| {
|
|
if (c == '\n') {
|
|
try result.appendSlice(alloc, "<br>");
|
|
} else {
|
|
try result.append(alloc, c);
|
|
}
|
|
}
|
|
|
|
return Value{ .string = try result.toOwnedSlice(alloc) };
|
|
}
|
|
|
|
fn filter_linenumbers(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){};
|
|
|
|
var lines = std.mem.splitScalar(u8, s, '\n');
|
|
var line_num: usize = 1;
|
|
|
|
while (lines.next()) |line| {
|
|
try std.fmt.format(result.writer(alloc), "{d}. ", .{line_num});
|
|
try result.appendSlice(alloc, line);
|
|
try result.append(alloc, '\n');
|
|
line_num += 1;
|
|
}
|
|
|
|
// Remove última \n se existir
|
|
if (result.items.len > 0 and result.items[result.items.len - 1] == '\n') {
|
|
_ = result.pop();
|
|
}
|
|
|
|
return Value{ .string = try result.toOwnedSlice(alloc) };
|
|
}
|
|
|
|
fn filter_ljust(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
const s = switch (value) {
|
|
.string => |str| str,
|
|
else => return value,
|
|
};
|
|
|
|
const width = if (arg) |a| switch (a) {
|
|
.int => |i| @as(usize, @max(i, 0)),
|
|
else => s.len,
|
|
} else s.len;
|
|
|
|
if (s.len >= width) return value;
|
|
|
|
var result = std.ArrayList(u8){};
|
|
try result.appendSlice(alloc, s);
|
|
for (s.len..width) |_| try result.append(alloc, ' ');
|
|
|
|
return Value{ .string = try result.toOwnedSlice(alloc) };
|
|
}
|
|
|
|
fn filter_lower(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
_ = arg;
|
|
const s = switch (value) {
|
|
.string => |str| str,
|
|
else => return value,
|
|
};
|
|
|
|
const lowered = toLower(alloc, s) catch s;
|
|
return Value{ .string = lowered };
|
|
}
|
|
|
|
fn filter_make_list(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
_ = arg;
|
|
const s = switch (value) {
|
|
.string => |str| str,
|
|
else => return value,
|
|
};
|
|
|
|
var list = std.ArrayList(Value){};
|
|
|
|
for (s) |c| {
|
|
var char_str: [4]u8 = undefined;
|
|
const len = std.unicode.utf8Encode(c, &char_str) catch 1;
|
|
const char_slice = char_str[0..len];
|
|
const copied = try alloc.dupe(u8, char_slice);
|
|
try list.append(alloc, Value{ .string = copied });
|
|
}
|
|
|
|
return Value{ .list = try list.toOwnedSlice(alloc) };
|
|
}
|
|
|
|
fn filter_phone2numeric(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| {
|
|
const lower = std.ascii.toLower(c);
|
|
const digit = switch (lower) {
|
|
'a', 'b', 'c' => '2',
|
|
'd', 'e', 'f' => '3',
|
|
'g', 'h', 'i' => '4',
|
|
'j', 'k', 'l' => '5',
|
|
'm', 'n', 'o' => '6',
|
|
'p', 'q', 'r', 's' => '7',
|
|
't', 'u', 'v' => '8',
|
|
'w', 'x', 'y', 'z' => '9',
|
|
else => c,
|
|
};
|
|
try result.append(alloc, digit);
|
|
}
|
|
|
|
return Value{ .string = try result.toOwnedSlice(alloc) };
|
|
}
|
|
|
|
//
|
|
// TODO: refactor
|
|
fn filter_pluralize(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
var singular: []const u8 = "";
|
|
var plural: []const u8 = "s";
|
|
|
|
if (arg) |a| {
|
|
switch (a) {
|
|
.string => |str| {
|
|
if (std.mem.indexOf(u8, str, ",")) |pos| {
|
|
singular = str[0..pos];
|
|
plural = str[pos + 1 ..];
|
|
} else {
|
|
plural = str;
|
|
}
|
|
},
|
|
else => {}, // ignora outros tipos
|
|
}
|
|
}
|
|
|
|
const count: i64 = switch (value) {
|
|
.int => |i| i,
|
|
.float => |f| @as(i64, @intFromFloat(f)),
|
|
.string => |s| if (s.len == 0) 0 else 1,
|
|
.list => |l| @as(i64, @intCast(l.len)),
|
|
.dict => |d| @as(i64, @intCast(d.count())),
|
|
.null => 0,
|
|
else => 1,
|
|
};
|
|
|
|
const ending = if (count == 1 or count == -1) singular else plural;
|
|
|
|
const copied = try alloc.dupe(u8, ending);
|
|
return Value{ .string = copied };
|
|
}
|
|
|
|
fn filter_pprint(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
_ = arg;
|
|
const str = try valueToSafeString(alloc, value);
|
|
return Value{ .string = str };
|
|
}
|
|
|
|
fn filter_random(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
_ = alloc;
|
|
_ = arg;
|
|
const list = switch (value) {
|
|
.list => |l| l,
|
|
else => return value,
|
|
};
|
|
|
|
if (list.len == 0) return Value.null;
|
|
|
|
var prng = std.Random.DefaultPrng.init(@intCast(std.time.milliTimestamp()));
|
|
const rand = prng.random();
|
|
|
|
const index = rand.uintLessThan(usize, list.len);
|
|
return list[index];
|
|
}
|
|
|
|
fn filter_rjust(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
const s = switch (value) {
|
|
.string => |str| str,
|
|
else => return value,
|
|
};
|
|
|
|
const width = if (arg) |a| switch (a) {
|
|
.int => |i| @as(usize, @max(i, 0)),
|
|
else => s.len,
|
|
} else s.len;
|
|
|
|
if (s.len >= width) return value;
|
|
|
|
var result = std.ArrayList(u8){};
|
|
for (s.len..width) |_| try result.append(alloc, ' ');
|
|
try result.appendSlice(alloc, s);
|
|
|
|
return Value{ .string = try result.toOwnedSlice(alloc) };
|
|
}
|
|
|
|
fn filter_safe(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
_ = alloc;
|
|
_ = arg;
|
|
return value; // safe: o renderer não escapa
|
|
}
|
|
|
|
fn filter_safeseq(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
_ = alloc;
|
|
_ = arg;
|
|
const list = switch (value) {
|
|
.list => |l| l,
|
|
else => return value,
|
|
};
|
|
|
|
// safeseq marca a lista como segura (não escapa itens)
|
|
// Por enquanto, retorna a mesma lista (o renderer vai tratar como safe)
|
|
return Value{ .list = list };
|
|
}
|
|
|
|
fn filter_slice(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
const slice_str = switch (arg orelse return value) {
|
|
.string => |s| s,
|
|
else => return value,
|
|
};
|
|
|
|
var start: ?usize = null;
|
|
var end: ?usize = null;
|
|
|
|
if (std.mem.indexOf(u8, slice_str, ":")) |colon| {
|
|
const before = slice_str[0..colon];
|
|
const after = slice_str[colon + 1 ..];
|
|
|
|
if (before.len > 0) {
|
|
start = std.fmt.parseInt(usize, before, 10) catch 0;
|
|
}
|
|
if (after.len > 0) {
|
|
end = std.fmt.parseInt(usize, after, 10) catch null;
|
|
}
|
|
} else {
|
|
return value;
|
|
}
|
|
|
|
return switch (value) {
|
|
.string => |s| blk: {
|
|
const effective_start = start orelse 0;
|
|
const effective_end = end orelse s.len;
|
|
if (effective_start >= s.len) return Value{ .string = "" };
|
|
const len = @min(effective_end - effective_start, s.len - effective_start);
|
|
const sliced = try alloc.dupe(u8, s[effective_start .. effective_start + len]);
|
|
break :blk Value{ .string = sliced };
|
|
},
|
|
.list => |l| blk: {
|
|
const effective_start = start orelse 0;
|
|
const effective_end = end orelse l.len;
|
|
if (effective_start >= l.len) return Value{ .list = &.{} };
|
|
const len = @min(effective_end - effective_start, l.len - effective_start);
|
|
const sliced = try alloc.dupe(Value, l[effective_start .. effective_start + len]);
|
|
break :blk Value{ .list = sliced };
|
|
},
|
|
else => value,
|
|
};
|
|
}
|
|
|
|
fn filter_slugify(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){};
|
|
|
|
var last_was_hyphen = false;
|
|
var i: usize = 0;
|
|
|
|
while (i < s.len) {
|
|
var replaced: u8 = s[i];
|
|
|
|
// Se for caractere acentuado de 2 bytes (UTF-8 Latin-1: 0xC3 xx)
|
|
if (i + 1 < s.len and s[i] == 0xC3) {
|
|
const next = s[i + 1];
|
|
replaced = switch (next) {
|
|
// Minúsculos
|
|
0xA1, 0xA0, 0xA2, 0xA3, 0xA4 => 'a', // á à â ã ä
|
|
0xA9, 0xA8, 0xAA, 0xAB => 'e', // é è ê ë
|
|
0xAD, 0xAC, 0xAE, 0xAF => 'i', // í ì î ï
|
|
0xB3, 0xB2, 0xB4, 0xB5, 0xB6 => 'o', // ó ò ô õ ö
|
|
0xBA, 0xB9, 0xBB, 0xBC => 'u', // ú ù û ü
|
|
0xA7 => 'c', // ç
|
|
0xB1 => 'n', // ñ
|
|
|
|
// Maiúsculos (convertidos para minúsculo)
|
|
0x81, 0x80, 0x82, 0x83, 0x84 => 'a', // Á À Â Ã Ä
|
|
0x89, 0x88, 0x8A, 0x8B => 'e', // É È Ê Ë
|
|
0x8D, 0x8C, 0x8E, 0x8F => 'i', // Í Ì Î Ï
|
|
0x93, 0x92, 0x94, 0x95, 0x96 => 'o', // Ó Ò Ô Õ Ö
|
|
0x9A, 0x99, 0x9B, 0x9C => 'u', // Ú Ù Û Ü
|
|
0x87 => 'c', // Ç
|
|
0x91 => 'n', // Ñ
|
|
else => std.ascii.toLower(s[i]),
|
|
};
|
|
i += 2;
|
|
} else {
|
|
replaced = std.ascii.toLower(s[i]);
|
|
i += 1;
|
|
}
|
|
|
|
if (std.ascii.isAlphanumeric(replaced)) {
|
|
try result.append(alloc, replaced);
|
|
last_was_hyphen = false;
|
|
} else if (replaced == ' ' or replaced == '-' or replaced == '_') {
|
|
if (!last_was_hyphen and result.items.len > 0) {
|
|
try result.append(alloc, '-');
|
|
}
|
|
last_was_hyphen = true;
|
|
} else {
|
|
last_was_hyphen = false;
|
|
}
|
|
}
|
|
|
|
// Remove hífen final
|
|
if (result.items.len > 0 and result.items[result.items.len - 1] == '-') {
|
|
_ = result.pop();
|
|
}
|
|
|
|
return Value{ .string = try result.toOwnedSlice(alloc) };
|
|
}
|
|
|
|
fn filter_stringformat(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
const format_str = switch (arg orelse return value) {
|
|
.string => |s| s,
|
|
else => return value,
|
|
};
|
|
|
|
var result = std.ArrayList(u8){};
|
|
|
|
var i: usize = 0;
|
|
while (i < format_str.len) {
|
|
if (format_str[i] == '%' and i + 1 < format_str.len) {
|
|
i += 1;
|
|
const specifier = format_str[i];
|
|
const str = switch (specifier) {
|
|
's' => switch (value) {
|
|
.string => |s| s,
|
|
else => try std.fmt.allocPrint(alloc, "{}", .{value}),
|
|
},
|
|
'd' => switch (value) {
|
|
.int => |n| try std.fmt.allocPrint(alloc, "{d}", .{n}),
|
|
else => try std.fmt.allocPrint(alloc, "{}", .{value}),
|
|
},
|
|
'f' => switch (value) {
|
|
.float => |f| try std.fmt.allocPrint(alloc, "{d}", .{f}),
|
|
.int => |n| try std.fmt.allocPrint(alloc, "{d}", .{@as(f64, @floatFromInt(n))}),
|
|
else => try std.fmt.allocPrint(alloc, "{}", .{value}),
|
|
},
|
|
'%' => "%",
|
|
else => blk: {
|
|
try result.append(alloc, '%');
|
|
try result.append(alloc, specifier);
|
|
break :blk "";
|
|
},
|
|
};
|
|
try result.appendSlice(alloc, str);
|
|
if (@typeInfo(@TypeOf(str)) == .pointer) alloc.free(str);
|
|
} else {
|
|
try result.append(alloc, format_str[i]);
|
|
}
|
|
i += 1;
|
|
}
|
|
|
|
return Value{ .string = try result.toOwnedSlice(alloc) };
|
|
}
|
|
|
|
fn filter_striptags(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){};
|
|
|
|
var in_tag = false;
|
|
for (s) |c| {
|
|
if (c == '<') {
|
|
in_tag = true;
|
|
} else if (c == '>' and in_tag) {
|
|
in_tag = false;
|
|
} else if (!in_tag) {
|
|
try result.append(alloc, c);
|
|
}
|
|
}
|
|
|
|
return Value{ .string = try result.toOwnedSlice(alloc) };
|
|
}
|
|
|
|
fn filter_timesince(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
_ = arg;
|
|
const then = switch (value) {
|
|
.int => |i| @as(i64, i),
|
|
else => std_time.timestamp(),
|
|
};
|
|
|
|
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 {
|
|
_ = arg;
|
|
// Reutiliza timesince, mas com sinal invertido
|
|
const future = switch (value) {
|
|
.int => |i| @as(i64, i),
|
|
else => std_time.timestamp(),
|
|
};
|
|
|
|
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 {
|
|
_ = arg;
|
|
const s = switch (value) {
|
|
.string => |str| str,
|
|
else => return value,
|
|
};
|
|
|
|
var result = std.ArrayList(u8){};
|
|
|
|
var word_start = true;
|
|
for (s) |c| {
|
|
if (std.ascii.isAlphanumeric(c)) {
|
|
try result.append(alloc, if (word_start) std.ascii.toUpper(c) else std.ascii.toLower(c));
|
|
word_start = false;
|
|
} else {
|
|
try result.append(alloc, c);
|
|
word_start = true;
|
|
}
|
|
}
|
|
|
|
return Value{ .string = try result.toOwnedSlice(alloc) };
|
|
}
|
|
|
|
fn filter_truncatechars(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
const s = switch (value) {
|
|
.string => |str| str,
|
|
else => return value,
|
|
};
|
|
|
|
const n = switch (arg orelse Value{ .int = 100 }) {
|
|
.int => |i| @as(usize, @max(i, 0)),
|
|
else => 100,
|
|
};
|
|
const ellipsis_len = 3;
|
|
|
|
if (n <= ellipsis_len) return Value{ .string = "" };
|
|
if (s.len <= n) return value;
|
|
|
|
const truncate_to = n - ellipsis_len;
|
|
const truncated = try alloc.alloc(u8, n);
|
|
@memcpy(truncated[0..truncate_to], s[0..truncate_to]);
|
|
@memcpy(truncated[truncate_to..], "...");
|
|
return Value{ .string = truncated };
|
|
}
|
|
|
|
fn filter_truncatechars_html(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
// Similar, mas poderia verificar tags HTML abertas — implementação avançada depois
|
|
return filter_truncatechars(alloc, value, arg);
|
|
}
|
|
|
|
fn filter_truncatewords(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
const s = switch (value) {
|
|
.string => |str| str,
|
|
else => return value,
|
|
};
|
|
|
|
const n = switch (arg orelse Value{ .int = 20 }) {
|
|
.int => |i| @as(usize, @max(i, 0)),
|
|
else => 20,
|
|
};
|
|
|
|
if (n == 0) return Value{ .string = "..." };
|
|
|
|
var words = std.mem.splitScalar(u8, s, ' ');
|
|
var result = std.ArrayList(u8){};
|
|
defer result.deinit(alloc);
|
|
|
|
var count: usize = 0;
|
|
while (words.next()) |word| : (count += 1) {
|
|
if (count == n) {
|
|
try result.appendSlice(alloc, "...");
|
|
break;
|
|
}
|
|
if (count > 0) try result.append(alloc, ' ');
|
|
try result.appendSlice(alloc, word);
|
|
}
|
|
|
|
// Se não estourou, não adiciona "..."
|
|
if (count < n) {
|
|
return Value{ .string = try alloc.dupe(u8, s) };
|
|
}
|
|
|
|
return Value{ .string = try result.toOwnedSlice(alloc) };
|
|
}
|
|
|
|
fn filter_truncatewords_html(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
// Similar, mas poderia fechar tags HTML — implementação avançada depois
|
|
return filter_truncatewords(alloc, value, arg);
|
|
}
|
|
|
|
fn filter_unordered_list(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
_ = arg;
|
|
const list = switch (value) {
|
|
.list => |l| l,
|
|
else => return value,
|
|
};
|
|
|
|
var result = std.ArrayList(u8){};
|
|
try result.appendSlice(alloc, "<ul>\n");
|
|
|
|
for (list) |item| {
|
|
try result.appendSlice(alloc, "<li>");
|
|
const str = try valueToSafeString(alloc, item);
|
|
try result.appendSlice(alloc, str);
|
|
try result.appendSlice(alloc, "</li>\n");
|
|
}
|
|
|
|
try result.appendSlice(alloc, "</ul>");
|
|
|
|
return Value{ .string = try result.toOwnedSlice(alloc) };
|
|
}
|
|
|
|
fn filter_upper(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
_ = arg;
|
|
const s = switch (value) {
|
|
.string => |str| str,
|
|
else => return value,
|
|
};
|
|
|
|
const uppered = toUpper(alloc, s) catch s;
|
|
return Value{ .string = uppered };
|
|
}
|
|
|
|
fn filter_urlencode(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_urlize(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
_ = arg;
|
|
const s = try valueToSafeString(alloc, value);
|
|
|
|
var result = std.ArrayList(u8){};
|
|
|
|
var i: usize = 0;
|
|
while (i < s.len) {
|
|
const remaining = s[i..];
|
|
if (std.mem.startsWith(u8, remaining, "http://") or
|
|
std.mem.startsWith(u8, remaining, "https://") or
|
|
std.mem.startsWith(u8, remaining, "www."))
|
|
{
|
|
var url_end = i;
|
|
while (url_end < s.len and
|
|
!std.ascii.isWhitespace(s[url_end]) and
|
|
s[url_end] != '<' and s[url_end] != '>' and
|
|
s[url_end] != '"' and s[url_end] != '\'') : (url_end += 1)
|
|
{}
|
|
const url = s[i..url_end];
|
|
|
|
const protocol = if (std.mem.startsWith(u8, url, "www.")) "http://" else "";
|
|
|
|
try result.appendSlice(alloc, "<a href=\"");
|
|
try result.appendSlice(alloc, protocol);
|
|
try result.appendSlice(alloc, url);
|
|
try result.appendSlice(alloc, "\">");
|
|
try result.appendSlice(alloc, url);
|
|
try result.appendSlice(alloc, "</a>");
|
|
|
|
i = url_end;
|
|
} else {
|
|
try result.append(alloc, s[i]);
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
return Value{ .string = try result.toOwnedSlice(alloc) };
|
|
}
|
|
|
|
fn filter_urlizetrunc(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
const limit = switch (arg orelse Value{ .int = 40 }) {
|
|
.int => |i| @as(usize, @max(i, 0)),
|
|
else => 40,
|
|
};
|
|
|
|
const s = try valueToSafeString(alloc, value);
|
|
|
|
std.debug.print("{s}\n", .{value.string});
|
|
var result = std.ArrayList(u8){};
|
|
|
|
var i: usize = 0;
|
|
while (i < s.len) {
|
|
if (std.mem.startsWith(u8, s[i..], "http://") or std.mem.startsWith(u8, s[i..], "https://")) {
|
|
var url_end = i;
|
|
while (url_end < s.len and !std.ascii.isWhitespace(s[url_end])) : (url_end += 1) {}
|
|
const url = s[i..url_end];
|
|
|
|
try result.appendSlice(alloc, "<a href=\"");
|
|
try result.appendSlice(alloc, url);
|
|
try result.appendSlice(alloc, "\">");
|
|
|
|
if (url.len > limit) {
|
|
try result.appendSlice(alloc, url[0..limit]);
|
|
try result.appendSlice(alloc, "...");
|
|
} else {
|
|
try result.appendSlice(alloc, url);
|
|
}
|
|
|
|
try result.appendSlice(alloc, "</a>");
|
|
|
|
i = url_end;
|
|
} else {
|
|
try result.append(alloc, s[i]);
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
return Value{ .string = try result.toOwnedSlice(alloc) };
|
|
}
|
|
|
|
fn filter_wordcount(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
_ = alloc;
|
|
_ = arg;
|
|
const s = switch (value) {
|
|
.string => |str| str,
|
|
else => return Value{ .int = 0 },
|
|
};
|
|
|
|
var count: usize = 0;
|
|
var in_word = false;
|
|
for (s) |c| {
|
|
if (std.ascii.isAlphanumeric(c)) {
|
|
if (!in_word) {
|
|
count += 1;
|
|
in_word = true;
|
|
}
|
|
} else {
|
|
in_word = false;
|
|
}
|
|
}
|
|
|
|
return Value{ .int = @intCast(count) };
|
|
}
|
|
|
|
fn filter_wordwrap(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
const width = switch (arg orelse Value{ .int = 80 }) {
|
|
.int => |i| @as(usize, @max(i, 0)),
|
|
else => 80,
|
|
};
|
|
|
|
const s = switch (value) {
|
|
.string => |str| str,
|
|
else => return value,
|
|
};
|
|
|
|
var result = std.ArrayList(u8){};
|
|
|
|
var line_len: usize = 0;
|
|
var words = std.mem.splitScalar(u8, s, ' ');
|
|
while (words.next()) |word| {
|
|
const space_len: usize = if (line_len > 0) 1 else 0;
|
|
const needed = line_len + space_len + word.len;
|
|
|
|
if (needed > width and line_len > 0) {
|
|
try result.append(alloc, '\n');
|
|
line_len = 0;
|
|
}
|
|
|
|
if (line_len > 0) {
|
|
try result.append(alloc, ' ');
|
|
line_len += 1;
|
|
}
|
|
|
|
try result.appendSlice(alloc, word);
|
|
line_len += word.len;
|
|
}
|
|
|
|
return Value{ .string = try result.toOwnedSlice(alloc) };
|
|
}
|
|
|
|
fn filter_yesno(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
|
const arg_str = switch (arg orelse return error.InvalidArgument) {
|
|
.string => |s| s,
|
|
else => return error.InvalidArgument,
|
|
};
|
|
|
|
var parts = std.mem.splitScalar(u8, arg_str, ',');
|
|
var true_val: []const u8 = "yes";
|
|
var false_val: []const u8 = "no";
|
|
var none_val: []const u8 = "no";
|
|
|
|
if (parts.next()) |t| true_val = t;
|
|
if (parts.next()) |f| false_val = f;
|
|
if (parts.next()) |n| none_val = n;
|
|
|
|
const is_truthy = switch (value) {
|
|
.bool => |b| b,
|
|
.null => null,
|
|
.string => |s| s.len > 0,
|
|
.int => |i| i != 0,
|
|
.float => |f| f != 0.0,
|
|
.list => |l| l.len > 0,
|
|
.dict => |d| d.count() > 0,
|
|
};
|
|
|
|
const result_str = if (is_truthy) |b|
|
|
if (b) true_val else false_val
|
|
else
|
|
none_val;
|
|
|
|
const copied = try alloc.dupe(u8, result_str);
|
|
return Value{ .string = copied };
|
|
}
|
|
|
|
// ==================== AUX FUNCTIONS ====================
|
|
pub fn capFirst(allocator: std.mem.Allocator, input: []const u8) ![]const u8 {
|
|
if (input.len == 0) return "";
|
|
|
|
const first_len = try std.unicode.utf8ByteSequenceLength(input[0]);
|
|
const first_codepoint = try std.unicode.utf8Decode(input[0..first_len]);
|
|
|
|
const upper_first = switch (first_codepoint) {
|
|
'a'...'z' => first_codepoint - 32,
|
|
'á' => 'Á',
|
|
'à' => 'À',
|
|
'â' => 'Â',
|
|
'ã' => 'Ã',
|
|
'ä' => 'Ä',
|
|
'é' => 'É',
|
|
'è' => 'È',
|
|
'ê' => 'Ê',
|
|
'í' => 'Í',
|
|
'ì' => 'Ì',
|
|
'î' => 'Î',
|
|
'ó' => 'Ó',
|
|
'ò' => 'Ò',
|
|
'ô' => 'Ô',
|
|
'õ' => 'Õ',
|
|
'ú' => 'Ú',
|
|
'ù' => 'Ù',
|
|
'û' => 'Û',
|
|
'ç' => 'Ç',
|
|
else => first_codepoint,
|
|
};
|
|
|
|
// ArrayList antigo, Zig 0.15 style
|
|
var result = std.ArrayList(u8){};
|
|
try result.ensureTotalCapacity(allocator, input.len);
|
|
|
|
// Escreve a primeira letra maiúscula
|
|
var buf: [4]u8 = undefined;
|
|
const bytes_written = try std.unicode.utf8Encode(upper_first, &buf);
|
|
try result.appendSlice(allocator, buf[0..bytes_written]);
|
|
|
|
// Copia o resto inalterado
|
|
try result.appendSlice(allocator, input[first_len..]);
|
|
|
|
return try result.toOwnedSlice(allocator);
|
|
}
|
|
|
|
fn toUpper(alloc: std.mem.Allocator, input: []const u8) ![]const u8 {
|
|
const result = try alloc.alloc(u8, input.len * 4);
|
|
var out_idx: usize = 0;
|
|
|
|
var i: usize = 0;
|
|
while (i < input.len) {
|
|
const len = try std.unicode.utf8ByteSequenceLength(input[i]);
|
|
const codepoint = try std.unicode.utf8Decode(input[i .. i + len]);
|
|
|
|
const upper = switch (codepoint) {
|
|
'a'...'z' => codepoint - 32,
|
|
'á' => 'Á',
|
|
'é' => 'É',
|
|
'í' => 'Í',
|
|
'ó' => 'Ó',
|
|
'ú' => 'Ú',
|
|
'ã' => 'Ã',
|
|
'õ' => 'Õ',
|
|
'ç' => 'Ç',
|
|
'â' => 'Â',
|
|
'ê' => 'Ê',
|
|
'î' => 'Î',
|
|
'ô' => 'Ô',
|
|
'û' => 'Û',
|
|
'à' => 'À',
|
|
'è' => 'È',
|
|
'ì' => 'Ì',
|
|
'ò' => 'Ò',
|
|
'ù' => 'Ù',
|
|
else => codepoint,
|
|
};
|
|
|
|
const bytes_written = try std.unicode.utf8Encode(upper, result[out_idx..]);
|
|
out_idx += bytes_written;
|
|
i += len;
|
|
}
|
|
|
|
return try alloc.dupe(u8, result[0..out_idx]);
|
|
}
|
|
|
|
fn toLower(alloc: std.mem.Allocator, input: []const u8) ![]const u8 {
|
|
const result = try alloc.alloc(u8, input.len * 4);
|
|
var out_idx: usize = 0;
|
|
|
|
var i: usize = 0;
|
|
while (i < input.len) {
|
|
const len = try std.unicode.utf8ByteSequenceLength(input[i]);
|
|
const codepoint = try std.unicode.utf8Decode(input[i .. i + len]);
|
|
|
|
const lower = switch (codepoint) {
|
|
'A'...'Z' => codepoint + 32,
|
|
'Á' => 'á',
|
|
'É' => 'é',
|
|
'Í' => 'í',
|
|
'Ó' => 'ó',
|
|
'Ú' => 'ú',
|
|
'Ã' => 'ã',
|
|
'Õ' => 'õ',
|
|
'Ç' => 'ç',
|
|
'Â' => 'â',
|
|
'Ê' => 'ê',
|
|
'Î' => 'î',
|
|
'Ô' => 'ô',
|
|
'Û' => 'û',
|
|
'À' => 'à',
|
|
'È' => 'è',
|
|
'Ì' => 'ì',
|
|
'Ò' => 'ò',
|
|
'Ù' => 'ù',
|
|
else => codepoint,
|
|
};
|
|
|
|
const bytes_written = try std.unicode.utf8Encode(lower, result[out_idx..]);
|
|
out_idx += bytes_written;
|
|
i += len;
|
|
}
|
|
|
|
return try alloc.dupe(u8, result[0..out_idx]);
|
|
}
|
|
|
|
fn valueToSafeString(alloc: std.mem.Allocator, value: Value) FilterError![]const u8 {
|
|
return switch (value) {
|
|
.string => |s| s,
|
|
.int => |i| try std.fmt.allocPrint(alloc, "{d}", .{i}),
|
|
.float => |f| try std.fmt.allocPrint(alloc, "{d}", .{f}),
|
|
.bool => |b| if (b) "true" else "false",
|
|
.null => "null",
|
|
else => "[object]", // list, dict → literal, não aloca
|
|
};
|
|
}
|
|
|
|
// ==================== MAPA DE FILTROS BUILTIN ====================
|
|
|
|
pub const builtin_filters = std.StaticStringMap(*const FilterFn).initComptime(.{
|
|
.{ "add", &filter_add },
|
|
.{ "addslashes", &filter_addslashes },
|
|
.{ "capfirst", &filter_capfirst },
|
|
.{ "center", &filter_center },
|
|
.{ "cut", &filter_cut },
|
|
// .{ "date", &filter_date },
|
|
.{ "default", &filter_default },
|
|
.{ "default_if_none", &filter_default_if_none },
|
|
.{ "dictsort", &filter_dictsort },
|
|
.{ "dictsortreversed", &filter_dictsortreversed },
|
|
.{ "divisibleby", &filter_divisibleby },
|
|
.{ "escape", &filter_escape },
|
|
.{ "escapejs", &filter_escapejs },
|
|
.{ "escapeseq", &filter_escapeseq },
|
|
.{ "filesizeformat", &filter_filesizeformat },
|
|
.{ "first", &filter_first },
|
|
.{ "floatformat", &filter_floatformat },
|
|
.{ "force_escape", &filter_force_escape },
|
|
.{ "get_digit", &filter_get_digit },
|
|
.{ "iriencode", &filter_iriencode },
|
|
.{ "join", &filter_join },
|
|
.{ "json_script", &filter_json_script },
|
|
.{ "last", &filter_last },
|
|
.{ "length", &filter_length },
|
|
.{ "linebreaks", &filter_linebreaks },
|
|
.{ "linebreaksbr", &filter_linebreaksbr },
|
|
.{ "linenumbers", &filter_linenumbers },
|
|
.{ "ljust", &filter_ljust },
|
|
.{ "lower", &filter_lower },
|
|
.{ "make_list", &filter_make_list },
|
|
.{ "phone2numeric", &filter_phone2numeric },
|
|
.{ "pluralize", &filter_pluralize },
|
|
.{ "pprint", &filter_pprint },
|
|
.{ "random", &filter_random },
|
|
.{ "rjust", &filter_rjust },
|
|
.{ "safe", &filter_safe },
|
|
.{ "safeseq", &filter_safeseq },
|
|
.{ "slice", &filter_slice },
|
|
.{ "slugify", &filter_slugify },
|
|
.{ "stringformat", &filter_stringformat },
|
|
.{ "striptags", &filter_striptags },
|
|
// .{ "time", &filter_time },
|
|
// .{ "timesince", &filter_timesince },
|
|
// .{ "timeuntil", &filter_timeuntil },
|
|
.{ "title", &filter_title },
|
|
.{ "truncatechars", &filter_truncatechars },
|
|
.{ "truncatechars_html", &filter_truncatechars_html },
|
|
.{ "truncatewords", &filter_truncatewords },
|
|
.{ "truncatewords_html", &filter_truncatewords_html },
|
|
.{ "unordered_list", &filter_unordered_list },
|
|
.{ "upper", &filter_upper },
|
|
.{ "urlencode", &filter_urlencode },
|
|
.{ "urlize", &filter_urlize },
|
|
.{ "urlizetrunc", &filter_urlizetrunc },
|
|
.{ "wordwrap", &filter_wordwrap },
|
|
.{ "wordcount", &filter_wordcount },
|
|
.{ "yesno", &filter_yesno },
|
|
});
|