Merge branch 'refact_parse'
This commit is contained in:
commit
4d45072435
13 changed files with 5108 additions and 2872 deletions
|
|
@ -1,4 +1,6 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const time = @import("time.zig");
|
||||||
|
const util = @import("util.zig");
|
||||||
|
|
||||||
pub const Value = union(enum) {
|
pub const Value = union(enum) {
|
||||||
null,
|
null,
|
||||||
|
|
@ -34,8 +36,12 @@ pub const Context = struct {
|
||||||
self.arena.deinit();
|
self.arena.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toValue(self: *Context, value: anytype) !Value {
|
pub fn toValue(self: *Context, value: anytype) !Value {
|
||||||
const T = @TypeOf(value);
|
const T = @TypeOf(value);
|
||||||
|
if (T == time.Time) {
|
||||||
|
return Value{ .string = try time.formatDateTime(self.allocator(), value, "Y-m-d H:i:s") };
|
||||||
|
}
|
||||||
|
|
||||||
return switch (@typeInfo(T)) {
|
return switch (@typeInfo(T)) {
|
||||||
.bool => Value{ .bool = value },
|
.bool => Value{ .bool = value },
|
||||||
.int, .comptime_int => Value{ .int = @intCast(value) },
|
.int, .comptime_int => Value{ .int = @intCast(value) },
|
||||||
|
|
@ -109,6 +115,11 @@ pub const Context = struct {
|
||||||
return self.get(path) orelse try self.toValue(default);
|
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 {
|
// pub fn get(self: *const Context, comptime T: type, key: []const u8) !T {
|
||||||
// // const opt_value = self.map.get(key) orelse return error.KeyNotFound;
|
// // const opt_value = self.map.get(key) orelse return error.KeyNotFound;
|
||||||
// const opt_value = self.getOptional(T, key);
|
// const opt_value = self.getOptional(T, key);
|
||||||
|
|
|
||||||
83
src/delta.zig
Normal file
83
src/delta.zig
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
182
src/delta_test.zig
Normal file
182
src/delta_test.zig
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
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" {
|
||||||
|
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" {
|
||||||
|
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" {
|
||||||
|
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" {
|
||||||
|
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" {
|
||||||
|
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" {
|
||||||
|
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)" {
|
||||||
|
// 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" {
|
||||||
|
// 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)" {
|
||||||
|
// 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)" {
|
||||||
|
// 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)" {
|
||||||
|
// 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)" {
|
||||||
|
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)" {
|
||||||
|
// 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" {
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
}
|
||||||
111
src/filters.zig
111
src/filters.zig
|
|
@ -2,11 +2,15 @@ const std = @import("std");
|
||||||
const Value = @import("context.zig").Value;
|
const Value = @import("context.zig").Value;
|
||||||
const std_time = std.time;
|
const std_time = std.time;
|
||||||
|
|
||||||
|
const time = @import("time.zig");
|
||||||
|
|
||||||
pub const FilterError = error{
|
pub const FilterError = error{
|
||||||
InvalidArgument,
|
InvalidArgument,
|
||||||
|
InvalidCharacter,
|
||||||
|
Overflow,
|
||||||
OutOfMemory,
|
OutOfMemory,
|
||||||
UnknownFilter,
|
UnknownFilter,
|
||||||
};
|
} || time.TimeError;
|
||||||
|
|
||||||
const DictEntry = struct {
|
const DictEntry = struct {
|
||||||
key: []const u8,
|
key: []const u8,
|
||||||
|
|
@ -122,35 +126,9 @@ fn filter_cut(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!V
|
||||||
return Value{ .string = try result.toOwnedSlice(alloc) };
|
return Value{ .string = try result.toOwnedSlice(alloc) };
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn filter_date(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
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
|
return dateTimeToString(alloc, value, arg);
|
||||||
// // 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 {
|
fn filter_default(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
||||||
_ = alloc;
|
_ = alloc;
|
||||||
|
|
@ -656,6 +634,12 @@ fn filter_make_list(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterE
|
||||||
return Value{ .list = try list.toOwnedSlice(alloc) };
|
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 {
|
fn filter_phone2numeric(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
||||||
_ = arg;
|
_ = arg;
|
||||||
const s = switch (value) {
|
const s = switch (value) {
|
||||||
|
|
@ -959,45 +943,22 @@ fn filter_striptags(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterE
|
||||||
return Value{ .string = try result.toOwnedSlice(alloc) };
|
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 {
|
fn filter_timesince(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
||||||
_ = arg;
|
const d = time.Time.parse(value.string) catch return value;
|
||||||
const then = switch (value) {
|
const now = time.Time.parse(arg.?.string) catch return value;
|
||||||
.int => |i| @as(i64, i),
|
|
||||||
else => std_time.timestamp(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const now = std_time.timestamp();
|
return Value{ .string = try d.timeSince(alloc, now) };
|
||||||
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 {
|
fn filter_timeuntil(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
||||||
_ = arg;
|
const d = time.Time.parse(value.string) catch return value;
|
||||||
// Reutiliza timesince, mas com sinal invertido
|
const now = time.Time.parse(arg.?.string) catch return value;
|
||||||
const future = switch (value) {
|
|
||||||
.int => |i| @as(i64, i),
|
|
||||||
else => std_time.timestamp(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const fake_past = Value{ .int = std_time.timestamp() };
|
return Value{ .string = try now.timeSince(alloc, d) };
|
||||||
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 {
|
fn filter_title(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
||||||
|
|
@ -1191,7 +1152,6 @@ fn filter_urlizetrunc(alloc: std.mem.Allocator, value: Value, arg: ?Value) Filte
|
||||||
|
|
||||||
const s = try valueToSafeString(alloc, value);
|
const s = try valueToSafeString(alloc, value);
|
||||||
|
|
||||||
std.debug.print("{s}\n", .{value.string});
|
|
||||||
var result = std.ArrayList(u8){};
|
var result = std.ArrayList(u8){};
|
||||||
|
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
|
|
@ -1319,6 +1279,20 @@ fn filter_yesno(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== AUX FUNCTIONS ====================
|
// ==================== 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 {
|
pub fn capFirst(allocator: std.mem.Allocator, input: []const u8) ![]const u8 {
|
||||||
if (input.len == 0) return "";
|
if (input.len == 0) return "";
|
||||||
|
|
||||||
|
|
@ -1463,7 +1437,7 @@ pub const builtin_filters = std.StaticStringMap(*const FilterFn).initComptime(.{
|
||||||
.{ "capfirst", &filter_capfirst },
|
.{ "capfirst", &filter_capfirst },
|
||||||
.{ "center", &filter_center },
|
.{ "center", &filter_center },
|
||||||
.{ "cut", &filter_cut },
|
.{ "cut", &filter_cut },
|
||||||
// .{ "date", &filter_date },
|
.{ "date", &filter_date },
|
||||||
.{ "default", &filter_default },
|
.{ "default", &filter_default },
|
||||||
.{ "default_if_none", &filter_default_if_none },
|
.{ "default_if_none", &filter_default_if_none },
|
||||||
.{ "dictsort", &filter_dictsort },
|
.{ "dictsort", &filter_dictsort },
|
||||||
|
|
@ -1488,6 +1462,7 @@ pub const builtin_filters = std.StaticStringMap(*const FilterFn).initComptime(.{
|
||||||
.{ "ljust", &filter_ljust },
|
.{ "ljust", &filter_ljust },
|
||||||
.{ "lower", &filter_lower },
|
.{ "lower", &filter_lower },
|
||||||
.{ "make_list", &filter_make_list },
|
.{ "make_list", &filter_make_list },
|
||||||
|
.{ "now", &filter_now },
|
||||||
.{ "phone2numeric", &filter_phone2numeric },
|
.{ "phone2numeric", &filter_phone2numeric },
|
||||||
.{ "pluralize", &filter_pluralize },
|
.{ "pluralize", &filter_pluralize },
|
||||||
.{ "pprint", &filter_pprint },
|
.{ "pprint", &filter_pprint },
|
||||||
|
|
@ -1499,9 +1474,9 @@ pub const builtin_filters = std.StaticStringMap(*const FilterFn).initComptime(.{
|
||||||
.{ "slugify", &filter_slugify },
|
.{ "slugify", &filter_slugify },
|
||||||
.{ "stringformat", &filter_stringformat },
|
.{ "stringformat", &filter_stringformat },
|
||||||
.{ "striptags", &filter_striptags },
|
.{ "striptags", &filter_striptags },
|
||||||
// .{ "time", &filter_time },
|
.{ "time", &filter_time },
|
||||||
// .{ "timesince", &filter_timesince },
|
.{ "timesince", &filter_timesince },
|
||||||
// .{ "timeuntil", &filter_timeuntil },
|
.{ "timeuntil", &filter_timeuntil },
|
||||||
.{ "title", &filter_title },
|
.{ "title", &filter_title },
|
||||||
.{ "truncatechars", &filter_truncatechars },
|
.{ "truncatechars", &filter_truncatechars },
|
||||||
.{ "truncatechars_html", &filter_truncatechars_html },
|
.{ "truncatechars_html", &filter_truncatechars_html },
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,16 @@ const testing = std.testing;
|
||||||
const Value = @import("context.zig").Value;
|
const Value = @import("context.zig").Value;
|
||||||
const Context = @import("context.zig").Context;
|
const Context = @import("context.zig").Context;
|
||||||
const builtin_filters = @import("filters.zig").builtin_filters;
|
const builtin_filters = @import("filters.zig").builtin_filters;
|
||||||
const FilterError = @import("filters.zig").FilterError;
|
const filter = @import("filters.zig");
|
||||||
|
const FilterError = filter.FilterError;
|
||||||
|
const time = @import("time.zig");
|
||||||
const std_time = std.time;
|
const std_time = std.time;
|
||||||
|
const RelativeDelta = @import("delta.zig").RelativeDelta;
|
||||||
|
|
||||||
test "filters upper/lower, capfirst" {
|
test "filters upper/lower, capfirst" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("1 - upper/lower, capfirst\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -29,6 +35,9 @@ test "filters upper/lower, capfirst" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - add" {
|
test "builtin filters - add" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("2 - add\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
const add = builtin_filters.get("add").?;
|
const add = builtin_filters.get("add").?;
|
||||||
|
|
||||||
|
|
@ -49,6 +58,9 @@ test "builtin filters - add" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - default and default_if_none" {
|
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 alloc = testing.allocator;
|
||||||
const default_filter = builtin_filters.get("default").?;
|
const default_filter = builtin_filters.get("default").?;
|
||||||
const default_if_none = builtin_filters.get("default_if_none").?;
|
const default_if_none = builtin_filters.get("default_if_none").?;
|
||||||
|
|
@ -73,6 +85,9 @@ test "builtin filters - default and default_if_none" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - length" {
|
test "builtin filters - length" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("4 - length\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
const length = builtin_filters.get("length").?;
|
const length = builtin_filters.get("length").?;
|
||||||
|
|
||||||
|
|
@ -94,7 +109,10 @@ test "builtin filters - length" {
|
||||||
try testing.expect((try length(ctx.allocator(), list_val, null)).int == 4);
|
try testing.expect((try length(ctx.allocator(), list_val, null)).int == 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - length com dict" {
|
test "builtin filters - length with dict" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("5 - length with dict\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -114,6 +132,9 @@ test "builtin filters - length com dict" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - first and last" {
|
test "builtin filters - first and last" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("6 - first and last\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
const first = builtin_filters.get("first").?;
|
const first = builtin_filters.get("first").?;
|
||||||
const last = builtin_filters.get("last").?;
|
const last = builtin_filters.get("last").?;
|
||||||
|
|
@ -143,6 +164,9 @@ test "builtin filters - first and last" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - join" {
|
test "builtin filters - join" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("7 - join\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
const join = builtin_filters.get("join").?;
|
const join = builtin_filters.get("join").?;
|
||||||
|
|
||||||
|
|
@ -159,7 +183,6 @@ test "builtin filters - join" {
|
||||||
try ctx.set("mixed", mixed);
|
try ctx.set("mixed", mixed);
|
||||||
const mixed_val = ctx.get("mixed").?;
|
const mixed_val = ctx.get("mixed").?;
|
||||||
|
|
||||||
|
|
||||||
const default_join = try join(ctx.allocator(), list_val, null);
|
const default_join = try join(ctx.allocator(), list_val, null);
|
||||||
const custom_join = try join(ctx.allocator(), list_val, sep_dash);
|
const custom_join = try join(ctx.allocator(), list_val, sep_dash);
|
||||||
const mixed_join = try join(ctx.allocator(), mixed_val, null);
|
const mixed_join = try join(ctx.allocator(), mixed_val, null);
|
||||||
|
|
@ -170,6 +193,9 @@ test "builtin filters - join" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - yesno" {
|
test "builtin filters - yesno" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("8 - yesno\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
const yesno = builtin_filters.get("yesno").?;
|
const yesno = builtin_filters.get("yesno").?;
|
||||||
|
|
||||||
|
|
@ -192,6 +218,9 @@ test "builtin filters - yesno" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - truncatechars and truncatechars_html" {
|
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 alloc = testing.allocator;
|
||||||
const truncatechars = builtin_filters.get("truncatechars").?;
|
const truncatechars = builtin_filters.get("truncatechars").?;
|
||||||
const truncatechars_html = builtin_filters.get("truncatechars_html").?;
|
const truncatechars_html = builtin_filters.get("truncatechars_html").?;
|
||||||
|
|
@ -218,6 +247,9 @@ test "builtin filters - truncatechars and truncatechars_html" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - truncatewords" {
|
test "builtin filters - truncatewords" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("10 - truncatewords\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
const truncatewords = builtin_filters.get("truncatewords").?;
|
const truncatewords = builtin_filters.get("truncatewords").?;
|
||||||
const truncatewords_html = builtin_filters.get("truncatewords_html").?;
|
const truncatewords_html = builtin_filters.get("truncatewords_html").?;
|
||||||
|
|
@ -241,6 +273,9 @@ test "builtin filters - truncatewords" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - slice" {
|
test "builtin filters - slice" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("11 - slice\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
const slice = builtin_filters.get("slice").?;
|
const slice = builtin_filters.get("slice").?;
|
||||||
|
|
||||||
|
|
@ -267,6 +302,9 @@ test "builtin filters - slice" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - safe and force_escape" {
|
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 alloc = testing.allocator;
|
||||||
const safe = builtin_filters.get("safe").?;
|
const safe = builtin_filters.get("safe").?;
|
||||||
const force_escape = builtin_filters.get("force_escape").?;
|
const force_escape = builtin_filters.get("force_escape").?;
|
||||||
|
|
@ -287,6 +325,9 @@ test "builtin filters - safe and force_escape" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - linebreaksbr and linebreaks" {
|
test "builtin filters - linebreaksbr and linebreaks" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("13 - linebreaksbr and linebreaks\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
const linebreaksbr = builtin_filters.get("linebreaksbr").?;
|
const linebreaksbr = builtin_filters.get("linebreaksbr").?;
|
||||||
const linebreaks = builtin_filters.get("linebreaks").?;
|
const linebreaks = builtin_filters.get("linebreaks").?;
|
||||||
|
|
@ -306,6 +347,9 @@ test "builtin filters - linebreaksbr and linebreaks" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - escape and force_escape" {
|
test "builtin filters - escape and force_escape" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("14 - escape and force_escape\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -326,6 +370,9 @@ test "builtin filters - escape and force_escape" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - striptags" {
|
test "builtin filters - striptags" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("15 - striptags\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -341,6 +388,9 @@ test "builtin filters - striptags" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - slugify" {
|
test "builtin filters - slugify" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("16 - slugify\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -356,6 +406,9 @@ test "builtin filters - slugify" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - floatformat" {
|
test "builtin filters - floatformat" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("17 - floatformat\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -384,6 +437,9 @@ test "builtin filters - floatformat" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - stringformat" {
|
test "builtin filters - stringformat" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("18 - stringformat\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -404,6 +460,9 @@ test "builtin filters - stringformat" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - cut" {
|
test "builtin filters - cut" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("19 - cut\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -419,6 +478,9 @@ test "builtin filters - cut" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - title" {
|
test "builtin filters - title" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("20 - title\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -434,6 +496,9 @@ test "builtin filters - title" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - wordcount" {
|
test "builtin filters - wordcount" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("21 - wordcount\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -449,6 +514,9 @@ test "builtin filters - wordcount" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - urlencode" {
|
test "builtin filters - urlencode" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("22 - urlencode\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -464,6 +532,9 @@ test "builtin filters - urlencode" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - pluralize" {
|
test "builtin filters - pluralize" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("23 - pluralize\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -489,31 +560,35 @@ test "builtin filters - pluralize" {
|
||||||
// try testing.expectEqualStrings("", zero.string);
|
// try testing.expectEqualStrings("", zero.string);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - addslashes, center, date" {
|
test "builtin filters - addslashes, center" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("24 - addslashes, center\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
try ctx.set("quote", "He's a good boy");
|
try ctx.set("quote", "He's a good boy");
|
||||||
try ctx.set("texto", "zig");
|
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_quote = ctx.get("quote").?;
|
||||||
const v_texto = ctx.get("texto").?;
|
const v_texto = ctx.get("texto").?;
|
||||||
|
|
||||||
const addslashes = builtin_filters.get("addslashes").?;
|
const addslashes = builtin_filters.get("addslashes").?;
|
||||||
const center = builtin_filters.get("center").?;
|
const center = builtin_filters.get("center").?;
|
||||||
// const date = builtin_filters.get("date").?;
|
|
||||||
|
|
||||||
const slashed = try addslashes(ctx.allocator(), v_quote, null);
|
const slashed = try addslashes(ctx.allocator(), v_quote, null);
|
||||||
const centered = try center(ctx.allocator(), v_texto, Value{ .int = 10 });
|
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("He\\'s a good boy", slashed.string);
|
||||||
try testing.expectEqualStrings(" zig ", centered.string);
|
try testing.expectEqualStrings(" zig ", centered.string);
|
||||||
// try testing.expect(std.mem.startsWith(u8, formatted.string, "2026"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - dictsort and dictsortreversed" {
|
test "builtin filters - dictsort and dictsortreversed" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("25 - dictsort and dictsortreversed\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -544,6 +619,9 @@ test "builtin filters - dictsort and dictsortreversed" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - divisibleby, escapejs, filesizeformat, get_digit, json_script" {
|
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;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -579,6 +657,9 @@ test "builtin filters - divisibleby, escapejs, filesizeformat, get_digit, json_s
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - escapeseq, iriencode, linenumbers" {
|
test "builtin filters - escapeseq, iriencode, linenumbers" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("27 - escapeseq, iriencode, linenumbers\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -601,6 +682,9 @@ test "builtin filters - escapeseq, iriencode, linenumbers" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - ljust, rjust, center" {
|
test "builtin filters - ljust, rjust, center" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("28 - ljust, rjust, center\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -623,6 +707,9 @@ test "builtin filters - ljust, rjust, center" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - make_list, phone2numeric, pprint" {
|
test "builtin filters - make_list, phone2numeric, pprint" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("29 - make_list, phone2numeric, pprint\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -648,6 +735,9 @@ test "builtin filters - make_list, phone2numeric, pprint" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - random, safeseq" {
|
test "builtin filters - random, safeseq" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("30 - random, safeseq\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -670,29 +760,69 @@ test "builtin filters - random, safeseq" {
|
||||||
try testing.expect(safe == .list);
|
try testing.expect(safe == .list);
|
||||||
}
|
}
|
||||||
|
|
||||||
// test "builtin filters - date, time, timesince, timeuntil" {
|
test "builtin filters - date, now, time, timesince, timeuntil" {
|
||||||
// const alloc = testing.allocator;
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
// var ctx = Context.init(alloc);
|
std.debug.print("31 - date, now, time, timesince, timeuntil\n", .{});
|
||||||
// defer ctx.deinit();
|
|
||||||
//
|
const alloc = testing.allocator;
|
||||||
// const now = std_time.timestamp();
|
var ctx = Context.init(alloc);
|
||||||
//
|
defer ctx.deinit();
|
||||||
// // const date = builtin_filters.get("date").?;
|
|
||||||
// const time = builtin_filters.get("time").?;
|
const times = [_]time.Time{
|
||||||
// const timesince = builtin_filters.get("timesince").?;
|
time.Time.new(2026, 1, 2, 13, 15, 10),
|
||||||
// // const timeuntil = builtin_filters.get("timeuntil").?;
|
time.Time.new(2026, 1, 6, 19, 25, 0),
|
||||||
//
|
time.Time.new(2026, 1, 2, 13, 35, 0),
|
||||||
// // const d = try date(ctx.allocator(), Value{ .int = now }, Value{ .string = "d/m/Y" });
|
time.Time.new(2026, 1, 2, 13, 15, 19),
|
||||||
// const t = try time(ctx.allocator(), Value{ .int = now }, Value{ .string = "H:i" });
|
time.Time.new(2025, 1, 2, 13, 15,19),
|
||||||
//
|
time.Time.new(2024, 7, 5, 19, 4, 2),
|
||||||
// // try testing.expect(d.string.len > 0);
|
};
|
||||||
// try testing.expect(t.string.len > 0);
|
|
||||||
//
|
const date_filter = builtin_filters.get("date").?;
|
||||||
// const since = try timesince(ctx.allocator(), Value{ .int = now - 3600 }, null);
|
const now_filter = builtin_filters.get("now").?;
|
||||||
// try testing.expect(std.mem.indexOf(u8, since.string, "hora") != null);
|
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 - urlize, urlizetrunc, wordwrap, unordered_list" {
|
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;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -714,15 +844,16 @@ 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 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 });
|
const wrapped = try wordwrap(ctx.allocator(), Value{ .string = long_text }, Value{ .int = 20 });
|
||||||
try testing.expect(std.mem.indexOf(u8, wrapped.string, "\n") != null);
|
try testing.expect(std.mem.indexOf(u8, wrapped.string, "\n") != null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "builtin filters - unordered_list" {
|
test "builtin filters - unordered_list" {
|
||||||
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
|
std.debug.print("33 - unordered_list\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
|
|
||||||
const list = [_]Value{ Value{ .string = "item1" }, Value{ .string = "item2" } };
|
const list = [_]Value{ Value{ .string = "item1" }, Value{ .string = "item2" } };
|
||||||
try ctx.set("lista", list);
|
try ctx.set("lista", list);
|
||||||
|
|
||||||
|
|
@ -737,6 +868,15 @@ test "builtin filters - unordered_list" {
|
||||||
\\<li>item2</li>
|
\\<li>item2</li>
|
||||||
\\</ul>
|
\\</ul>
|
||||||
;
|
;
|
||||||
std.debug.print("lista gerada: {any}\n", .{ul.string});
|
|
||||||
try testing.expectEqualStrings(expected, 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;
|
||||||
|
}
|
||||||
|
|
|
||||||
338
src/lorem.zig
Normal file
338
src/lorem.zig
Normal file
|
|
@ -0,0 +1,338 @@
|
||||||
|
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, "<p>{s}</p>", .{pg}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return try std.mem.join(allocator, "\n", pa.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
const first = try std.fmt.allocPrint(allocator, "<p>{s}</p>", .{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, "<p>{s}</p>", .{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);
|
||||||
|
}
|
||||||
208
src/meta.zig
Normal file
208
src/meta.zig
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
3271
src/parser.zig
3271
src/parser.zig
File diff suppressed because it is too large
Load diff
1692
src/parser_test.zig
1692
src/parser_test.zig
File diff suppressed because it is too large
Load diff
386
src/renderer.zig
386
src/renderer.zig
|
|
@ -10,6 +10,8 @@ const builtin_filters = @import("filters.zig").builtin_filters;
|
||||||
const FilterError = @import("filters.zig").FilterError;
|
const FilterError = @import("filters.zig").FilterError;
|
||||||
const parser = @import("parser.zig");
|
const parser = @import("parser.zig");
|
||||||
const TemplateCache = @import("cache.zig").TemplateCache;
|
const TemplateCache = @import("cache.zig").TemplateCache;
|
||||||
|
const time = @import("time.zig");
|
||||||
|
const lorem = @import("lorem.zig");
|
||||||
|
|
||||||
pub const RenderError = error{
|
pub const RenderError = error{
|
||||||
InvalidCharacter,
|
InvalidCharacter,
|
||||||
|
|
@ -36,7 +38,8 @@ pub const Renderer = struct {
|
||||||
fn checkForExtends(self: *const Renderer, nodes: []parser.Node) ?parser.Node {
|
fn checkForExtends(self: *const Renderer, nodes: []parser.Node) ?parser.Node {
|
||||||
_ = self;
|
_ = self;
|
||||||
for (nodes) |n| {
|
for (nodes) |n| {
|
||||||
if (n.type == .extends) return n;
|
// if (n.type == .extends) return n;
|
||||||
|
if (n.type == .tag and n.tag.?.kind == .extends) return n;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +63,8 @@ pub const Renderer = struct {
|
||||||
const extends_node = self.checkForExtends(nodes);
|
const extends_node = self.checkForExtends(nodes);
|
||||||
|
|
||||||
if (extends_node) |ext| {
|
if (extends_node) |ext| {
|
||||||
const base_template = try self.readTemplateFile(ext.extends.?.parent_name);
|
// const base_template = try self.readTemplateFile(ext.extends.?.parent_name);
|
||||||
|
const base_template = try self.readTemplateFile(ext.tag.?.body.extends.parent_name);
|
||||||
defer self.allocator.free(base_template);
|
defer self.allocator.free(base_template);
|
||||||
|
|
||||||
var base_parser = parser.Parser.init(base_template);
|
var base_parser = parser.Parser.init(base_template);
|
||||||
|
|
@ -122,19 +126,18 @@ pub const Renderer = struct {
|
||||||
|
|
||||||
fn renderWithInheritance(self: *const Renderer, alloc: Allocator, base_nodes: []parser.Node, child_nodes: []parser.Node, writer: anytype) RenderError!void {
|
fn renderWithInheritance(self: *const Renderer, alloc: Allocator, base_nodes: []parser.Node, child_nodes: []parser.Node, writer: anytype) RenderError!void {
|
||||||
for (base_nodes) |base_node| {
|
for (base_nodes) |base_node| {
|
||||||
if (base_node.type == .block) {
|
if (base_node.type == .tag and base_node.tag.?.kind == .block) {
|
||||||
const block_name = base_node.block.?.name;
|
const block_name = base_node.tag.?.body.block.name;
|
||||||
|
|
||||||
// Procura no filho
|
|
||||||
const child_block = self.findChildBlock(child_nodes, block_name);
|
const child_block = self.findChildBlock(child_nodes, block_name);
|
||||||
if (child_block) |child| {
|
if (child_block) |child| {
|
||||||
// Renderiza o filho, passando o conteúdo do pai para block.super
|
// Renderiza o filho, passando o conteúdo do pai para block.super
|
||||||
for (child.body) |child_node| {
|
for (child.body) |child_node| {
|
||||||
try self.renderNode(alloc, child_nodes, child_node, writer, null, base_node.block.?.body);
|
try self.renderNode(alloc, child_nodes, child_node, writer, null, base_node.tag.?.body.block.body);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Renderiza o do pai
|
// Renderiza o do pai
|
||||||
for (base_node.block.?.body) |child| {
|
for (base_node.tag.?.body.block.body) |child| {
|
||||||
try self.renderNode(alloc, child_nodes, child, writer, null, null);
|
try self.renderNode(alloc, child_nodes, child, writer, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -186,167 +189,244 @@ pub const Renderer = struct {
|
||||||
try self.valueToString(alloc, &buf, value);
|
try self.valueToString(alloc, &buf, value);
|
||||||
try writer.writeAll(buf.items);
|
try writer.writeAll(buf.items);
|
||||||
},
|
},
|
||||||
.if_block => {
|
.tag => {
|
||||||
const condition = try self.evaluateCondition(alloc, node.@"if".?.condition);
|
switch (node.tag.?.kind) {
|
||||||
|
.if_block => {
|
||||||
|
const condition = try self.evaluateCondition(alloc, node.tag.?.body.@"if".condition);
|
||||||
|
|
||||||
if (condition) {
|
if (condition) {
|
||||||
for (node.@"if".?.true_body) |child| {
|
for (node.tag.?.body.@"if".true_body) |child| {
|
||||||
try self.renderNode(alloc, nodes, child, writer, null, null);
|
try self.renderNode(alloc, nodes, child, writer, null, null);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (node.@"if".?.false_body) |child| {
|
for (node.tag.?.body.@"if".false_body) |child| {
|
||||||
try self.renderNode(alloc, nodes, child, writer, null, null);
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.@"for".?.body.len == 0) {
|
|
||||||
for (node.@"for".?.empty_body) |child| {
|
|
||||||
try self.renderNode(alloc, nodes, child, writer, &ctx, 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) |item| {
|
||||||
|
var ctx = Context.init(alloc);
|
||||||
|
defer ctx.deinit();
|
||||||
|
|
||||||
|
try ctx.set(node.tag.?.body.@"for".loop_var, item);
|
||||||
|
|
||||||
|
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, "<input type=\"hidden\" name=\"csrfmiddlewaretoken\" value=\"{s}\">", .{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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.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.widthratio.?.value, "")) {
|
|
||||||
value = Value{ .string = node.widthratio.?.value };
|
|
||||||
if (self.context.get(node.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.widthratio.?.max_value, "")) {
|
|
||||||
max_value = Value{ .string = node.widthratio.?.max_value };
|
|
||||||
if (self.context.get(node.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.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");
|
|
||||||
},
|
|
||||||
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 {
|
fn findChildBlock(self: *const Renderer, nodes: []parser.Node, name: []const u8) ?parser.BlockNode {
|
||||||
_ = self;
|
_ = self;
|
||||||
for (nodes) |n| {
|
for (nodes) |n| {
|
||||||
if (n.type != .block) continue;
|
if (n.type != .tag) continue;
|
||||||
if (std.mem.eql(u8, n.block.?.name, name)) return n.block.?;
|
if (n.tag.?.kind != .block) continue;
|
||||||
|
if (std.mem.eql(u8, n.tag.?.body.block.name, name)) return n.tag.?.body.block;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toValue(self: *Context, value: anytype) RenderError!Value {
|
fn escapeHtml(self: *const Renderer, value: Value) !Value {
|
||||||
const T = @TypeOf(value);
|
const s = switch (value) {
|
||||||
return switch (@typeInfo(T)) {
|
.string => |str| str,
|
||||||
.bool => Value{ .bool = value },
|
else => return value,
|
||||||
.int, .comptime_int => Value{ .int = @intCast(value) },
|
|
||||||
.float, .comptime_float => Value{ .float = @floatCast(value) },
|
|
||||||
.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| {
|
|
||||||
const field_val = @field(value, field.name);
|
|
||||||
const converted = try self.toValue(field_val);
|
|
||||||
try dict.put(self.allocator(), field.name, converted);
|
|
||||||
}
|
|
||||||
break :blk Value{ .dict = dict };
|
|
||||||
},
|
|
||||||
.array => blk: {
|
|
||||||
var list = try self.allocator().alloc(Value, value.len);
|
|
||||||
for (value, 0..) |item, i| {
|
|
||||||
list[i] = try self.toValue(item);
|
|
||||||
}
|
|
||||||
break :blk Value{ .list = list };
|
|
||||||
},
|
|
||||||
.optional => if (value) |v| try self.toValue(v) else .null,
|
|
||||||
.null => .null,
|
|
||||||
// CASO ESPECIAL: o valor já é um Value (ex: lista de Value)
|
|
||||||
.@"union" => if (T == Value) value else @compileError("Unsupported union type: " ++ @typeName(T)),
|
|
||||||
else => @compileError("Unsupported type: " ++ @typeName(T)),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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) RenderError!void {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ const TemplateCache = @import("cache.zig").TemplateCache;
|
||||||
|
|
||||||
test "renderer: literal + variável simples" {
|
test "renderer: literal + variável simples" {
|
||||||
std.debug.print("____________________________________________________\n", .{});
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
std.debug.print("\n1 - renderer: literal + variável simples\n\n", .{});
|
std.debug.print("1 - renderer: literal + variável simples\n", .{});
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -20,7 +20,7 @@ test "renderer: literal + variável simples" {
|
||||||
|
|
||||||
const renderer = Renderer.init(&ctx, &cache);
|
const renderer = Renderer.init(&ctx, &cache);
|
||||||
|
|
||||||
try ctx.set("nome", Value{ .string = "Mariana" });
|
try ctx.set("nome", Value{ .string = "Fulana" });
|
||||||
|
|
||||||
var buf = std.ArrayList(u8){};
|
var buf = std.ArrayList(u8){};
|
||||||
defer buf.deinit(alloc);
|
defer buf.deinit(alloc);
|
||||||
|
|
@ -33,12 +33,12 @@ test "renderer: literal + variável simples" {
|
||||||
|
|
||||||
// std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items});
|
// std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items});
|
||||||
|
|
||||||
try testing.expectEqualStrings("Olá, Mariana! Bem-vinda.", buf.items);
|
try testing.expectEqualStrings("Olá, Fulana! Bem-vinda.", buf.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "renderer: filtros + autoescape" {
|
test "renderer: filtros + autoescape" {
|
||||||
std.debug.print("____________________________________________________\n", .{});
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
std.debug.print("\n2 - renderer: filtros + autoescape\n\n", .{});
|
std.debug.print("2 - renderer: filtros + autoescape\n", .{});
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -68,7 +68,6 @@ test "renderer: filtros + autoescape" {
|
||||||
\\Filtrado: maiusculo-e-slug
|
\\Filtrado: maiusculo-e-slug
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
||||||
// std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items});
|
// std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items});
|
||||||
|
|
||||||
try testing.expectEqualStrings(expected, buf.items);
|
try testing.expectEqualStrings(expected, buf.items);
|
||||||
|
|
@ -76,7 +75,7 @@ test "renderer: filtros + autoescape" {
|
||||||
|
|
||||||
test "literal simples" {
|
test "literal simples" {
|
||||||
std.debug.print("____________________________________________________\n", .{});
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
std.debug.print("\n3 - literal simples\n\n", .{});
|
std.debug.print("3 - literal simples\n", .{});
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -100,7 +99,7 @@ test "literal simples" {
|
||||||
|
|
||||||
test "variável com filtro encadeado e autoescape" {
|
test "variável com filtro encadeado e autoescape" {
|
||||||
std.debug.print("____________________________________________________\n", .{});
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
std.debug.print("\n4 - variável com filtro encadeado e autoescape\n\n", .{});
|
std.debug.print("4 - variável com filtro encadeado e autoescape\n", .{});
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -126,7 +125,7 @@ test "variável com filtro encadeado e autoescape" {
|
||||||
|
|
||||||
test "autoescape com safe" {
|
test "autoescape com safe" {
|
||||||
std.debug.print("____________________________________________________\n", .{});
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
std.debug.print("\n5 - autoescape com safe\n\n", .{});
|
std.debug.print("5 - autoescape com safe\n", .{});
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -150,10 +149,10 @@ test "autoescape com safe" {
|
||||||
try testing.expectEqualStrings("Escape: <div>conteúdo</div> | Safe: <div>conteúdo</div>", buf.items);
|
try testing.expectEqualStrings("Escape: <div>conteúdo</div> | Safe: <div>conteúdo</div>", buf.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: evaluationConditions more complex
|
// // TODO: evaluationConditions more complex
|
||||||
test "renderer - if and for" {
|
test "renderer - if and for" {
|
||||||
std.debug.print("____________________________________________________\n", .{});
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
std.debug.print("\n6 - renderer - if and for\n\n", .{});
|
std.debug.print("6 - renderer - if and for\n", .{});
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -195,7 +194,7 @@ test "renderer - if and for" {
|
||||||
|
|
||||||
test "renderer - block and extends" {
|
test "renderer - block and extends" {
|
||||||
std.debug.print("____________________________________________________\n", .{});
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
std.debug.print("\n7 - renderer - block and extends\n\n", .{});
|
std.debug.print("7 - renderer - block and extends\n", .{});
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -260,7 +259,7 @@ test "renderer - block and extends" {
|
||||||
|
|
||||||
test "renderer - block and extends with super" {
|
test "renderer - block and extends with super" {
|
||||||
std.debug.print("____________________________________________________\n", .{});
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
std.debug.print("\n8 - renderer - block and extends with super\n\n", .{});
|
std.debug.print("8 - renderer - block and extends with super\n", .{});
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -327,7 +326,7 @@ test "renderer - block and extends with super" {
|
||||||
|
|
||||||
test "renderer - include" {
|
test "renderer - include" {
|
||||||
std.debug.print("____________________________________________________\n", .{});
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
std.debug.print("\n9 - renderer - include\n\n", .{});
|
std.debug.print("9 - renderer - include\n", .{});
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
const header =
|
const header =
|
||||||
|
|
@ -386,7 +385,7 @@ test "renderer - include" {
|
||||||
|
|
||||||
test "renderer - comment" {
|
test "renderer - comment" {
|
||||||
std.debug.print("____________________________________________________\n", .{});
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
std.debug.print("\n10 - renderer - comment\n\n", .{});
|
std.debug.print("10 - renderer - comment\n", .{});
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
const template =
|
const template =
|
||||||
|
|
@ -434,7 +433,7 @@ test "renderer - comment" {
|
||||||
|
|
||||||
test "renderer - full template with extends, super, include, comment" {
|
test "renderer - full template with extends, super, include, comment" {
|
||||||
std.debug.print("____________________________________________________\n", .{});
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
std.debug.print("\n11 - renderer - full template with extends, super, include, comment\n\n", .{});
|
std.debug.print("11 - renderer - full template with extends, super, include, comment\n", .{});
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
const header = "<header>Bem-vindo</header>";
|
const header = "<header>Bem-vindo</header>";
|
||||||
|
|
@ -482,14 +481,16 @@ test "renderer - full template with extends, super, include, comment" {
|
||||||
// std.debug.print("OUTPUT:\n\n{s}\n", .{output});
|
// std.debug.print("OUTPUT:\n\n{s}\n", .{output});
|
||||||
|
|
||||||
try testing.expect(std.mem.indexOf(u8, output, "<header>Bem-vindo</header>") != null);
|
try testing.expect(std.mem.indexOf(u8, output, "<header>Bem-vindo</header>") != null);
|
||||||
|
try testing.expect(std.mem.indexOf(u8, output, "<main>") != null);
|
||||||
try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") != 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, "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, "Isso não aparece") == null);
|
||||||
|
try testing.expect(std.mem.indexOf(u8, output, "bazinga") == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "renderer - if inside block" {
|
test "renderer - if inside block" {
|
||||||
std.debug.print("____________________________________________________\n", .{});
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
std.debug.print("\n12 - render - if inside block\n\n", .{});
|
std.debug.print("12 - render - if inside block\n", .{});
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
const base =
|
const base =
|
||||||
|
|
@ -541,11 +542,12 @@ test "renderer - if inside block" {
|
||||||
try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") != 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, "Conteúdo do filho") != null);
|
||||||
try testing.expect(std.mem.indexOf(u8, output, "Oops") == 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" {
|
test "renderer - if with operators" {
|
||||||
std.debug.print("____________________________________________________\n", .{});
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
std.debug.print("\n13 - render - if inside block\n\n", .{});
|
std.debug.print("13 - render - if inside block\n", .{});
|
||||||
|
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
|
|
@ -585,7 +587,7 @@ test "renderer - if with operators" {
|
||||||
|
|
||||||
test "renderer - widthratio inside block" {
|
test "renderer - widthratio inside block" {
|
||||||
std.debug.print("____________________________________________________\n", .{});
|
std.debug.print("____________________________________________________\n", .{});
|
||||||
std.debug.print("\n14 - render - widthratio inside block\n\n", .{});
|
std.debug.print("14 - render - widthratio inside block\n", .{});
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
const base =
|
const base =
|
||||||
|
|
@ -633,3 +635,436 @@ test "renderer - widthratio inside block" {
|
||||||
try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") != 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, "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 =
|
||||||
|
\\<input type="hidden" name="csrfmiddlewaretoken" value="zh5fyUSICjXNsDTtJCjl9A3O2dDSHhYFlIngAEO6PXK9NX56Z1XLEy7doYuPcE0u">
|
||||||
|
;
|
||||||
|
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, "<p>");
|
||||||
|
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, "<p>");
|
||||||
|
try testing.expect(spaces == 3);
|
||||||
|
}
|
||||||
|
|
|
||||||
874
src/time.zig
Normal file
874
src/time.zig
Normal file
|
|
@ -0,0 +1,874 @@
|
||||||
|
// 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 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);
|
||||||
|
// }
|
||||||
149
src/util.zig
Normal file
149
src/util.zig
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue