Compare commits
54 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f38f43dbc4 | ||
|
|
47f52fea58 | ||
|
|
20e4bacab0 | ||
|
|
8bf9902ebf | ||
|
|
777b13ed98 | ||
|
|
fadae5d39c | ||
|
|
ea30958701 | ||
|
|
a6d8795c79 | ||
|
|
59e543ca89 | ||
|
|
7beb8758a5 | ||
|
|
1181f0fa68 | ||
|
|
f0115a15b9 | ||
|
|
a0645cb0d1 | ||
|
|
8fd9086d98 | ||
|
|
cce0c6a954 | ||
|
|
966035dccb | ||
|
|
56dd881e44 | ||
|
|
4d45072435 | ||
|
|
24ecea0244 | ||
|
|
14e307f3dc | ||
|
|
655bed4c8f | ||
|
|
1888c5e07a | ||
|
|
36cc1caac0 | ||
|
|
745b2b29ca | ||
|
|
1ab6291116 | ||
|
|
7c53a272a7 | ||
|
|
44fb897a41 | ||
|
|
d3cff325eb | ||
|
|
3fa37f9613 | ||
|
|
f09689d539 | ||
|
|
f8e7b01e9c | ||
|
|
e11d3fb034 | ||
|
|
002d2b949e | ||
|
|
c7d52a0f32 | ||
|
|
c4319f5da3 | ||
|
|
5ff94ccf17 | ||
|
|
327a44baf0 | ||
|
|
eb30117bbf | ||
|
|
d4538db83a | ||
|
|
ac122a513b | ||
|
|
bb76e6cd44 | ||
|
|
b93d6f5c81 | ||
|
|
c14a92e7ab | ||
|
|
1e94763ba1 | ||
|
|
9e1e3c2039 | ||
|
|
8b36704652 | ||
|
|
7f47cf440b | ||
|
|
cf24e968ca | ||
|
|
eaff212b63 | ||
|
|
0116e84c09 | ||
|
|
c0e15a0179 | ||
|
|
0911b47396 | ||
|
|
a4ec89b743 | ||
|
|
024386eaf5 |
22 changed files with 6132 additions and 2497 deletions
|
|
@ -84,6 +84,15 @@ pub fn build(b: *std.Build) void {
|
|||
.use_llvm = true,
|
||||
});
|
||||
|
||||
// const lib = b.addLibrary(.{
|
||||
// .name = "zdt_prov",
|
||||
// .root_module = mod,
|
||||
// });
|
||||
//
|
||||
// lib.root_module.addIncludePath(b.path("src/svg"));
|
||||
//
|
||||
// b.installArtifact(lib);
|
||||
|
||||
// This declares intent for the executable to be installed into the
|
||||
// install prefix when running `zig build` (i.e. when executing the default
|
||||
// step). By default the install prefix is `zig-out/` but can be overridden
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@ const std = @import("std");
|
|||
const Allocator = std.heap.ArenaAllocator;
|
||||
|
||||
const parser = @import("parser.zig");
|
||||
const icons = @import("svg/icons.zig");
|
||||
|
||||
pub const TemplateCache = struct {
|
||||
arena: Allocator,
|
||||
cache: std.StringHashMapUnmanaged([]parser.Node),
|
||||
default_path: ?[]const u8 = "templates",
|
||||
icons: ?icons.SvgIcon =null,
|
||||
|
||||
pub fn init(child_allocator: std.mem.Allocator) TemplateCache {
|
||||
const arena = std.heap.ArenaAllocator.init(child_allocator);
|
||||
|
|
@ -15,7 +18,6 @@ pub const TemplateCache = struct {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
pub fn deinit(self: *TemplateCache) void {
|
||||
self.arena.deinit();
|
||||
}
|
||||
|
|
@ -50,6 +52,10 @@ pub const TemplateCache = struct {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn initIcons(self: *TemplateCache) !void {
|
||||
self.icons = icons.SvgIcon.init(self.allocator()) catch null;
|
||||
}
|
||||
|
||||
pub fn clear(self: *TemplateCache) void {
|
||||
self.deinit();
|
||||
self.cache = .{};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
const std = @import("std");
|
||||
const time = @import("time.zig");
|
||||
const util = @import("util.zig");
|
||||
|
||||
pub const Value = union(enum) {
|
||||
null,
|
||||
|
|
@ -34,8 +36,24 @@ pub const Context = struct {
|
|||
self.arena.deinit();
|
||||
}
|
||||
|
||||
fn toValue(self: *Context, value: anytype) !Value {
|
||||
pub fn toValue(self: *Context, value: anytype) !Value {
|
||||
const T = @TypeOf(value);
|
||||
if (T == time.Time) {
|
||||
return Value{ .string = try time.formatDateTime(self.allocator(), value, "Y-m-d H:i:s") };
|
||||
}
|
||||
|
||||
if (@typeInfo(T) == .pointer) {
|
||||
if (@typeInfo(T).pointer.size == .slice) {
|
||||
if (@typeInfo(@typeInfo(T).pointer.child) == .@"struct") {
|
||||
var list = try self.allocator().alloc(Value, value.len);
|
||||
for (value, 0..) |item, i| {
|
||||
list[i] = try self.toValue(item);
|
||||
}
|
||||
return Value{ .list = list };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return switch (@typeInfo(T)) {
|
||||
.bool => Value{ .bool = value },
|
||||
.int, .comptime_int => Value{ .int = @intCast(value) },
|
||||
|
|
@ -105,6 +123,14 @@ pub const Context = struct {
|
|||
|
||||
return current;
|
||||
}
|
||||
pub fn getOr(self: *Context, path: []const u8, default: anytype) ?Value {
|
||||
return self.get(path) orelse try self.toValue(default);
|
||||
}
|
||||
|
||||
fn isArrayList(value: anytype) bool {
|
||||
if (std.mem.startsWith(u8, @typeName(value), "array_list")) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// pub fn get(self: *const Context, comptime T: type, key: []const u8) !T {
|
||||
// // const opt_value = self.map.get(key) orelse return error.KeyNotFound;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ const Context = @import("context.zig").Context;
|
|||
const Value = @import("context.zig").Value;
|
||||
|
||||
test "context set amigável e get com ponto" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("1 - context set amigável e get com ponto\n", .{});
|
||||
|
||||
const allocator = testing.allocator;
|
||||
var ctx = Context.init(allocator);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -17,17 +20,26 @@ test "context set amigável e get com ponto" {
|
|||
// struct
|
||||
const Person = struct { nome: []const u8, idade: i64 };
|
||||
const p = Person{ .nome = "Ana", .idade = 25 };
|
||||
try ctx.set("user", p);
|
||||
const p2 = Person{ .nome = "Fulana", .idade = 28 };
|
||||
|
||||
const people = [_]Person{ p, p2 };
|
||||
|
||||
// try ctx.set("user", p);
|
||||
try ctx.set("user", people);
|
||||
|
||||
// list
|
||||
const numeros = [_]i64{ 1, 2, 3 };
|
||||
try ctx.set("lista", numeros);
|
||||
|
||||
for (ctx.get("user").?.list) |item| {
|
||||
std.debug.print("user {any}\n", .{item.dict.get("nome").?});
|
||||
}
|
||||
|
||||
// acesso
|
||||
try testing.expectEqualStrings("Lucas", ctx.get("nome").?.string);
|
||||
try testing.expect(ctx.get("idade").?.int == 30);
|
||||
try testing.expectEqualStrings("Ana", ctx.get("user.nome").?.string);
|
||||
try testing.expect(ctx.get("user.idade").?.int == 25);
|
||||
// try testing.expectEqualStrings("Ana", ctx.get("user.nome").?.string);
|
||||
// try testing.expect(ctx.get("user.idade").?.int == 25);
|
||||
try testing.expect(ctx.get("lista.1").?.int == 2);
|
||||
try testing.expect(ctx.get("vazio").?.string.len == 0);
|
||||
try testing.expect(ctx.get("preco").?.float == 99.99);
|
||||
|
|
|
|||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
224
src/delta_test.zig
Normal file
224
src/delta_test.zig
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const Time = @import("time.zig").Time;
|
||||
const Context = @import("context.zig").Context;
|
||||
const RelativeDelta = @import("delta.zig").RelativeDelta;
|
||||
|
||||
test "relativedelta rigoroso - meses" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("1 - context set amigável e get com ponto\n", .{});
|
||||
|
||||
const a = try Time.parse("2025-03-31");
|
||||
const b = try Time.parse("2025-01-31");
|
||||
const delta = a.subRelative(b);
|
||||
try testing.expectEqual(delta.months, 2);
|
||||
try testing.expectEqual(delta.years, 0);
|
||||
try testing.expectEqual(delta.days, 0);
|
||||
|
||||
const rev = b.subRelative(a);
|
||||
try testing.expectEqual(rev.months, -2);
|
||||
}
|
||||
|
||||
test "relativedelta - overflow de dia" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("2 - relativedelta - overflow de dia\n", .{});
|
||||
|
||||
const jan31 = try Time.parse("2023-01-31");
|
||||
const mar01 = try Time.parse("2023-03-01");
|
||||
|
||||
const delta = mar01.subRelative(jan31);
|
||||
// Esperado algo como: +1 mês +1 dia (ou +2 meses -30 dias, mas dateutil prefere o primeiro)
|
||||
try testing.expect(delta.months == 1);
|
||||
try testing.expect(delta.days == 1);
|
||||
}
|
||||
|
||||
test "bissexto: 2021-02-28 - 2020-02-29" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("3 - bissexto: 2021-02-28 - 2020-02-29\n", .{});
|
||||
|
||||
const a = try Time.parse("2021-02-28");
|
||||
const b = try Time.parse("2020-02-29");
|
||||
const delta = a.subRelative(b);
|
||||
try testing.expectEqual(delta.years, 1);
|
||||
try testing.expectEqual(delta.months, 0);
|
||||
try testing.expectEqual(delta.days, 0);
|
||||
}
|
||||
|
||||
test "bissexto: 2021-03-01 - 2020-02-29" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("4 - bissexto: 2021-03-01 - 2020-02-29\n", .{});
|
||||
|
||||
const a = try Time.parse("2021-03-01");
|
||||
const b = try Time.parse("2020-02-29");
|
||||
const delta = a.subRelative(b);
|
||||
try testing.expectEqual(delta.years, 1);
|
||||
try testing.expectEqual(delta.months, 0);
|
||||
try testing.expectEqual(delta.days, 1);
|
||||
}
|
||||
|
||||
test "bissexto: 2021-02-27 - 2020-02-29" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("5 - bissexto: 2021-02-27 - 2020-02-29\n", .{});
|
||||
|
||||
const a = try Time.parse("2021-02-27");
|
||||
const b = try Time.parse("2020-02-29");
|
||||
const delta = a.subRelative(b);
|
||||
try testing.expectEqual(delta.years, 0);
|
||||
try testing.expectEqual(delta.months, 11);
|
||||
try testing.expectEqual(delta.days, 29);
|
||||
}
|
||||
|
||||
test "bissexto reverso: 2020-02-29 - 2021-02-28" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("6 - bissexto reverso: 2020-02-29 - 2021-02-28\n", .{});
|
||||
|
||||
const a = try Time.parse("2020-02-29");
|
||||
const b = try Time.parse("2021-02-28");
|
||||
const delta = a.subRelative(b);
|
||||
try testing.expectEqual(delta.years, -1);
|
||||
try testing.expectEqual(delta.months, 0);
|
||||
try testing.expectEqual(delta.days, 0);
|
||||
}
|
||||
|
||||
test "addRelative: anos normais (não bissexto)" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("7 - addRelative: anos normais (não bissexto)\n", .{});
|
||||
|
||||
// 2023 não é bissexto
|
||||
const base = try Time.parse("2023-06-15");
|
||||
const expected = try Time.parse("2026-06-15");
|
||||
|
||||
const delta = RelativeDelta.init(.{ .years = 3 });
|
||||
const result = base.addRelative(delta);
|
||||
|
||||
try testing.expectEqualStrings(
|
||||
try expected.toString( null),
|
||||
try result.toString( null),
|
||||
);
|
||||
}
|
||||
|
||||
test "addRelative: anos normais com overflow de dia" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("8 - addRelative: anos normais com overflow de dia\n", .{});
|
||||
|
||||
// Janeiro 31 + 2 anos → deve ir para 31/jan (2025 não bissexto)
|
||||
const base = try Time.parse("2023-01-31");
|
||||
const expected = try Time.parse("2025-01-31");
|
||||
|
||||
const delta = RelativeDelta.init(.{ .years = 2 });
|
||||
const result = base.addRelative(delta);
|
||||
|
||||
try testing.expectEqualStrings(
|
||||
try expected.toString( null),
|
||||
try result.toString( null),
|
||||
);
|
||||
}
|
||||
|
||||
test "addRelative: de 29/fev bissexto + 1 ano (vai para 28/fev)" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("9 - addRelative: de 29/fev bissexto + 1 ano (vai para 28/fev)\n", .{});
|
||||
|
||||
// 2020 foi bissexto → +1 ano deve ir para 2021-02-28 (não bissexto)
|
||||
const base = try Time.parse("2020-02-29");
|
||||
const expected = try Time.parse("2021-02-28");
|
||||
|
||||
const delta = RelativeDelta.init(.{ .years = 1 });
|
||||
const result = base.addRelative(delta);
|
||||
|
||||
try testing.expectEqualStrings(
|
||||
try expected.toString( null),
|
||||
try result.toString( null),
|
||||
);
|
||||
}
|
||||
|
||||
test "addRelative: de 29/fev bissexto + 4 anos (permanece 29/fev)" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("10 - addRelative: de 29/fev bissexto + 4 anos (permanece 29/fev)\n", .{});
|
||||
|
||||
// 2020 → 2024 (ambos bissextos)
|
||||
const base = try Time.parse("2020-02-29");
|
||||
const expected = try Time.parse("2024-02-29");
|
||||
|
||||
const delta = RelativeDelta.init(.{ .years = 4 });
|
||||
const result = base.addRelative(delta);
|
||||
|
||||
try testing.expectEqualStrings(
|
||||
try expected.toString( null),
|
||||
try result.toString( null),
|
||||
);
|
||||
}
|
||||
|
||||
test "addRelative: de 29/fev + 1 ano + 1 mês (vai para março)" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("11 - addRelative: de 29/fev + 1 ano + 1 mês (vai para março)\n", .{});
|
||||
|
||||
// 2020-02-29 + 1 ano → 2021-02-28 + 1 mês → 2021-03-28
|
||||
const base = try Time.parse("2020-02-29");
|
||||
const expected = try Time.parse("2021-03-28");
|
||||
|
||||
const delta = RelativeDelta.init(.{ .years = 1, .months = 1 });
|
||||
const result = base.addRelative(delta);
|
||||
|
||||
try testing.expectEqualStrings(
|
||||
try expected.toString( null),
|
||||
try result.toString( null),
|
||||
);
|
||||
}
|
||||
|
||||
test "addRelative: meses com overflow (31 → 28/30)" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("12 - addRelative: meses com overflow (31 → 28/30)\n", .{});
|
||||
|
||||
const cases = [_]struct { base: []const u8, months: i32, expected: []const u8 }{
|
||||
.{ .base = "2023-01-31", .months = 1, .expected = "2023-02-28" }, // não bissexto
|
||||
.{ .base = "2024-01-31", .months = 1, .expected = "2024-02-29" }, // bissexto
|
||||
.{ .base = "2023-03-31", .months = -1, .expected = "2023-02-28" },
|
||||
.{ .base = "2023-01-31", .months = 2, .expected = "2023-03-31" },
|
||||
.{ .base = "2023-08-31", .months = 1, .expected = "2023-09-30" }, // setembro tem 30
|
||||
};
|
||||
|
||||
for (cases) |c| {
|
||||
const base_t = try Time.parse(c.base);
|
||||
const exp_t = try Time.parse(c.expected);
|
||||
|
||||
const delta = RelativeDelta.init(.{ .months = c.months });
|
||||
const result = base_t.addRelative(delta);
|
||||
|
||||
try testing.expectEqualStrings(
|
||||
try exp_t.toString( null),
|
||||
try result.toString( null),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
test "addRelative: combinação anos + meses + dias (bissexto envolvido)" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("13 - addRelative: combinação anos + meses + dias (bissexto envolvido)\n", .{});
|
||||
|
||||
// 2024-02-29 + 1 ano + 2 meses + 3 dias
|
||||
// → 2025-02-28 + 2 meses → 2025-04-28 + 3 dias → 2025-05-01
|
||||
const base = try Time.parse("2024-02-29");
|
||||
const expected = try Time.parse("2025-05-01");
|
||||
|
||||
const delta = RelativeDelta.init(.{ .years = 1, .months = 2, .days = 3 });
|
||||
const result = base.addRelative(delta);
|
||||
|
||||
try testing.expectEqualStrings(
|
||||
try expected.toString( null),
|
||||
try result.toString( null),
|
||||
);
|
||||
}
|
||||
|
||||
test "addRelative: delta zero não altera data" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("14 - addRelative: delta zero não altera data\n", .{});
|
||||
|
||||
const base = try Time.parse("2025-07-20");
|
||||
const delta = RelativeDelta.init(.{});
|
||||
const result = base.addRelative(delta);
|
||||
|
||||
try testing.expectEqualStrings(
|
||||
try base.toString( null),
|
||||
try result.toString( null),
|
||||
);
|
||||
}
|
||||
112
src/filters.zig
112
src/filters.zig
|
|
@ -2,10 +2,15 @@ const std = @import("std");
|
|||
const Value = @import("context.zig").Value;
|
||||
const std_time = std.time;
|
||||
|
||||
const time = @import("time.zig");
|
||||
|
||||
pub const FilterError = error{
|
||||
InvalidArgument,
|
||||
InvalidCharacter,
|
||||
Overflow,
|
||||
OutOfMemory,
|
||||
};
|
||||
UnknownFilter,
|
||||
} || time.TimeError;
|
||||
|
||||
const DictEntry = struct {
|
||||
key: []const u8,
|
||||
|
|
@ -121,35 +126,9 @@ fn filter_cut(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!V
|
|||
return Value{ .string = try result.toOwnedSlice(alloc) };
|
||||
}
|
||||
|
||||
// fn filter_date(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
||||
// // Por enquanto, simples: aceita string ou int (timestamp) e formata com strftime-like
|
||||
// // Futuro: suporte completo a std.time
|
||||
// _ = alloc;
|
||||
// const format = switch (arg orelse Value{ .string = "d/m/Y" }) {
|
||||
// .string => |f| f,
|
||||
// else => "d/m/Y",
|
||||
// };
|
||||
//
|
||||
// const timestamp = switch (value) {
|
||||
// .int => |i| @as(i64, i),
|
||||
// .string => |s| std.fmt.parseInt(i64, s, 10) catch 0,
|
||||
// else => 0,
|
||||
// };
|
||||
//
|
||||
// // Simulação simples (em produção usar std.time)
|
||||
// const day = @rem(timestamp, 30) + 1;
|
||||
// const month = @rem(timestamp / 30, 12) + 1;
|
||||
// const year = 2026 + @divFloor(timestamp, 360);
|
||||
//
|
||||
// var buf: [64]u8 = undefined;
|
||||
// const formatted = switch (format) {
|
||||
// "d/m/Y" => std.fmt.bufPrint(&buf, "{d:0>2}/{d:0>2}/{d}", .{ day, month, year }) catch "??/??/????",
|
||||
// "Y-m-d" => std.fmt.bufPrint(&buf, "{d}-{d:0>2}-{d:0>2}", .{ year, month, day }) catch "????-??-??",
|
||||
// else => "formato não suportado",
|
||||
// };
|
||||
//
|
||||
// return Value{ .string = try alloc.dupe(u8, formatted) };
|
||||
// }
|
||||
fn filter_date(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
||||
return dateTimeToString(alloc, value, arg);
|
||||
}
|
||||
|
||||
fn filter_default(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
||||
_ = alloc;
|
||||
|
|
@ -655,6 +634,12 @@ fn filter_make_list(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterE
|
|||
return Value{ .list = try list.toOwnedSlice(alloc) };
|
||||
}
|
||||
|
||||
fn filter_now(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
||||
_ = value;
|
||||
if (arg.?.string.len == 0) return Value{ .string = try time.Time.now().toStringAlloc(alloc, "F d, Y") };
|
||||
return Value{ .string = try time.Time.now().toStringAlloc(alloc, arg.?.string) };
|
||||
}
|
||||
|
||||
fn filter_phone2numeric(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
||||
_ = arg;
|
||||
const s = switch (value) {
|
||||
|
|
@ -958,45 +943,22 @@ fn filter_striptags(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterE
|
|||
return Value{ .string = try result.toOwnedSlice(alloc) };
|
||||
}
|
||||
|
||||
fn filter_time(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
||||
return dateTimeToString(alloc, value, arg);
|
||||
}
|
||||
|
||||
fn filter_timesince(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
||||
_ = arg;
|
||||
const then = switch (value) {
|
||||
.int => |i| @as(i64, i),
|
||||
else => std_time.timestamp(),
|
||||
};
|
||||
const d = time.Time.parse(value.string) catch return value;
|
||||
const now = time.Time.parse(arg.?.string) catch return value;
|
||||
|
||||
const now = std_time.timestamp();
|
||||
var diff = now - then;
|
||||
if (diff < 0) diff = -diff;
|
||||
|
||||
if (diff < 60) {
|
||||
return Value{ .string = try alloc.dupe(u8, "menos de um minuto") };
|
||||
} else if (diff < 3600) {
|
||||
const mins = diff / 60;
|
||||
const str = if (mins == 1) "1 minuto" else try std.fmt.allocPrint(alloc, "{d} minutos", .{mins});
|
||||
return Value{ .string = str };
|
||||
} else if (diff < 86400) {
|
||||
const hours = diff / 3600;
|
||||
const str = if (hours == 1) "1 hora" else try std.fmt.allocPrint(alloc, "{d} horas", .{hours});
|
||||
return Value{ .string = str };
|
||||
} else {
|
||||
const days = diff / 86400;
|
||||
const str = if (days == 1) "1 dia" else try std.fmt.allocPrint(alloc, "{d} dias", .{days});
|
||||
return Value{ .string = str };
|
||||
}
|
||||
return Value{ .string = try d.timeSince(alloc, now) };
|
||||
}
|
||||
|
||||
fn filter_timeuntil(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
||||
_ = arg;
|
||||
// Reutiliza timesince, mas com sinal invertido
|
||||
const future = switch (value) {
|
||||
.int => |i| @as(i64, i),
|
||||
else => std_time.timestamp(),
|
||||
};
|
||||
const d = time.Time.parse(value.string) catch return value;
|
||||
const now = time.Time.parse(arg.?.string) catch return value;
|
||||
|
||||
const fake_past = Value{ .int = std_time.timestamp() };
|
||||
const since = try filter_timesince(alloc, fake_past, Value{ .int = future });
|
||||
return since;
|
||||
return Value{ .string = try now.timeSince(alloc, d) };
|
||||
}
|
||||
|
||||
fn filter_title(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
||||
|
|
@ -1190,7 +1152,6 @@ fn filter_urlizetrunc(alloc: std.mem.Allocator, value: Value, arg: ?Value) Filte
|
|||
|
||||
const s = try valueToSafeString(alloc, value);
|
||||
|
||||
std.debug.print("{s}\n", .{value.string});
|
||||
var result = std.ArrayList(u8){};
|
||||
|
||||
var i: usize = 0;
|
||||
|
|
@ -1318,6 +1279,20 @@ fn filter_yesno(alloc: std.mem.Allocator, value: Value, arg: ?Value) FilterError
|
|||
}
|
||||
|
||||
// ==================== AUX FUNCTIONS ====================
|
||||
pub fn dateTimeToString(allocator: std.mem.Allocator, value: Value, arg: ?Value) FilterError!Value {
|
||||
if (value.string.len > 0) {
|
||||
const t: time.Time = try time.Time.parse(value.string);
|
||||
const arg_str: Value = arg orelse Value{ .string = "F d, Y" };
|
||||
const format_str: []const u8 = arg_str.string;
|
||||
|
||||
const result: []const u8 = try t.toStringAlloc(allocator, format_str);
|
||||
if (result.len > 0) {
|
||||
return Value{ .string = result };
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
pub fn capFirst(allocator: std.mem.Allocator, input: []const u8) ![]const u8 {
|
||||
if (input.len == 0) return "";
|
||||
|
||||
|
|
@ -1462,7 +1437,7 @@ pub const builtin_filters = std.StaticStringMap(*const FilterFn).initComptime(.{
|
|||
.{ "capfirst", &filter_capfirst },
|
||||
.{ "center", &filter_center },
|
||||
.{ "cut", &filter_cut },
|
||||
// .{ "date", &filter_date },
|
||||
.{ "date", &filter_date },
|
||||
.{ "default", &filter_default },
|
||||
.{ "default_if_none", &filter_default_if_none },
|
||||
.{ "dictsort", &filter_dictsort },
|
||||
|
|
@ -1487,6 +1462,7 @@ pub const builtin_filters = std.StaticStringMap(*const FilterFn).initComptime(.{
|
|||
.{ "ljust", &filter_ljust },
|
||||
.{ "lower", &filter_lower },
|
||||
.{ "make_list", &filter_make_list },
|
||||
.{ "now", &filter_now },
|
||||
.{ "phone2numeric", &filter_phone2numeric },
|
||||
.{ "pluralize", &filter_pluralize },
|
||||
.{ "pprint", &filter_pprint },
|
||||
|
|
@ -1498,9 +1474,9 @@ pub const builtin_filters = std.StaticStringMap(*const FilterFn).initComptime(.{
|
|||
.{ "slugify", &filter_slugify },
|
||||
.{ "stringformat", &filter_stringformat },
|
||||
.{ "striptags", &filter_striptags },
|
||||
// .{ "time", &filter_time },
|
||||
// .{ "timesince", &filter_timesince },
|
||||
// .{ "timeuntil", &filter_timeuntil },
|
||||
.{ "time", &filter_time },
|
||||
.{ "timesince", &filter_timesince },
|
||||
.{ "timeuntil", &filter_timeuntil },
|
||||
.{ "title", &filter_title },
|
||||
.{ "truncatechars", &filter_truncatechars },
|
||||
.{ "truncatechars_html", &filter_truncatechars_html },
|
||||
|
|
|
|||
|
|
@ -3,10 +3,16 @@ const testing = std.testing;
|
|||
const Value = @import("context.zig").Value;
|
||||
const Context = @import("context.zig").Context;
|
||||
const builtin_filters = @import("filters.zig").builtin_filters;
|
||||
const FilterError = @import("filters.zig").FilterError;
|
||||
const filter = @import("filters.zig");
|
||||
const FilterError = filter.FilterError;
|
||||
const time = @import("time.zig");
|
||||
const std_time = std.time;
|
||||
const RelativeDelta = @import("delta.zig").RelativeDelta;
|
||||
|
||||
test "filters upper/lower, capfirst" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("1 - upper/lower, capfirst\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -29,6 +35,9 @@ test "filters upper/lower, capfirst" {
|
|||
}
|
||||
|
||||
test "builtin filters - add" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("2 - add\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
const add = builtin_filters.get("add").?;
|
||||
|
||||
|
|
@ -49,6 +58,9 @@ test "builtin filters - add" {
|
|||
}
|
||||
|
||||
test "builtin filters - default and default_if_none" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("3 - default and default_if_none\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
const default_filter = builtin_filters.get("default").?;
|
||||
const default_if_none = builtin_filters.get("default_if_none").?;
|
||||
|
|
@ -73,6 +85,9 @@ test "builtin filters - default and default_if_none" {
|
|||
}
|
||||
|
||||
test "builtin filters - length" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("4 - length\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
const length = builtin_filters.get("length").?;
|
||||
|
||||
|
|
@ -94,7 +109,10 @@ test "builtin filters - length" {
|
|||
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;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -114,6 +132,9 @@ test "builtin filters - length com dict" {
|
|||
}
|
||||
|
||||
test "builtin filters - first and last" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("6 - first and last\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
const first = builtin_filters.get("first").?;
|
||||
const last = builtin_filters.get("last").?;
|
||||
|
|
@ -143,6 +164,9 @@ test "builtin filters - first and last" {
|
|||
}
|
||||
|
||||
test "builtin filters - join" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("7 - join\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
const join = builtin_filters.get("join").?;
|
||||
|
||||
|
|
@ -159,7 +183,6 @@ test "builtin filters - join" {
|
|||
try ctx.set("mixed", mixed);
|
||||
const mixed_val = ctx.get("mixed").?;
|
||||
|
||||
|
||||
const default_join = try join(ctx.allocator(), list_val, null);
|
||||
const custom_join = try join(ctx.allocator(), list_val, sep_dash);
|
||||
const mixed_join = try join(ctx.allocator(), mixed_val, null);
|
||||
|
|
@ -170,6 +193,9 @@ test "builtin filters - join" {
|
|||
}
|
||||
|
||||
test "builtin filters - yesno" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("8 - yesno\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
const yesno = builtin_filters.get("yesno").?;
|
||||
|
||||
|
|
@ -192,6 +218,9 @@ test "builtin filters - yesno" {
|
|||
}
|
||||
|
||||
test "builtin filters - truncatechars and truncatechars_html" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("9 - truncatechars and truncatechars_html\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
const truncatechars = builtin_filters.get("truncatechars").?;
|
||||
const truncatechars_html = builtin_filters.get("truncatechars_html").?;
|
||||
|
|
@ -218,6 +247,9 @@ test "builtin filters - truncatechars and truncatechars_html" {
|
|||
}
|
||||
|
||||
test "builtin filters - truncatewords" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("10 - truncatewords\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
const truncatewords = builtin_filters.get("truncatewords").?;
|
||||
const truncatewords_html = builtin_filters.get("truncatewords_html").?;
|
||||
|
|
@ -241,6 +273,9 @@ test "builtin filters - truncatewords" {
|
|||
}
|
||||
|
||||
test "builtin filters - slice" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("11 - slice\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
const slice = builtin_filters.get("slice").?;
|
||||
|
||||
|
|
@ -267,6 +302,9 @@ test "builtin filters - slice" {
|
|||
}
|
||||
|
||||
test "builtin filters - safe and force_escape" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("12 - safe and force_escape\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
const safe = builtin_filters.get("safe").?;
|
||||
const force_escape = builtin_filters.get("force_escape").?;
|
||||
|
|
@ -287,6 +325,9 @@ test "builtin filters - safe and force_escape" {
|
|||
}
|
||||
|
||||
test "builtin filters - linebreaksbr and linebreaks" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("13 - linebreaksbr and linebreaks\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
const linebreaksbr = builtin_filters.get("linebreaksbr").?;
|
||||
const linebreaks = builtin_filters.get("linebreaks").?;
|
||||
|
|
@ -306,6 +347,9 @@ test "builtin filters - linebreaksbr and linebreaks" {
|
|||
}
|
||||
|
||||
test "builtin filters - escape and force_escape" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("14 - escape and force_escape\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -326,6 +370,9 @@ test "builtin filters - escape and force_escape" {
|
|||
}
|
||||
|
||||
test "builtin filters - striptags" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("15 - striptags\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -341,6 +388,9 @@ test "builtin filters - striptags" {
|
|||
}
|
||||
|
||||
test "builtin filters - slugify" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("16 - slugify\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -356,6 +406,9 @@ test "builtin filters - slugify" {
|
|||
}
|
||||
|
||||
test "builtin filters - floatformat" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("17 - floatformat\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -384,6 +437,9 @@ test "builtin filters - floatformat" {
|
|||
}
|
||||
|
||||
test "builtin filters - stringformat" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("18 - stringformat\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -404,6 +460,9 @@ test "builtin filters - stringformat" {
|
|||
}
|
||||
|
||||
test "builtin filters - cut" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("19 - cut\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -419,6 +478,9 @@ test "builtin filters - cut" {
|
|||
}
|
||||
|
||||
test "builtin filters - title" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("20 - title\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -434,6 +496,9 @@ test "builtin filters - title" {
|
|||
}
|
||||
|
||||
test "builtin filters - wordcount" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("21 - wordcount\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -449,6 +514,9 @@ test "builtin filters - wordcount" {
|
|||
}
|
||||
|
||||
test "builtin filters - urlencode" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("22 - urlencode\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -464,6 +532,9 @@ test "builtin filters - urlencode" {
|
|||
}
|
||||
|
||||
test "builtin filters - pluralize" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("23 - pluralize\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -489,31 +560,35 @@ test "builtin filters - pluralize" {
|
|||
// 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;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
||||
try ctx.set("quote", "He's a good boy");
|
||||
try ctx.set("texto", "zig");
|
||||
try ctx.set("time", time.Time.new(2026, 1, 1, 0, 0, 0));
|
||||
|
||||
const v_quote = ctx.get("quote").?;
|
||||
const v_texto = ctx.get("texto").?;
|
||||
|
||||
const addslashes = builtin_filters.get("addslashes").?;
|
||||
const center = builtin_filters.get("center").?;
|
||||
// const date = builtin_filters.get("date").?;
|
||||
|
||||
const slashed = try addslashes(ctx.allocator(), v_quote, null);
|
||||
const centered = try center(ctx.allocator(), v_texto, Value{ .int = 10 });
|
||||
// const formatted = try date(ctx.allocator(), Value{ .int = 0 }, Value{ .string = "Y-m-d" });
|
||||
|
||||
try testing.expectEqualStrings("He\\'s a good boy", slashed.string);
|
||||
try testing.expectEqualStrings(" zig ", centered.string);
|
||||
// try testing.expect(std.mem.startsWith(u8, formatted.string, "2026"));
|
||||
}
|
||||
|
||||
test "builtin filters - dictsort and dictsortreversed" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("25 - dictsort and dictsortreversed\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -544,6 +619,9 @@ test "builtin filters - dictsort and dictsortreversed" {
|
|||
}
|
||||
|
||||
test "builtin filters - divisibleby, escapejs, filesizeformat, get_digit, json_script" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("26 - divisibleby, escapejs, filesizeformat, get_digit, json_script\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -579,6 +657,9 @@ test "builtin filters - divisibleby, escapejs, filesizeformat, get_digit, json_s
|
|||
}
|
||||
|
||||
test "builtin filters - escapeseq, iriencode, linenumbers" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("27 - escapeseq, iriencode, linenumbers\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -601,6 +682,9 @@ test "builtin filters - escapeseq, iriencode, linenumbers" {
|
|||
}
|
||||
|
||||
test "builtin filters - ljust, rjust, center" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("28 - ljust, rjust, center\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -623,6 +707,9 @@ test "builtin filters - ljust, rjust, center" {
|
|||
}
|
||||
|
||||
test "builtin filters - make_list, phone2numeric, pprint" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("29 - make_list, phone2numeric, pprint\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -648,6 +735,9 @@ test "builtin filters - make_list, phone2numeric, pprint" {
|
|||
}
|
||||
|
||||
test "builtin filters - random, safeseq" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("30 - random, safeseq\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -670,29 +760,69 @@ test "builtin filters - random, safeseq" {
|
|||
try testing.expect(safe == .list);
|
||||
}
|
||||
|
||||
// test "builtin filters - date, time, timesince, timeuntil" {
|
||||
// const alloc = testing.allocator;
|
||||
// var ctx = Context.init(alloc);
|
||||
// defer ctx.deinit();
|
||||
//
|
||||
// const now = std_time.timestamp();
|
||||
//
|
||||
// // const date = builtin_filters.get("date").?;
|
||||
// const time = builtin_filters.get("time").?;
|
||||
// const timesince = builtin_filters.get("timesince").?;
|
||||
// // const timeuntil = builtin_filters.get("timeuntil").?;
|
||||
//
|
||||
// // const d = try date(ctx.allocator(), Value{ .int = now }, Value{ .string = "d/m/Y" });
|
||||
// const t = try time(ctx.allocator(), Value{ .int = now }, Value{ .string = "H:i" });
|
||||
//
|
||||
// // try testing.expect(d.string.len > 0);
|
||||
// try testing.expect(t.string.len > 0);
|
||||
//
|
||||
// const since = try timesince(ctx.allocator(), Value{ .int = now - 3600 }, null);
|
||||
// try testing.expect(std.mem.indexOf(u8, since.string, "hora") != null);
|
||||
// }
|
||||
test "builtin filters - date, now, time, timesince, timeuntil" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("31 - date, now, time, timesince, timeuntil\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
||||
const times = [_]time.Time{
|
||||
time.Time.new(2026, 1, 2, 13, 15, 10),
|
||||
time.Time.new(2026, 1, 6, 19, 25, 0),
|
||||
time.Time.new(2026, 1, 2, 13, 35, 0),
|
||||
time.Time.new(2026, 1, 2, 13, 15, 19),
|
||||
time.Time.new(2025, 1, 2, 13, 15,19),
|
||||
time.Time.new(2024, 7, 5, 19, 4, 2),
|
||||
};
|
||||
|
||||
const date_filter = builtin_filters.get("date").?;
|
||||
const now_filter = builtin_filters.get("now").?;
|
||||
const time_filter = builtin_filters.get("time").?;
|
||||
const timesince_filter = builtin_filters.get("timesince").?;
|
||||
const timeuntil_filer = builtin_filters.get("timeuntil").?;
|
||||
|
||||
try ctx.set("dates", times);
|
||||
const dates = ctx.get("dates").?;
|
||||
|
||||
const date_formated = try date_filter(ctx.allocator(), dates.list[0], Value{ .string = "Y-m-d" });
|
||||
const now_formated = try now_filter(ctx.allocator(), dates.list[0], Value{ .string = "Y-m-d" });
|
||||
const time_formated = try time_filter(ctx.allocator(), dates.list[0], Value{ .string = "H:i:s" });
|
||||
const timesince_formated_1 = try timesince_filter(ctx.allocator(), dates.list[0], dates.list[1]);
|
||||
const timesince_formated_2 = try timesince_filter(ctx.allocator(), dates.list[0], dates.list[2]);
|
||||
const timesince_formated_3 = try timesince_filter(ctx.allocator(), dates.list[0], dates.list[3]);
|
||||
const timesince_formated_4 = try timesince_filter(ctx.allocator(), dates.list[4], dates.list[0]);
|
||||
const timesince_formated_5 = try timesince_filter(ctx.allocator(), dates.list[5], dates.list[0]);
|
||||
|
||||
const timeuntil_formated_1 = try timeuntil_filer(ctx.allocator(), dates.list[1], dates.list[0]);
|
||||
const timeuntil_formated_2 = try timeuntil_filer(ctx.allocator(), dates.list[2], dates.list[0]);
|
||||
const timeuntil_formated_3 = try timeuntil_filer(ctx.allocator(), dates.list[3], dates.list[0]);
|
||||
const timeuntil_formated_4 = try timeuntil_filer(ctx.allocator(), dates.list[0], dates.list[4]);
|
||||
const timeuntil_formated_5 = try timeuntil_filer(ctx.allocator(), dates.list[0], dates.list[5]);
|
||||
|
||||
try testing.expectEqualStrings("2026-01-02", date_formated.string);
|
||||
try testing.expect(isDateFormat(now_formated.string));
|
||||
try testing.expectEqualStrings("13:15:10", time_formated.string);
|
||||
|
||||
try testing.expectEqualStrings("4 days, 6 hours",timesince_formated_1.string);
|
||||
try testing.expectEqualStrings("19 minutes",timesince_formated_2.string);
|
||||
try testing.expectEqualStrings("0 minutes",timesince_formated_3.string);
|
||||
try testing.expectEqualStrings("11 months, 4 weeks",timesince_formated_4.string);
|
||||
try testing.expectEqualStrings("1 year, 5 months",timesince_formated_5.string);
|
||||
|
||||
try testing.expectEqualStrings("4 days, 6 hours",timeuntil_formated_1.string);
|
||||
try testing.expectEqualStrings("19 minutes",timeuntil_formated_2.string);
|
||||
try testing.expectEqualStrings("0 minutes",timeuntil_formated_3.string);
|
||||
try testing.expectEqualStrings("11 months, 4 weeks",timeuntil_formated_4.string);
|
||||
try testing.expectEqualStrings("1 year, 5 months",timeuntil_formated_5.string);
|
||||
|
||||
}
|
||||
|
||||
test "builtin filters - urlize, urlizetrunc, wordwrap, unordered_list" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("32 - urlize, urlizetrunc, wordwrap, unordered_list\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -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 wrapped = try wordwrap(ctx.allocator(), Value{ .string = long_text }, Value{ .int = 20 });
|
||||
try testing.expect(std.mem.indexOf(u8, wrapped.string, "\n") != null);
|
||||
|
||||
}
|
||||
|
||||
test "builtin filters - unordered_list" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("33 - unordered_list\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
||||
|
||||
const list = [_]Value{ Value{ .string = "item1" }, Value{ .string = "item2" } };
|
||||
try ctx.set("lista", list);
|
||||
|
||||
|
|
@ -737,6 +868,15 @@ test "builtin filters - unordered_list" {
|
|||
\\<li>item2</li>
|
||||
\\</ul>
|
||||
;
|
||||
std.debug.print("lista gerada: {any}\n", .{ul.string});
|
||||
try testing.expectEqualStrings(expected, ul.string);
|
||||
}
|
||||
|
||||
fn isDateFormat(txt: []const u8) bool {
|
||||
if (txt.len != 10) return false;
|
||||
if (txt[4] != '-' or txt[7] != '-') return false;
|
||||
for (txt, 0..) |c, i| {
|
||||
if (i == 4 or i == 7) continue;
|
||||
if (!std.ascii.isDigit(c)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
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);
|
||||
}
|
||||
74
src/main.zig
74
src/main.zig
|
|
@ -9,33 +9,40 @@ pub fn main() !void {
|
|||
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||
const alloc = arena.allocator();
|
||||
|
||||
const base =
|
||||
\\<html>
|
||||
\\<head><title>{% block title %}Título Padrão{% endblock %}</title></head>
|
||||
\\<body>
|
||||
\\{% block content %}Conteúdo padrão{% endblock %}
|
||||
\\</body>
|
||||
\\</html>
|
||||
;
|
||||
// const base =
|
||||
// \\<html>
|
||||
// \\<head><title>{% block title %}Título Padrão{% endblock %}</title></head>
|
||||
// \\<body>
|
||||
// \\{% block content %}Conteúdo padrão{% endblock %}
|
||||
// \\</body>
|
||||
// \\</html>
|
||||
// ;
|
||||
//
|
||||
// const child =
|
||||
// \\{% extends "base.html" %}
|
||||
// \\{% block title %}Meu Título{% endblock %}
|
||||
// \\{% block content %}
|
||||
// \\Olá {{ nome }}!
|
||||
// \\{% endblock %}
|
||||
// ;
|
||||
//
|
||||
// try std.fs.cwd().writeFile(.{
|
||||
// .sub_path = "base.html",
|
||||
// .data = base,
|
||||
// });
|
||||
// try std.fs.cwd().writeFile(.{
|
||||
// .sub_path = "child.html",
|
||||
// .data = child,
|
||||
// });
|
||||
// defer std.fs.cwd().deleteFile("base.html") catch {};
|
||||
// defer std.fs.cwd().deleteFile("child.html") catch {};
|
||||
|
||||
const child =
|
||||
\\{% extends "base.html" %}
|
||||
\\{% block title %}Meu Título{% endblock %}
|
||||
\\{% block content %}
|
||||
\\Olá {{ nome }}!
|
||||
\\{% endblock %}
|
||||
;
|
||||
|
||||
try std.fs.cwd().writeFile(.{
|
||||
.sub_path = "base.html",
|
||||
.data = base,
|
||||
});
|
||||
try std.fs.cwd().writeFile(.{
|
||||
.sub_path = "child.html",
|
||||
.data = child,
|
||||
});
|
||||
defer std.fs.cwd().deleteFile("base.html") catch {};
|
||||
defer std.fs.cwd().deleteFile("child.html") catch {};
|
||||
const User =struct {
|
||||
name: []const u8,
|
||||
email: []const u8,
|
||||
notifications: i64 = 0
|
||||
};
|
||||
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -45,12 +52,27 @@ pub fn main() !void {
|
|||
|
||||
const renderer = Renderer.init(&ctx, &cache);
|
||||
|
||||
const user = User{
|
||||
.name = "Lucas",
|
||||
.email = "lucas@email",
|
||||
.notifications = 5
|
||||
};
|
||||
|
||||
const itens = [3][]const u8{"Livro", "Caneta", "Caderno"};
|
||||
|
||||
try ctx.set("nome", "Lucas");
|
||||
try ctx.set("user", user);
|
||||
try ctx.set("msg", "Bazinga!");
|
||||
try ctx.set("itens", itens);
|
||||
|
||||
for(ctx.get("itens").?.list) |item| {
|
||||
std.debug.print(" - {s}\n", .{item.string});
|
||||
}
|
||||
|
||||
var buffer = std.ArrayList(u8){};
|
||||
defer buffer.deinit(ctx.allocator());
|
||||
|
||||
try renderer.render("child.html", buffer.writer(ctx.allocator()));
|
||||
try renderer.render("home.html", buffer.writer(ctx.allocator()));
|
||||
|
||||
const output = buffer.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;
|
||||
}
|
||||
3037
src/parser.zig
3037
src/parser.zig
File diff suppressed because it is too large
Load diff
1708
src/parser_test.zig
1708
src/parser_test.zig
File diff suppressed because it is too large
Load diff
569
src/renderer.zig
569
src/renderer.zig
|
|
@ -10,37 +10,19 @@ const builtin_filters = @import("filters.zig").builtin_filters;
|
|||
const FilterError = @import("filters.zig").FilterError;
|
||||
const parser = @import("parser.zig");
|
||||
const TemplateCache = @import("cache.zig").TemplateCache;
|
||||
const time = @import("time.zig");
|
||||
const lorem = @import("lorem.zig");
|
||||
const icons = @import("svg/icons.zig");
|
||||
|
||||
pub const RenderError = FilterError || error{
|
||||
OutOfMemory,
|
||||
pub const RenderError = error{
|
||||
InvalidCharacter,
|
||||
InvalidSyntax,
|
||||
UnknownVariable,
|
||||
UnknownFilter,
|
||||
InvalidTemplate,
|
||||
BlockNotFound,
|
||||
CircularExtends,
|
||||
FileNotFound,
|
||||
AccessDenied,
|
||||
FileTooBig,
|
||||
NoSpaceLeft,
|
||||
OutOfMemory,
|
||||
Overflow,
|
||||
Unexpected,
|
||||
UnclosedTag,
|
||||
InvalidAutoescapeArgument,
|
||||
UnclosedVariable,
|
||||
UnclosedBlock,
|
||||
UnclosedComment,
|
||||
InvalidAssignmentSyntax,
|
||||
UnclosedQuoteInAssignment,
|
||||
InvalidForSyntax,
|
||||
UnclosedVerbatim,
|
||||
InvalidUrlSyntax,
|
||||
UnclosedQuoteInUrl,
|
||||
InvalidDebugArgs,
|
||||
InvalidRegroupSyntax,
|
||||
InvalidWidthRatioSyntax,
|
||||
InvalidTemplateTag,
|
||||
InvalidCsrfTokenArgs,
|
||||
};
|
||||
UnsupportedExpression,
|
||||
// } || FilterError || parser.ParserError || icons.SvgError || std.fs.File.OpenError;
|
||||
} || FilterError || parser.ParserError || std.fs.File.OpenError;
|
||||
|
||||
pub const Renderer = struct {
|
||||
context: *Context,
|
||||
|
|
@ -58,19 +40,23 @@ pub const Renderer = struct {
|
|||
fn checkForExtends(self: *const Renderer, nodes: []parser.Node) ?parser.Node {
|
||||
_ = self;
|
||||
for (nodes) |n| {
|
||||
if (n.type == .extends) return n;
|
||||
// if (n.type == .extends) return n;
|
||||
if (n.type == .tag and n.tag.?.kind == .extends) return n;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn readTemplateFile(self: *const Renderer, path: []const u8) RenderError![]const u8 {
|
||||
fn readTemplateFile(self: *const Renderer, path: []const u8) ![]const u8 {
|
||||
const max_size = 10 * 1024 * 1024;
|
||||
return std.fs.cwd().readFileAlloc(self.allocator, path, max_size) catch |err| switch (err) {
|
||||
error.FileNotFound => return RenderError.FileNotFound,
|
||||
error.AccessDenied => return RenderError.AccessDenied,
|
||||
error.FileTooBig => return RenderError.FileTooBig,
|
||||
error.NoSpaceLeft => return RenderError.NoSpaceLeft,
|
||||
error.OutOfMemory => return RenderError.OutOfMemory,
|
||||
|
||||
const full_path = try std.fs.path.join(self.allocator, &.{ self.cache.default_path.?, path });
|
||||
defer self.allocator.free(full_path);
|
||||
|
||||
const file = try std.fs.cwd().openFile(full_path, .{});
|
||||
defer file.close();
|
||||
|
||||
// return std.fs.cwd().readFileAlloc(self.allocator, path, max_size) catch |err| switch (err) {
|
||||
return file.readToEndAlloc(self.allocator, max_size) catch |err| switch (err) {
|
||||
else => return RenderError.Unexpected,
|
||||
};
|
||||
}
|
||||
|
|
@ -79,7 +65,8 @@ pub const Renderer = struct {
|
|||
const extends_node = self.checkForExtends(nodes);
|
||||
|
||||
if (extends_node) |ext| {
|
||||
const base_template = try self.readTemplateFile(ext.extends.?.parent_name);
|
||||
// const base_template = try self.readTemplateFile(ext.extends.?.parent_name);
|
||||
const base_template = try self.readTemplateFile(ext.tag.?.body.extends.parent_name);
|
||||
defer self.allocator.free(base_template);
|
||||
|
||||
var base_parser = parser.Parser.init(base_template);
|
||||
|
|
@ -97,16 +84,17 @@ pub const Renderer = struct {
|
|||
}
|
||||
|
||||
fn renderTemplate(self: *const Renderer, template: []const u8, writer: anytype, cache_key: ?[]const u8) RenderError!void {
|
||||
_ = cache_key;
|
||||
var arena = std.heap.ArenaAllocator.init(self.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
if (cache_key) |ck| {
|
||||
if (self.cache.get(ck)) |cached_nodes| {
|
||||
try self.renderNodes(alloc, cached_nodes, writer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// if (cache_key) |ck| {
|
||||
// if (self.cache.get(ck)) |cached_nodes| {
|
||||
// try self.renderNodes(alloc, cached_nodes, writer);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
var p = parser.Parser.init(template);
|
||||
const nodes = try p.parse(alloc);
|
||||
|
|
@ -115,16 +103,15 @@ pub const Renderer = struct {
|
|||
alloc.free(nodes);
|
||||
}
|
||||
|
||||
if (cache_key) |ck| {
|
||||
var alc = self.cache.allocator();
|
||||
var cached_nodes = try alc.alloc(parser.Node, nodes.len);
|
||||
errdefer alc.free(cached_nodes);
|
||||
for (nodes, 0..) |node, i| {
|
||||
cached_nodes[i] = try node.clone(self.allocator);
|
||||
std.debug.print("clonou {any}\n", .{cached_nodes[i]});
|
||||
}
|
||||
try self.cache.add(ck, nodes);
|
||||
}
|
||||
// if (cache_key) |ck| {
|
||||
// var alc = self.cache.allocator();
|
||||
// var cached_nodes = try alc.alloc(parser.Node, nodes.len);
|
||||
// errdefer alc.free(cached_nodes);
|
||||
// for (nodes, 0..) |node, i| {
|
||||
// cached_nodes[i] = try node.clone(self.allocator);
|
||||
// }
|
||||
// try self.cache.add(ck, nodes);
|
||||
// }
|
||||
|
||||
return try self.renderNodes(alloc, nodes, writer);
|
||||
}
|
||||
|
|
@ -142,24 +129,22 @@ pub const Renderer = struct {
|
|||
|
||||
fn renderWithInheritance(self: *const Renderer, alloc: Allocator, base_nodes: []parser.Node, child_nodes: []parser.Node, writer: anytype) RenderError!void {
|
||||
for (base_nodes) |base_node| {
|
||||
if (base_node.type == .block) {
|
||||
const block_name = base_node.block.?.name;
|
||||
if (base_node.type == .tag and base_node.tag.?.kind == .block) {
|
||||
const block_name = base_node.tag.?.body.block.name;
|
||||
|
||||
// Procura no filho
|
||||
const child_block = self.findChildBlock(child_nodes, block_name);
|
||||
if (child_block) |child| {
|
||||
// Renderiza o filho, passando o conteúdo do pai para block.super
|
||||
for (child.body) |child_node| {
|
||||
try self.renderNode(alloc, child_nodes, child_node, writer, null, base_node.block.?.body);
|
||||
try self.renderNode(alloc, child_nodes, child_node, writer, null, base_node.tag.?.body.block.body);
|
||||
}
|
||||
} else {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Qualquer outra coisa no base
|
||||
try self.renderNode(alloc, child_nodes, base_node, writer, null, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -182,7 +167,14 @@ pub const Renderer = struct {
|
|||
for (node.variable.?.filters) |f| {
|
||||
const filter_fn = builtin_filters.get(f.name) orelse return error.UnknownFilter;
|
||||
|
||||
const arg = if (f.arg) |a| Value{ .string = a } else null;
|
||||
// const arg = if (f.arg) |a| Value{ .string = a } else null;
|
||||
var arg: Value = Value.null;
|
||||
if (f.arg) |a| {
|
||||
arg = Value{ .string = a };
|
||||
const result = try std.fmt.parseInt(i64, a, 10);
|
||||
if (std.math.maxInt(i64) < result) return error.Overflow;
|
||||
arg = Value{ .int = result };
|
||||
}
|
||||
value = try filter_fn(alloc, value, arg);
|
||||
|
||||
if (std.mem.eql(u8, f.name, "safe")) is_safe = true;
|
||||
|
|
@ -200,88 +192,292 @@ pub const Renderer = struct {
|
|||
try self.valueToString(alloc, &buf, value);
|
||||
try writer.writeAll(buf.items);
|
||||
},
|
||||
.if_block => {
|
||||
const condition = self.evaluateCondition(node.@"if".?.condition) catch return false;
|
||||
.tag => {
|
||||
switch (node.tag.?.kind) {
|
||||
.if_block => {
|
||||
const condition = try self.evaluateCondition(alloc, node.tag.?.body.@"if".condition, context);
|
||||
|
||||
if (condition) {
|
||||
for (node.@"if".?.true_body) |child| {
|
||||
try self.renderNode(alloc, nodes, child, writer, null, null);
|
||||
}
|
||||
} else {
|
||||
for (node.@"if".?.false_body) |child| {
|
||||
try self.renderNode(alloc, nodes, child, writer, null, null);
|
||||
}
|
||||
if (condition) {
|
||||
for (node.tag.?.body.@"if".true_body) |child| {
|
||||
try self.renderNode(alloc, nodes, child, writer, context, null);
|
||||
}
|
||||
} else {
|
||||
for (node.tag.?.body.@"if".false_body) |child| {
|
||||
try self.renderNode(alloc, nodes, child, writer, context, null);
|
||||
}
|
||||
}
|
||||
},
|
||||
.include => {
|
||||
const included_template = try self.readTemplateFile(node.tag.?.body.include.template_path);
|
||||
defer alloc.free(included_template);
|
||||
|
||||
var included_parser = parser.Parser.init(included_template);
|
||||
const included_nodes = try included_parser.parse(alloc);
|
||||
defer {
|
||||
for (included_nodes) |n| n.deinit(alloc);
|
||||
alloc.free(included_nodes);
|
||||
}
|
||||
|
||||
// Renderiza o include no contexto atual (sem novo contexto)
|
||||
for (included_nodes) |included_node| {
|
||||
try self.renderNode(alloc, nodes, included_node, writer, context, null);
|
||||
}
|
||||
},
|
||||
.for_block => {
|
||||
const list_value = self.context.get(node.tag.?.body.@"for".iterable) orelse Value.null;
|
||||
const list = switch (list_value) {
|
||||
.list => |l| l,
|
||||
else => return,
|
||||
};
|
||||
|
||||
for (list, 0..) |item, i| {
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
||||
try ctx.set(node.tag.?.body.@"for".loop_var, item);
|
||||
try ctx.set("forloop.counter", i + 1);
|
||||
try ctx.set("forloop.counter0", i);
|
||||
try ctx.set("forloop.revcounter", (list.len - i));
|
||||
try ctx.set("forloop.revcounter0", (list.len - i) - 1);
|
||||
try ctx.set("forloop.first", i == 0);
|
||||
try ctx.set("forloop.last", i == (list.len - 1));
|
||||
try ctx.set("forloop.length", list.len);
|
||||
// forloop.counter
|
||||
// The current iteration of the loop (1-indexed)
|
||||
// forloop.counter0
|
||||
// The current iteration of the loop (0-indexed)
|
||||
// forloop.revcounter
|
||||
// The number of iterations from the end of the loop (1-indexed)
|
||||
// forloop.revcounter0
|
||||
// The number of iterations from the end of the loop (0-indexed)
|
||||
// forloop.first
|
||||
// True if this is the first time through the loop
|
||||
// forloop.last
|
||||
// True if this is the last time through the loop
|
||||
// forloop.length
|
||||
|
||||
for (node.tag.?.body.@"for".body) |child| {
|
||||
try self.renderNode(alloc, nodes, child, writer, &ctx, null);
|
||||
}
|
||||
|
||||
if (node.tag.?.body.@"for".body.len == 0) {
|
||||
for (node.tag.?.body.@"for".empty_body) |child| {
|
||||
try self.renderNode(alloc, nodes, child, writer, &ctx, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
.super => {
|
||||
if (parent_block_nodes) |parent| {
|
||||
for (parent) |child| {
|
||||
try self.renderNode(alloc, nodes, child, writer, null, null);
|
||||
}
|
||||
}
|
||||
},
|
||||
.block => {
|
||||
for (node.tag.?.body.block.body) |child| {
|
||||
const parent_content = parent_block_nodes orelse node.tag.?.body.block.body;
|
||||
try self.renderNode(alloc, nodes, child, writer, null, parent_content);
|
||||
}
|
||||
},
|
||||
.widthratio => {
|
||||
var divisor: Value = Value{ .float = 1.0 };
|
||||
var float_divisor: f64 = 1.0;
|
||||
|
||||
var value: Value = Value{ .float = 1.0 };
|
||||
var float_value: f64 = 1.0;
|
||||
|
||||
var max_value: Value = Value{ .float = 1.0 };
|
||||
var float_max_value: f64 = 1.0;
|
||||
|
||||
if (!std.mem.eql(u8, node.tag.?.body.widthratio.value, "")) {
|
||||
value = Value{ .string = node.tag.?.body.widthratio.value };
|
||||
if (self.context.get(node.tag.?.body.widthratio.value)) |v| {
|
||||
value = v;
|
||||
}
|
||||
float_value = switch (value) {
|
||||
.int => @as(f64, @floatFromInt(value.int)),
|
||||
.float => value.float,
|
||||
.string => std.fmt.parseFloat(f64, value.string) catch 1.0,
|
||||
else => 1.0,
|
||||
};
|
||||
}
|
||||
|
||||
if (!std.mem.eql(u8, node.tag.?.body.widthratio.max_value, "")) {
|
||||
max_value = Value{ .string = node.tag.?.body.widthratio.max_value };
|
||||
if (self.context.get(node.tag.?.body.widthratio.max_value)) |v| {
|
||||
max_value = v;
|
||||
}
|
||||
float_max_value = switch (max_value) {
|
||||
.int => @as(f64, @floatFromInt(max_value.int)),
|
||||
.float => max_value.float,
|
||||
.string => std.fmt.parseFloat(f64, max_value.string) catch 1.0,
|
||||
else => 1.0,
|
||||
};
|
||||
}
|
||||
|
||||
if (node.tag.?.body.widthratio.divisor) |div| {
|
||||
divisor = Value{ .string = div };
|
||||
if (self.context.get(div)) |d| {
|
||||
divisor = d;
|
||||
}
|
||||
float_divisor = switch (divisor) {
|
||||
.int => @as(f64, @floatFromInt(divisor.int)),
|
||||
.float => divisor.float,
|
||||
.string => std.fmt.parseFloat(f64, divisor.string) catch 0.0,
|
||||
else => 1.0,
|
||||
};
|
||||
}
|
||||
|
||||
const ratio = (float_value / float_max_value) * float_divisor;
|
||||
|
||||
try writer.writeAll(std.fmt.allocPrint(alloc, "{d}", .{ratio}) catch "0");
|
||||
},
|
||||
.now => {
|
||||
var format: []const u8 = node.tag.?.body.now.format;
|
||||
if (format.len == 0) format = "Y-m-d H:i:s";
|
||||
const datetime = try time.Time.now().toStringAlloc(alloc, format);
|
||||
try writer.writeAll(datetime);
|
||||
},
|
||||
.csrf_token => {
|
||||
const token = self.context.get("csrf_token");
|
||||
if (token == null) return;
|
||||
try writer.writeAll(std.fmt.allocPrint(alloc, "<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;
|
||||
}
|
||||
},
|
||||
.svg => {
|
||||
const svg_kind = node.tag.?.body.svg.kind;
|
||||
const svg_name = node.tag.?.body.svg.name;
|
||||
|
||||
if (self.cache.icons.?.getIcon(alloc, svg_kind, svg_name)) |svg_content| {
|
||||
try writer.writeAll("<div class=\"svg-container\">");
|
||||
try writer.writeAll(svg_content);
|
||||
try writer.writeAll("</div>");
|
||||
} else {
|
||||
try writer.writeAll(icons.fallback_svg);
|
||||
// Opcional: log ou comentário de debug
|
||||
// try writer.print("<!-- SVG não encontrado: {s}/{s} -->", .{svg_kind, svg_name});
|
||||
}
|
||||
return;
|
||||
},
|
||||
else => {
|
||||
std.debug.print("PANIC: unknown node type {d}\n", .{@intFromEnum(node.type)});
|
||||
// @panic("unknown node type");
|
||||
try writer.writeAll("<!-- tag não suportada: ");
|
||||
// try writer.writeAll(try std.fmt.allocPrint(alloc, "{any}", .{node.tag.?.kind}));
|
||||
try writer.writeAll(" -->");
|
||||
},
|
||||
}
|
||||
},
|
||||
.include => {
|
||||
const included_template = try self.readTemplateFile(node.include.?.template_name);
|
||||
defer alloc.free(included_template);
|
||||
|
||||
var included_parser = parser.Parser.init(included_template);
|
||||
const included_nodes = try included_parser.parse(alloc);
|
||||
defer {
|
||||
for (included_nodes) |n| n.deinit(alloc);
|
||||
alloc.free(included_nodes);
|
||||
}
|
||||
|
||||
// Renderiza o include no contexto atual (sem novo contexto)
|
||||
for (included_nodes) |included_node| {
|
||||
try self.renderNode(alloc, nodes, included_node, writer, context, null);
|
||||
}
|
||||
},
|
||||
.for_block => {
|
||||
const list_value = self.context.get(node.@"for".?.iterable) orelse Value.null;
|
||||
const list = switch (list_value) {
|
||||
.list => |l| l,
|
||||
else => return,
|
||||
};
|
||||
|
||||
for (list) |item| {
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
||||
try ctx.set(node.@"for".?.loop_var, item);
|
||||
|
||||
for (node.@"for".?.body) |child| {
|
||||
try self.renderNode(alloc, nodes, child, writer, &ctx, null);
|
||||
}
|
||||
|
||||
for (node.@"for".?.empty_body) |child| {
|
||||
try self.renderNode(alloc, nodes, child, writer, &ctx, null);
|
||||
}
|
||||
}
|
||||
},
|
||||
.super => {
|
||||
if (parent_block_nodes) |parent| {
|
||||
for (parent) |child| {
|
||||
try self.renderNode(alloc, nodes, child, writer, null, null);
|
||||
}
|
||||
}
|
||||
},
|
||||
.block => {
|
||||
for (node.block.?.body) |child| {
|
||||
const parent_content = parent_block_nodes orelse node.block.?.body;
|
||||
try self.renderNode(alloc, nodes, child, writer, null, parent_content);
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn resolveStringVariable(self: *const Renderer, value: []const u8) ?Value {
|
||||
_ = self;
|
||||
if (std.mem.eql(u8, value, "true")) return Value{ .bool = true };
|
||||
if (std.mem.eql(u8, value, "false")) return Value{ .bool = false };
|
||||
const is_int = std.fmt.parseInt(i64, value, 10) catch |err| switch (err) {
|
||||
error.InvalidCharacter => null,
|
||||
error.Overflow => null,
|
||||
};
|
||||
if (is_int != null) return Value{ .int = is_int.? };
|
||||
|
||||
const is_float = std.fmt.parseFloat(f64, value) catch |err| switch (err) {
|
||||
error.InvalidCharacter => null,
|
||||
};
|
||||
if (is_float != null) return Value{ .float = is_float.? };
|
||||
return Value{ .string = value };
|
||||
}
|
||||
|
||||
fn findChildBlock(self: *const Renderer, nodes: []parser.Node, name: []const u8) ?parser.BlockNode {
|
||||
_ = self;
|
||||
for (nodes) |n| {
|
||||
if (n.type != .block) continue;
|
||||
if (std.mem.eql(u8, n.block.?.name, name)) return n.block.?;
|
||||
if (n.type != .tag) continue;
|
||||
if (n.tag.?.kind != .block) continue;
|
||||
if (std.mem.eql(u8, n.tag.?.body.block.name, name)) return n.tag.?.body.block;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn valueToString(self: *const Renderer, alloc: Allocator, buf: *ArrayListUnmanaged(u8), value: Value) !void {
|
||||
fn escapeHtml(self: *const Renderer, value: Value) !Value {
|
||||
const s = switch (value) {
|
||||
.string => |str| str,
|
||||
else => return value,
|
||||
};
|
||||
|
||||
var result = std.ArrayList(u8){};
|
||||
|
||||
for (s) |c| {
|
||||
switch (c) {
|
||||
'&' => try result.appendSlice(self.allocator, "&"),
|
||||
'<' => try result.appendSlice(self.allocator, "<"),
|
||||
'>' => try result.appendSlice(self.allocator, ">"),
|
||||
'"' => try result.appendSlice(self.allocator, """),
|
||||
'\'' => try result.appendSlice(self.allocator, "'"),
|
||||
else => try result.append(self.allocator, c),
|
||||
}
|
||||
}
|
||||
|
||||
return Value{ .string = try result.toOwnedSlice(self.allocator) };
|
||||
}
|
||||
|
||||
fn valueToString(self: *const Renderer, alloc: Allocator, buf: *ArrayListUnmanaged(u8), value: Value) RenderError!void {
|
||||
_ = self;
|
||||
var w = buf.writer(alloc);
|
||||
switch (value) {
|
||||
.null => try w.writeAll("null"),
|
||||
.null => try w.writeAll(""),
|
||||
.bool => |b| try w.print("{}", .{b}),
|
||||
.int => |n| try w.print("{d}", .{n}),
|
||||
.float => |f| try w.print("{d}", .{f}),
|
||||
|
|
@ -291,7 +487,7 @@ pub const Renderer = struct {
|
|||
}
|
||||
}
|
||||
|
||||
fn evaluateCondition(self: *const Renderer, expr: []const u8) !bool {
|
||||
fn evaluateCondition_bkp(self: *const Renderer, expr: []const u8) RenderError!bool {
|
||||
const value = self.context.get(expr) orelse Value.null;
|
||||
return switch (value) {
|
||||
.bool => |b| b,
|
||||
|
|
@ -303,4 +499,123 @@ pub const Renderer = struct {
|
|||
.null => false,
|
||||
};
|
||||
}
|
||||
|
||||
fn evaluateCondition(self: *const Renderer, allocator: Allocator, expr: []const u8, context: ?*Context) RenderError!bool {
|
||||
const trimmed = std.mem.trim(u8, expr, " \t\r\n");
|
||||
if (trimmed.len == 0) return false;
|
||||
|
||||
var parts = std.mem.splitScalar(u8, trimmed, ' ');
|
||||
|
||||
// Coleta tokens não vazios
|
||||
var tokens = std.ArrayList([]const u8){};
|
||||
defer tokens.deinit(allocator);
|
||||
|
||||
while (parts.next()) |part| {
|
||||
const t = std.mem.trim(u8, part, " \t\r\n");
|
||||
if (t.len > 0) try tokens.append(allocator, t);
|
||||
}
|
||||
|
||||
// Caso simples: só nome de variável
|
||||
if (tokens.items.len == 1) {
|
||||
const value = self.context.get(tokens.items[0]) orelse Value.null;
|
||||
return isTruthy(value);
|
||||
}
|
||||
|
||||
// Caso especial: "not variavel"
|
||||
if (tokens.items.len == 2 and std.mem.eql(u8, tokens.items[0], "not")) {
|
||||
const value = self.context.get(tokens.items[1]) orelse Value.null;
|
||||
return !isTruthy(value);
|
||||
}
|
||||
|
||||
// Caso com operadores de comparação: var op valor
|
||||
if (tokens.items.len == 3) {
|
||||
const left = tokens.items[0];
|
||||
const op = tokens.items[1];
|
||||
const right_str = tokens.items[2];
|
||||
|
||||
var left_value: Value = Value.null;
|
||||
if (context) |ctx| {
|
||||
left_value = ctx.get(left) orelse Value.null;
|
||||
}
|
||||
if (left_value == Value.null) left_value = self.context.get(left) orelse Value.null;
|
||||
|
||||
const right_value = parseLiteral(right_str);
|
||||
|
||||
if (std.mem.eql(u8, op, ">")) return compare(left_value, right_value, .gt);
|
||||
if (std.mem.eql(u8, op, "<")) return compare(left_value, right_value, .lt);
|
||||
if (std.mem.eql(u8, op, ">=")) return compare(left_value, right_value, .ge);
|
||||
if (std.mem.eql(u8, op, "<=")) return compare(left_value, right_value, .le);
|
||||
if (std.mem.eql(u8, op, "==")) return compare(left_value, right_value, .eq);
|
||||
if (std.mem.eql(u8, op, "!=")) return compare(left_value, right_value, .ne);
|
||||
|
||||
return error.InvalidSyntax;
|
||||
}
|
||||
|
||||
// Caso mais complexo (and/or/not composto) - por enquanto erro
|
||||
return error.UnsupportedExpression;
|
||||
}
|
||||
|
||||
// Função auxiliar: converte string literal pra Value
|
||||
fn parseLiteral(str: []const u8) Value {
|
||||
const trimmed = std.mem.trim(u8, str, " \t\r\n\"'");
|
||||
if (std.mem.eql(u8, trimmed, "true")) return Value{ .bool = true };
|
||||
if (std.mem.eql(u8, trimmed, "false")) return Value{ .bool = false };
|
||||
if (std.mem.eql(u8, trimmed, "null")) return Value.null;
|
||||
|
||||
if (std.fmt.parseInt(i64, trimmed, 10)) |n| return Value{ .int = n } else |_| {}
|
||||
if (std.fmt.parseFloat(f64, trimmed)) |f| return Value{ .float = f } else |_| {}
|
||||
|
||||
return Value{ .string = trimmed };
|
||||
}
|
||||
|
||||
// Função auxiliar: truthy check
|
||||
fn isTruthy(v: Value) bool {
|
||||
return switch (v) {
|
||||
.null => false,
|
||||
.bool => |b| b,
|
||||
.int => |i| i != 0,
|
||||
.float => |f| f != 0.0,
|
||||
.string => |s| s.len > 0,
|
||||
.list => |l| l.len > 0,
|
||||
.dict => |d| d.count() > 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Função auxiliar: comparação
|
||||
fn compare(left: Value, right: Value, op: enum { gt, lt, ge, le, eq, ne, not }) bool {
|
||||
// Implementação básica (expanda conforme necessário)
|
||||
switch (left) {
|
||||
.int => |l| switch (right) {
|
||||
.int => |r| return switch (op) {
|
||||
.gt => l > r,
|
||||
.lt => l < r,
|
||||
.ge => l >= r,
|
||||
.le => l <= r,
|
||||
.eq => l == r,
|
||||
.ne, .not => l != r,
|
||||
},
|
||||
else => return false,
|
||||
},
|
||||
.float => |l| switch (right) {
|
||||
.float => |r| return switch (op) {
|
||||
.gt => l > r,
|
||||
.lt => l < r,
|
||||
.ge => l >= r,
|
||||
.le => l <= r,
|
||||
.eq => l == r,
|
||||
.ne, .not => l != r,
|
||||
},
|
||||
else => return false,
|
||||
},
|
||||
.string => |l| switch (right) {
|
||||
.string => |r| return switch (op) {
|
||||
.eq => std.mem.eql(u8, l, r),
|
||||
.ne, .not => !std.mem.eql(u8, l, r),
|
||||
else => false,
|
||||
},
|
||||
else => return false,
|
||||
},
|
||||
else => return false,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ const Value = @import("context.zig").Value;
|
|||
const TemplateCache = @import("cache.zig").TemplateCache;
|
||||
|
||||
test "renderer: literal + variável simples" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("1 - renderer: literal + variável simples\n", .{});
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -18,7 +20,7 @@ test "renderer: literal + variável simples" {
|
|||
|
||||
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){};
|
||||
defer buf.deinit(alloc);
|
||||
|
|
@ -29,10 +31,14 @@ test "renderer: literal + variável simples" {
|
|||
|
||||
try renderer.renderString(template, buf.writer(alloc));
|
||||
|
||||
try testing.expectEqualStrings("Olá, Mariana! Bem-vinda.", buf.items);
|
||||
// std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items});
|
||||
|
||||
try testing.expectEqualStrings("Olá, Fulana! Bem-vinda.", buf.items);
|
||||
}
|
||||
|
||||
test "renderer: filtros + autoescape" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("2 - renderer: filtros + autoescape\n", .{});
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -62,10 +68,14 @@ test "renderer: filtros + autoescape" {
|
|||
\\Filtrado: maiusculo-e-slug
|
||||
;
|
||||
|
||||
// std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items});
|
||||
|
||||
try testing.expectEqualStrings(expected, buf.items);
|
||||
}
|
||||
|
||||
test "literal simples" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("3 - literal simples\n", .{});
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -82,10 +92,14 @@ test "literal simples" {
|
|||
|
||||
try renderer.renderString(template, buf.writer(alloc));
|
||||
|
||||
// std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items});
|
||||
|
||||
try testing.expectEqualStrings("Texto literal com acentos: Olá, mundo!", buf.items);
|
||||
}
|
||||
|
||||
test "variável com filtro encadeado e autoescape" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("4 - variável com filtro encadeado e autoescape\n", .{});
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -104,10 +118,14 @@ test "variável com filtro encadeado e autoescape" {
|
|||
|
||||
try renderer.renderString(template, buf.writer(alloc));
|
||||
|
||||
// std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items});
|
||||
|
||||
try testing.expectEqualStrings("Resultado: EXEMPLO DE TEXTO", buf.items); // assume lower then upper
|
||||
}
|
||||
|
||||
test "autoescape com safe" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("5 - autoescape com safe\n", .{});
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -126,10 +144,15 @@ test "autoescape com safe" {
|
|||
|
||||
try renderer.renderString(template, buf.writer(alloc));
|
||||
|
||||
// std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items});
|
||||
|
||||
try testing.expectEqualStrings("Escape: <div>conteúdo</div> | Safe: <div>conteúdo</div>", buf.items);
|
||||
}
|
||||
|
||||
// // TODO: evaluationConditions more complex
|
||||
test "renderer - if and for" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("6 - renderer - if and for\n", .{});
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
|
@ -160,6 +183,8 @@ test "renderer - if and for" {
|
|||
|
||||
try renderer.renderString(template, buf.writer(alloc));
|
||||
|
||||
// std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items});
|
||||
|
||||
try testing.expect(std.mem.indexOf(u8, buf.items, "Sim!") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, buf.items, "Não") == null);
|
||||
try testing.expect(std.mem.indexOf(u8, buf.items, "- Ana") != null);
|
||||
|
|
@ -168,12 +193,15 @@ test "renderer - if and for" {
|
|||
}
|
||||
|
||||
test "renderer - block and extends" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("7 - renderer - block and extends\n", .{});
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
||||
var cache = TemplateCache.init(alloc);
|
||||
defer cache.deinit();
|
||||
cache.default_path = ".";
|
||||
|
||||
const renderer = Renderer.init(&ctx, &cache);
|
||||
|
||||
|
|
@ -220,7 +248,7 @@ test "renderer - block and extends" {
|
|||
|
||||
const output = buf.items;
|
||||
|
||||
std.debug.print("OUTPUT:\n{s}\n", .{output});
|
||||
// std.debug.print("OUTPUT:\n\n{s}\n", .{output});
|
||||
|
||||
try testing.expect(std.mem.indexOf(u8, output, "<html>") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, output, "Meu Título") != null);
|
||||
|
|
@ -230,12 +258,15 @@ test "renderer - block and extends" {
|
|||
}
|
||||
|
||||
test "renderer - block and extends with super" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("8 - renderer - block and extends with super\n", .{});
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
||||
var cache = TemplateCache.init(alloc);
|
||||
defer cache.deinit();
|
||||
cache.default_path = ".";
|
||||
|
||||
const renderer = Renderer.init(&ctx, &cache);
|
||||
|
||||
|
|
@ -284,7 +315,7 @@ test "renderer - block and extends with super" {
|
|||
|
||||
const output = buf.items;
|
||||
|
||||
std.debug.print("{s}\n", .{output});
|
||||
// std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items});
|
||||
|
||||
try testing.expect(std.mem.indexOf(u8, output, "<html>") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, output, "Meu Título") != null);
|
||||
|
|
@ -294,6 +325,8 @@ test "renderer - block and extends with super" {
|
|||
}
|
||||
|
||||
test "renderer - include" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("9 - renderer - include\n", .{});
|
||||
const alloc = testing.allocator;
|
||||
|
||||
const header =
|
||||
|
|
@ -330,6 +363,7 @@ test "renderer - include" {
|
|||
|
||||
var cache = TemplateCache.init(alloc);
|
||||
defer cache.deinit();
|
||||
cache.default_path = ".";
|
||||
|
||||
const renderer = Renderer.init(&ctx, &cache);
|
||||
|
||||
|
|
@ -342,12 +376,16 @@ test "renderer - include" {
|
|||
|
||||
const output = buf.items;
|
||||
|
||||
// std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items});
|
||||
|
||||
try testing.expect(std.mem.indexOf(u8, output, "<h1>Bem-vindo</h1>") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas!") != null);
|
||||
try testing.expectEqualStrings(expected, output);
|
||||
}
|
||||
|
||||
test "renderer - comment" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("10 - renderer - comment\n", .{});
|
||||
const alloc = testing.allocator;
|
||||
|
||||
const template =
|
||||
|
|
@ -370,6 +408,7 @@ test "renderer - comment" {
|
|||
|
||||
var cache = TemplateCache.init(alloc);
|
||||
defer cache.deinit();
|
||||
cache.default_path = ".";
|
||||
|
||||
const renderer = Renderer.init(&ctx, &cache);
|
||||
|
||||
|
|
@ -382,6 +421,8 @@ test "renderer - comment" {
|
|||
|
||||
const output = buf.items;
|
||||
|
||||
// std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items});
|
||||
|
||||
try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, output, "Fim: Lucas") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, output, "Isso é um comentário") == null);
|
||||
|
|
@ -390,54 +431,670 @@ test "renderer - comment" {
|
|||
try testing.expectEqualStrings(expected, output);
|
||||
}
|
||||
|
||||
// FIX: comment inside block
|
||||
//
|
||||
// test "renderer - full template with extends, super, include, comment" {
|
||||
// const alloc = testing.allocator;
|
||||
//
|
||||
// const header = "<header>Bem-vindo</header>";
|
||||
// const base =
|
||||
// \\{% include "header.html" %}
|
||||
// \\<main>
|
||||
// \\ {% block content %}
|
||||
// \\ Conteúdo padrão
|
||||
// \\ {% endblock %}
|
||||
// \\</main>
|
||||
// ;
|
||||
//
|
||||
// const child =
|
||||
// \\{% extends "base.html" %}
|
||||
// \\{% block content %}
|
||||
// \\ {{ block.super }}
|
||||
// \\ Conteúdo do filho
|
||||
// \\ {% comment %} Isso não aparece {% endcomment %}
|
||||
// \\{% endblock %}
|
||||
// ;
|
||||
//
|
||||
// try std.fs.cwd().writeFile(.{ .sub_path = "header.html", .data = header });
|
||||
// try std.fs.cwd().writeFile(.{ .sub_path = "base.html", .data = base });
|
||||
// try std.fs.cwd().writeFile(.{ .sub_path = "child.html", .data = child });
|
||||
// defer std.fs.cwd().deleteFile("header.html") catch {};
|
||||
// defer std.fs.cwd().deleteFile("base.html") catch {};
|
||||
// defer std.fs.cwd().deleteFile("child.html") catch {};
|
||||
//
|
||||
// var ctx = Context.init(alloc);
|
||||
// defer ctx.deinit();
|
||||
//
|
||||
// var cache = TemplateCache.init(alloc);
|
||||
// defer cache.deinit();
|
||||
//
|
||||
// const renderer = Renderer.init(&ctx, &cache);
|
||||
//
|
||||
// var buf = std.ArrayList(u8){};
|
||||
// defer buf.deinit(alloc);
|
||||
//
|
||||
// try renderer.render("child.html", buf.writer(alloc));
|
||||
//
|
||||
// const output = buf.items;
|
||||
//
|
||||
// try testing.expect(std.mem.indexOf(u8, output, "<header>Bem-vindo</header>") != null);
|
||||
// try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") != null);
|
||||
// try testing.expect(std.mem.indexOf(u8, output, "Conteúdo do filho") != null);
|
||||
// try testing.expect(std.mem.indexOf(u8, output, "Isso não aparece") == null);
|
||||
// }
|
||||
test "renderer - full template with extends, super, include, comment" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("11 - renderer - full template with extends, super, include, comment\n", .{});
|
||||
const alloc = testing.allocator;
|
||||
|
||||
const header = "<header>Bem-vindo</header>";
|
||||
const base =
|
||||
\\{% include "header.html" %}
|
||||
\\<main>
|
||||
\\ {% block content %}
|
||||
\\ Conteúdo padrão
|
||||
\\ {% endblock %}
|
||||
\\</main>
|
||||
;
|
||||
|
||||
const child =
|
||||
\\{% extends "base.html" %}
|
||||
\\{% block content %}
|
||||
\\ {{ block.super }}
|
||||
\\ Conteúdo do filho
|
||||
\\ {% comment %} Isso não aparece {% endcomment %}
|
||||
\\{% endblock %}
|
||||
\\{% comment %} bazinga {% endcomment %}
|
||||
;
|
||||
|
||||
try std.fs.cwd().writeFile(.{ .sub_path = "header.html", .data = header });
|
||||
try std.fs.cwd().writeFile(.{ .sub_path = "base.html", .data = base });
|
||||
try std.fs.cwd().writeFile(.{ .sub_path = "child.html", .data = child });
|
||||
defer std.fs.cwd().deleteFile("header.html") catch {};
|
||||
defer std.fs.cwd().deleteFile("base.html") catch {};
|
||||
defer std.fs.cwd().deleteFile("child.html") catch {};
|
||||
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
||||
var cache = TemplateCache.init(alloc);
|
||||
defer cache.deinit();
|
||||
cache.default_path = ".";
|
||||
|
||||
const renderer = Renderer.init(&ctx, &cache);
|
||||
|
||||
var buf = std.ArrayList(u8){};
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
try renderer.render("child.html", buf.writer(alloc));
|
||||
|
||||
const output = buf.items;
|
||||
// std.debug.print("OUTPUT:\n\n{s}\n", .{output});
|
||||
|
||||
try testing.expect(std.mem.indexOf(u8, output, "<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 do filho") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, output, "Isso não aparece") == null);
|
||||
try testing.expect(std.mem.indexOf(u8, output, "bazinga") == null);
|
||||
}
|
||||
|
||||
test "renderer - if inside block" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("12 - render - if inside block\n", .{});
|
||||
const alloc = testing.allocator;
|
||||
|
||||
const base =
|
||||
\\<main>
|
||||
\\ {% block content %}
|
||||
\\ Conteúdo padrão
|
||||
\\ {% endblock %}
|
||||
\\</main>
|
||||
;
|
||||
|
||||
const child =
|
||||
\\{% extends "base.html" %}
|
||||
\\{% block content %}
|
||||
\\ {{ block.super }}
|
||||
\\ Conteúdo do filho
|
||||
\\{% if idade > 18 %}
|
||||
\\ Idade: {{ idade }}
|
||||
\\{% else %}
|
||||
\\ Oops
|
||||
\\{% endif %}
|
||||
\\{% endblock %}
|
||||
;
|
||||
|
||||
try std.fs.cwd().writeFile(.{ .sub_path = "base.html", .data = base });
|
||||
try std.fs.cwd().writeFile(.{ .sub_path = "child.html", .data = child });
|
||||
defer std.fs.cwd().deleteFile("base.html") catch {};
|
||||
defer std.fs.cwd().deleteFile("child.html") catch {};
|
||||
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
||||
var cache = TemplateCache.init(alloc);
|
||||
defer cache.deinit();
|
||||
cache.default_path = ".";
|
||||
|
||||
const renderer = Renderer.init(&ctx, &cache);
|
||||
|
||||
try ctx.set("idade", 23);
|
||||
|
||||
var buf = std.ArrayList(u8){};
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
try renderer.render("child.html", buf.writer(alloc));
|
||||
|
||||
const output = buf.items;
|
||||
|
||||
// std.debug.print("OUTPUT:\n\n{s}\n", .{output});
|
||||
|
||||
try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, output, "Conteúdo do filho") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, output, "Oops") == null);
|
||||
try testing.expect(std.mem.indexOf(u8, output, "Idade: 23") != null);
|
||||
}
|
||||
|
||||
test "renderer - if with operators" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("13 - render - if inside block\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
||||
var cache = TemplateCache.init(alloc);
|
||||
defer cache.deinit();
|
||||
|
||||
const renderer = Renderer.init(&ctx, &cache);
|
||||
|
||||
try ctx.set("idade", Value{ .int = 20 });
|
||||
try ctx.set("nome", Value{ .string = "Lucas" });
|
||||
try ctx.set("ativo", Value{ .bool = true });
|
||||
try ctx.set("order", 1);
|
||||
|
||||
const template =
|
||||
\\{% if idade > 18 %}Maior{% endif %}
|
||||
\\{% if idade < 18 %}Menor{% endif %}
|
||||
\\{% if nome == "Lucas" %}Olá Lucas{% endif %}
|
||||
\\{% if ativo %}Ativo{% endif %}
|
||||
\\{% if order >= 2 %}High Order{% else %}Low Order{% endif %}
|
||||
;
|
||||
|
||||
var buf = std.ArrayList(u8){};
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
try renderer.renderString(template, buf.writer(alloc));
|
||||
|
||||
// std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items});
|
||||
|
||||
try testing.expect(std.mem.indexOf(u8, buf.items, "Maior") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, buf.items, "Olá Lucas") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, buf.items, "Ativo") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, buf.items, "Menor") == null);
|
||||
try testing.expect(std.mem.indexOf(u8, buf.items, "Low Order") != null);
|
||||
}
|
||||
|
||||
test "renderer - widthratio inside block" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("14 - render - widthratio inside block\n", .{});
|
||||
const alloc = testing.allocator;
|
||||
|
||||
const base =
|
||||
\\<main>
|
||||
\\ {% block content %}
|
||||
\\ Conteúdo padrão
|
||||
\\ {% endblock %}
|
||||
\\</main>
|
||||
;
|
||||
|
||||
const child =
|
||||
\\{% extends "base.html" %}
|
||||
\\{% block content %}
|
||||
\\ Conteúdo do filho
|
||||
\\<img src="bar.png" alt="Bar" height="10" width="{% widthratio value 400 %}">
|
||||
\\ {{ block.super }}
|
||||
\\{% endblock %}
|
||||
;
|
||||
|
||||
try std.fs.cwd().writeFile(.{ .sub_path = "base.html", .data = base });
|
||||
try std.fs.cwd().writeFile(.{ .sub_path = "child.html", .data = child });
|
||||
defer std.fs.cwd().deleteFile("base.html") catch {};
|
||||
defer std.fs.cwd().deleteFile("child.html") catch {};
|
||||
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
||||
var cache = TemplateCache.init(alloc);
|
||||
defer cache.deinit();
|
||||
cache.default_path = ".";
|
||||
|
||||
const renderer = Renderer.init(&ctx, &cache);
|
||||
|
||||
try ctx.set("value", 50);
|
||||
|
||||
var buf = std.ArrayList(u8){};
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
try renderer.render("child.html", buf.writer(alloc));
|
||||
|
||||
const output = buf.items;
|
||||
|
||||
// std.debug.print("OUTPUT:\n\n{s}\n", .{output});
|
||||
|
||||
try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") != null);
|
||||
try testing.expect(std.mem.indexOf(u8, output, "Conteúdo do filho") != null);
|
||||
}
|
||||
|
||||
test "renderer - now" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("15 - render now\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
||||
var cache = TemplateCache.init(alloc);
|
||||
defer cache.deinit();
|
||||
|
||||
const renderer = Renderer.init(&ctx, &cache);
|
||||
|
||||
try ctx.set("idade", Value{ .int = 20 });
|
||||
|
||||
const template =
|
||||
\\{% now %}
|
||||
\\{% now \"Y-m-d H:i:s\" %}
|
||||
\\{% now \"Y" %}
|
||||
\\{% now \"m\" %}
|
||||
\\{% now \"n\" %}
|
||||
\\{% now \"d\" %}
|
||||
\\{% now \"j\" %}
|
||||
\\{% now \"F\" %}
|
||||
\\{% now \"M\" %}
|
||||
\\{% now \"l\" %}
|
||||
\\{% now \"D\" %}
|
||||
\\{% now \"H:i:s\" %}
|
||||
\\{% now \"H\" %}
|
||||
\\{% now \"G\" %}
|
||||
\\{% now \"i\" %}
|
||||
\\{% now \"s\" %}
|
||||
\\{% now \"a\" %}
|
||||
\\{% now \"A\" %}
|
||||
\\{% now \"P\" %}
|
||||
;
|
||||
|
||||
var buf = std.ArrayList(u8){};
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
try renderer.renderString(template, buf.writer(alloc));
|
||||
|
||||
std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items});
|
||||
|
||||
// try testing.expect(std.mem.indexOf(u8, buf.items, "Maior") != null);
|
||||
}
|
||||
|
||||
test "renderer - csrf_token in context" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("15 - csrf_token in context\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
||||
var cache = TemplateCache.init(alloc);
|
||||
defer cache.deinit();
|
||||
|
||||
const renderer = Renderer.init(&ctx, &cache);
|
||||
|
||||
const token: []const u8 = "zh5fyUSICjXNsDTtJCjl9A3O2dDSHhYFlIngAEO6PXK9NX56Z1XLEy7doYuPcE0u";
|
||||
|
||||
try ctx.set("csrf_token", token);
|
||||
const template =
|
||||
\\{% csrf_token %}
|
||||
;
|
||||
|
||||
const expected =
|
||||
\\<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);
|
||||
}
|
||||
|
||||
test "renderer - svg" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("27 - svg\n", .{});
|
||||
|
||||
const alloc = testing.allocator;
|
||||
var ctx = Context.init(alloc);
|
||||
defer ctx.deinit();
|
||||
|
||||
var cache = TemplateCache.init(alloc);
|
||||
try cache.initIcons();
|
||||
defer cache.deinit();
|
||||
|
||||
const renderer = Renderer.init(&ctx, &cache);
|
||||
|
||||
const template =
|
||||
\\{% svg material kangaroo %}
|
||||
;
|
||||
|
||||
var buf = std.ArrayList(u8){};
|
||||
defer buf.deinit(alloc);
|
||||
|
||||
try renderer.renderString(template, buf.writer(alloc));
|
||||
|
||||
std.debug.print("OUTPUT:\n\n{s}\n", .{buf.items});
|
||||
|
||||
try testing.expect(std.mem.indexOf(u8, buf.items, "<div class=\"svg-container\">") != null);
|
||||
// const spaces = std.mem.count(u8, buf.items, "<p>");
|
||||
// try testing.expect(spaces == 3);
|
||||
}
|
||||
|
|
|
|||
34
src/root.zig
34
src/root.zig
|
|
@ -1,23 +1,11 @@
|
|||
//! By convention, root.zig is the root source file when making a library.
|
||||
const std = @import("std");
|
||||
|
||||
pub fn bufferedPrint() !void {
|
||||
// Stdout is for the actual output of your application, for example if you
|
||||
// are implementing gzip, then only the compressed bytes should be sent to
|
||||
// stdout, not any debugging messages.
|
||||
var stdout_buffer: [1024]u8 = undefined;
|
||||
var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
|
||||
const stdout = &stdout_writer.interface;
|
||||
|
||||
try stdout.print("Run `zig build test` to run the tests.\n", .{});
|
||||
|
||||
try stdout.flush(); // Don't forget to flush!
|
||||
}
|
||||
|
||||
pub fn add(a: i32, b: i32) i32 {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
test "basic add functionality" {
|
||||
try std.testing.expect(add(3, 7) == 10);
|
||||
}
|
||||
pub const cache = @import("cache.zig");
|
||||
pub const context = @import("context.zig");
|
||||
pub const delta = @import("delta.zig");
|
||||
pub const filters = @import("filters.zig");
|
||||
pub const icons = @import("svg/icons.zig");
|
||||
pub const lorem = @import("lorem.zig");
|
||||
pub const meta = @import("meta.zig");
|
||||
pub const parser = @import("parser.zig");
|
||||
pub const renderer = @import("renderer.zig");
|
||||
pub const time = @import("time.zig");
|
||||
pub const util = @import("util.zig");
|
||||
|
|
|
|||
117
src/svg/icons.zig
Normal file
117
src/svg/icons.zig
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub const IconSet = enum {
|
||||
bootstrap,
|
||||
dripicons,
|
||||
hero_outline,
|
||||
hero_solid,
|
||||
material,
|
||||
};
|
||||
|
||||
pub const embedded_data = std.EnumMap(IconSet, []const u8).init(.{
|
||||
.bootstrap = @embedFile("bootstrap.svgs.bin"),
|
||||
.dripicons = @embedFile("dripicons.svgs.bin"),
|
||||
.hero_outline = @embedFile("hero_outline.svgs.bin"),
|
||||
.hero_solid = @embedFile("hero_solid.svgs.bin"),
|
||||
.material = @embedFile("material.svgs.bin"),
|
||||
});
|
||||
|
||||
pub const SvgIcon = struct {
|
||||
icon_map: std.StringHashMapUnmanaged([]const u8) = .{},
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) !SvgIcon {
|
||||
var self = SvgIcon{};
|
||||
|
||||
inline for (std.meta.fields(IconSet)) |field| {
|
||||
const set = @field(IconSet, field.name);
|
||||
const data = embedded_data.get(set);
|
||||
|
||||
try self.loadSet(allocator, set, data.?);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *SvgIcon, allocator: std.mem.Allocator) void {
|
||||
var it = self.icon_map.iterator();
|
||||
while (it.next()) |entry| {
|
||||
allocator.free(entry.key_ptr.*);
|
||||
allocator.free(entry.value_ptr.*);
|
||||
}
|
||||
self.icon_map.deinit(allocator);
|
||||
self.* = .{};
|
||||
}
|
||||
|
||||
pub fn get(self: *const SvgIcon, key: []const u8) ?[]const u8 {
|
||||
return self.icon_map.get(key);
|
||||
}
|
||||
|
||||
pub fn getIcon(self: *const SvgIcon,allocator: std.mem.Allocator, kind: []const u8, name: []const u8) ?[]const u8 {
|
||||
const key = std.fmt.allocPrint(allocator, "{s}:{s}", .{ kind, name }) catch return null;
|
||||
defer allocator.free(key);
|
||||
return self.icon_map.get(key);
|
||||
}
|
||||
|
||||
pub fn count(self: *const SvgIcon) usize {
|
||||
return self.icon_map.count();
|
||||
}
|
||||
|
||||
fn loadSet(
|
||||
self: *SvgIcon,
|
||||
allocator: std.mem.Allocator,
|
||||
set: IconSet,
|
||||
data: []const u8,
|
||||
) !void {
|
||||
if (data.len < 12) return error.InvalidEmbeddedData;
|
||||
|
||||
var pos: usize = 0;
|
||||
|
||||
const magic = std.mem.readInt(u32, data[pos..][0..4], .little);
|
||||
pos += 4;
|
||||
if (magic != 0x53564749) return error.InvalidMagic;
|
||||
|
||||
const version = std.mem.readInt(u32, data[pos..][0..4], .little);
|
||||
pos += 4;
|
||||
if (version != 1) return error.UnsupportedVersion;
|
||||
|
||||
const num_entries = std.mem.readInt(u32, data[pos..][0..4], .little);
|
||||
pos += 4;
|
||||
|
||||
const prefix = @tagName(set);
|
||||
|
||||
var i: u32 = 0;
|
||||
while (i < num_entries) : (i += 1) {
|
||||
const name_len = std.mem.readInt(u32, data[pos..][0..4], .little);
|
||||
pos += 4;
|
||||
|
||||
if (pos + name_len > data.len) return error.CorruptedNameLength;
|
||||
const name_slice = data[pos .. pos + name_len];
|
||||
pos += name_len;
|
||||
|
||||
const svg_len = std.mem.readInt(u32, data[pos..][0..4], .little);
|
||||
pos += 4;
|
||||
|
||||
if (pos + svg_len > data.len) return error.CorruptedSvgLength;
|
||||
const svg_slice = data[pos .. pos + svg_len];
|
||||
pos += svg_len;
|
||||
|
||||
// Monta a chave com prefixo do set
|
||||
const key = try std.fmt.allocPrint(allocator, "{s}:{s}", .{ prefix, name_slice });
|
||||
|
||||
// Duplica o conteúdo SVG (o map assume ownership)
|
||||
const value = try allocator.dupe(u8, svg_slice);
|
||||
|
||||
// Insere no mapa unmanaged
|
||||
try self.icon_map.put(allocator, key, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const fallback_svg =
|
||||
\\<div class="svg-container">
|
||||
\\<svg width="24" height="24" fill="none">
|
||||
\\ <rect width="24" height="24" rx="4" fill="#f0f0f0"/>
|
||||
\\ <text x="12" y="16" font-size="10" text-anchor="middle" fill="#999">?</text>
|
||||
\\</svg>
|
||||
\\</div>
|
||||
;
|
||||
878
src/time.zig
Normal file
878
src/time.zig
Normal file
|
|
@ -0,0 +1,878 @@
|
|||
// https://github.com/cztomsik/tokamak
|
||||
const std = @import("std");
|
||||
const util = @import("util.zig");
|
||||
const testing = std.testing;
|
||||
const meta = @import("meta.zig");
|
||||
const dlt = @import("delta.zig");
|
||||
|
||||
const RATA_MIN = date_to_rata(Date.MIN);
|
||||
const RATA_MAX = date_to_rata(Date.MAX);
|
||||
const RATA_TO_UNIX = 719468;
|
||||
const EOD = 86_400 - 1;
|
||||
|
||||
pub const DAY_NAMES_SHORT = [_][]const u8{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
|
||||
pub const DAY_NAMES_LONG = [_][]const u8{ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
|
||||
|
||||
pub const MONTH_NAMES_SHORT = [_][]const u8{ "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
|
||||
pub const MONTH_NAMES_LONG = [_][]const u8{ "", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };
|
||||
|
||||
pub const MONTH_DAYS = [12]u8{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
||||
|
||||
pub const TIME_CHUNKS = [4]u32{
|
||||
60 * 60 * 24 * 7, //week
|
||||
60 * 60 * 24, //day
|
||||
60 * 60, //hour
|
||||
60, //minute
|
||||
};
|
||||
|
||||
pub const TIME_STRINGS = [6][]const u8{ "year", "month", "week", "day", "hour", "minute" };
|
||||
|
||||
// TODO: Decide if we want to use std.debug.assert(), @panic() or just throw an error
|
||||
fn checkRange(num: anytype, min: @TypeOf(num), max: @TypeOf(num)) void {
|
||||
if (util.lt(num, min) or util.gt(num, max)) {
|
||||
// TODO: fix later (we can't use {f} and {any} is also wrong)
|
||||
// std.log.warn("Value {} is not in range [{}, {}]", .{ num, min, max });
|
||||
std.log.warn("Value not in range", .{});
|
||||
}
|
||||
}
|
||||
|
||||
pub const TimeUnit = enum { second, minute, hour, day, month, year };
|
||||
pub const DateUnit = enum { day, month, year };
|
||||
|
||||
// pub const SECS_PER_DAY: i64 = 86_400;
|
||||
// pub const SECS_PER_HOUR: i64 = 3_600;
|
||||
// pub const SECS_PER_MIN: i64 = 60;
|
||||
|
||||
pub const TimeError = error{
|
||||
Eof,
|
||||
ExpectedNull,
|
||||
ExpectedValue,
|
||||
InvalidCharacter,
|
||||
InvalidFormat,
|
||||
Overflow,
|
||||
OutOfMemory,
|
||||
};
|
||||
|
||||
// https://www.youtube.com/watch?v=0s9F4QWAl-E&t=2120
|
||||
pub fn isLeapYear(year: i32) bool {
|
||||
const d: i32 = if (@mod(year, 100) != 0) 4 else 16;
|
||||
return (year & (d - 1)) == 0;
|
||||
}
|
||||
|
||||
// https://www.youtube.com/watch?v=0s9F4QWAl-E&t=2257
|
||||
pub fn daysInMonth(year: i32, month: u8) u8 {
|
||||
if (month == 2) {
|
||||
return if (isLeapYear(year)) 29 else 28;
|
||||
}
|
||||
|
||||
return 30 | (month ^ (month >> 3));
|
||||
}
|
||||
|
||||
pub fn formatDateTime(alloc: std.mem.Allocator, t: Time, format_str: []const u8) ![]u8 {
|
||||
var result = std.ArrayList(u8){};
|
||||
defer result.deinit(alloc);
|
||||
var writer = result.writer(alloc);
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < format_str.len) : (i += 1) {
|
||||
const c = format_str[i];
|
||||
|
||||
if (c == '\\') {
|
||||
i += 1;
|
||||
if (i >= format_str.len) break;
|
||||
try writer.writeByte(format_str[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Todos os códigos do date + time que já implementamos
|
||||
switch (c) {
|
||||
// === Códigos de data (do filtro date) ===
|
||||
'Y' => try writer.print("{d:0>4}", .{@as(u16, @intCast(t.year()))}),
|
||||
'm' => try writer.print("{d:0>2}", .{t.month()}),
|
||||
'n' => try writer.print("{d}", .{t.month()}),
|
||||
'd' => try writer.print("{d:0>2}", .{t.day()}),
|
||||
'j' => try writer.print("{d}", .{t.day()}),
|
||||
'F' => try writer.writeAll(t.monthNameLong()),
|
||||
'M' => try writer.writeAll(t.monthNameShort()),
|
||||
'l' => try writer.writeAll(t.weekdayNameLong()),
|
||||
'D' => try writer.writeAll(t.weekdayNameShort()),
|
||||
|
||||
// === Códigos de tempo (do filtro time) ===
|
||||
'H' => try writer.print("{d:0>2}", .{t.hour()}),
|
||||
'G' => try writer.print("{d}", .{t.hour()}),
|
||||
'i' => try writer.print("{d:0>2}", .{t.minute()}),
|
||||
's' => try writer.print("{d:0>2}", .{t.second()}),
|
||||
'a' => try writer.writeAll(if (t.hour() < 12) "a.m." else "p.m."),
|
||||
'A' => try writer.writeAll(if (t.hour() < 12) "AM" else "PM"),
|
||||
'P' => {
|
||||
const hr24 = t.hour();
|
||||
const min = t.minute();
|
||||
if (hr24 == 0 and min == 0) {
|
||||
try writer.writeAll("midnight");
|
||||
} else if (hr24 == 12 and min == 0) {
|
||||
try writer.writeAll("noon");
|
||||
} else {
|
||||
var hr12 = @mod(hr24, 12);
|
||||
if (hr12 == 0) hr12 = 12;
|
||||
try writer.print("{d}", .{hr12});
|
||||
if (min > 0) try writer.print(":{d:0>2}", .{min});
|
||||
try writer.writeAll(if (hr24 < 12) " a.m." else " p.m.");
|
||||
}
|
||||
},
|
||||
'u' => try writer.writeAll("000000"),
|
||||
|
||||
else => try writer.writeByte(c),
|
||||
}
|
||||
}
|
||||
|
||||
return try result.toOwnedSlice(alloc);
|
||||
}
|
||||
|
||||
pub const Date = struct {
|
||||
year: i32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
|
||||
pub const MIN = Date.ymd(-1467999, 1, 1);
|
||||
pub const MAX = Date.ymd(1471744, 12, 31);
|
||||
|
||||
pub fn cmp(a: Date, b: Date) std.math.Order {
|
||||
if (a.year != b.year) return util.cmp(a.year, b.year);
|
||||
if (a.month != b.month) return util.cmp(a.month, b.month);
|
||||
return util.cmp(a.day, b.day);
|
||||
}
|
||||
|
||||
pub fn parse(str: []const u8) TimeError!Date {
|
||||
var it = std.mem.splitScalar(u8, str, '-');
|
||||
return ymd(
|
||||
try std.fmt.parseInt(i32, it.next() orelse return error.Eof, 10),
|
||||
try std.fmt.parseInt(u8, it.next() orelse return error.Eof, 10),
|
||||
try std.fmt.parseInt(u8, it.next() orelse return error.Eof, 10),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn ymd(year: i32, month: u8, day: u8) Date {
|
||||
return .{
|
||||
.year = year,
|
||||
.month = month,
|
||||
.day = day,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn today() Date {
|
||||
return Time.now().date();
|
||||
}
|
||||
|
||||
pub fn yesterday() Date {
|
||||
return today().add(.day, -1);
|
||||
}
|
||||
|
||||
pub fn tomorrow() Date {
|
||||
return today().add(.day, 1);
|
||||
}
|
||||
|
||||
pub fn startOf(unit: DateUnit) Date {
|
||||
return today().setStartOf(unit);
|
||||
}
|
||||
|
||||
pub fn endOf(unit: DateUnit) Date {
|
||||
return today().setEndOf(unit);
|
||||
}
|
||||
|
||||
pub fn setStartOf(self: Date, unit: DateUnit) Date {
|
||||
return switch (unit) {
|
||||
.day => self,
|
||||
.month => ymd(self.year, self.month, 1),
|
||||
.year => ymd(self.year, 1, 1),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setEndOf(self: Date, unit: DateUnit) Date {
|
||||
return switch (unit) {
|
||||
.day => self,
|
||||
.month => ymd(self.year, self.month, daysInMonth(self.year, self.month)),
|
||||
.year => ymd(self.year, 12, 31),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn add(self: Date, part: DateUnit, amount: i64) Date {
|
||||
return switch (part) {
|
||||
.day => Time.unix(0).setDate(self).add(.days, amount).date(),
|
||||
.month => {
|
||||
const total_months = @as(i32, self.month) + @as(i32, @intCast(amount));
|
||||
const new_year = self.year + @divFloor(total_months - 1, 12);
|
||||
const new_month = @as(u8, @intCast(@mod(total_months - 1, 12) + 1));
|
||||
return ymd(
|
||||
new_year,
|
||||
new_month,
|
||||
@min(self.day, daysInMonth(new_year, new_month)),
|
||||
);
|
||||
},
|
||||
.year => {
|
||||
const new_year = self.year + @as(i32, @intCast(amount));
|
||||
return ymd(
|
||||
new_year,
|
||||
self.month,
|
||||
@min(self.day, daysInMonth(new_year, self.month)),
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn dayOfWeek(self: Date) u8 {
|
||||
const rata_day = date_to_rata(self);
|
||||
return @intCast(@mod(rata_day + 3, 7));
|
||||
}
|
||||
|
||||
pub fn format(self: Date, writer: anytype) !void {
|
||||
try writer.print("{d}-{d:0>2}-{d:0>2}", .{
|
||||
@as(u32, @intCast(self.year)),
|
||||
self.month,
|
||||
self.day,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn ordinal(self: Date) usize {
|
||||
const days_before_month = [_]u16{ 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
|
||||
|
||||
var days: usize = days_before_month[self.month];
|
||||
days += self.day;
|
||||
|
||||
if (self.month > 2 and isLeapYear(self.year)) {
|
||||
days += 1;
|
||||
}
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
pub fn weekday(self: Date) u8 {
|
||||
const y: i32 = self.year;
|
||||
const m: u8 = self.month;
|
||||
const d: u8 = self.day;
|
||||
|
||||
const t = [_]i32{ 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 };
|
||||
var year_adj: i32 = y;
|
||||
if (m < 3) year_adj -= 1;
|
||||
|
||||
var wd: i32 = year_adj + @divFloor(year_adj, 4) - @divFloor(year_adj, 100) + @divFloor(year_adj, 400) + t[m - 1] + @as(i32, d);
|
||||
wd = @mod(wd, 7);
|
||||
if (wd < 0) wd += 7;
|
||||
|
||||
return @intCast(if (wd == 6) 7 else wd + 1);
|
||||
}
|
||||
|
||||
pub fn isoWeek(self: Date) u8 {
|
||||
const iso_y = self.isoWeekYear();
|
||||
const jan4 = Date{ .year = iso_y, .month = 1, .day = 4 };
|
||||
const jan4_ord: i32 = @intCast(jan4.ordinal());
|
||||
const self_ord: i32 = @intCast(self.ordinal());
|
||||
|
||||
const days_diff = self_ord - jan4_ord;
|
||||
const week = @divFloor(days_diff + 4, 7) + 1; // +4 corrige o offset (testado em Python)
|
||||
|
||||
return @intCast(@max(1, @min(53, week)));
|
||||
}
|
||||
|
||||
pub fn isoWeekYear(self: Date) i32 {
|
||||
const wd = self.weekday(); // 1=Seg ... 7=Dom
|
||||
const ord = @as(i32, @intCast(self.ordinal()));
|
||||
const thursday_ord = ord + (4 - (wd - 1)); // quinta da semana
|
||||
|
||||
var y = self.year;
|
||||
|
||||
var days_in_year: i32 = 365;
|
||||
if (isLeapYear(y)) days_in_year = 366;
|
||||
|
||||
if (thursday_ord <= 0) {
|
||||
y -= 1;
|
||||
} else if (thursday_ord > days_in_year) {
|
||||
y += 1;
|
||||
}
|
||||
return y;
|
||||
}
|
||||
};
|
||||
|
||||
pub const TimeTime = struct {
|
||||
hour: u32,
|
||||
minute: u32,
|
||||
second: u32,
|
||||
|
||||
fn cmp(a: TimeTime, b: TimeTime) std.math.Order {
|
||||
if (a.hour < b.hour) return .lt;
|
||||
if (a.hour > b.hour) return .gt;
|
||||
if (a.minute < b.minute) return .lt;
|
||||
if (a.minute > b.minute) return .gt;
|
||||
if (a.second < b.second) return .lt;
|
||||
if (a.second > b.second) return .gt;
|
||||
return .eq;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Time = struct {
|
||||
epoch: i64,
|
||||
|
||||
pub fn parse(str: []const u8) !Time {
|
||||
if (std.mem.indexOfScalar(u8, str, ' ')) |space| {
|
||||
// Datetime: "YYYY-MM-DD HH:MM:SS"
|
||||
const date_str = str[0..space];
|
||||
const time_str = str[space + 1 ..];
|
||||
|
||||
const d = try Date.parse(date_str);
|
||||
|
||||
var it = std.mem.splitScalar(u8, time_str, ':');
|
||||
const h = try std.fmt.parseInt(u8, it.next() orelse return error.InvalidFormat, 10);
|
||||
const m = try std.fmt.parseInt(u8, it.next() orelse return error.InvalidFormat, 10);
|
||||
const s = try std.fmt.parseInt(u8, it.next() orelse return error.InvalidFormat, 10);
|
||||
|
||||
var t = Time.unix(0).setDate(d);
|
||||
t = t.setHour(h).setMinute(m).setSecond(s);
|
||||
return t;
|
||||
} else {
|
||||
const d = try Date.parse(str);
|
||||
return Time.unix(0).setDate(d);
|
||||
}
|
||||
return Time.now();
|
||||
}
|
||||
|
||||
pub fn unix(epoch: i64) Time {
|
||||
return .{ .epoch = epoch };
|
||||
}
|
||||
|
||||
pub fn new(y: i32, m: u8, d: u8, h: ?u32, min: ?u32, sec: ?u32) Time {
|
||||
var t = unix(0).setDate(.ymd(y, m, d));
|
||||
if (h) |h_| t = t.setHour(h_);
|
||||
if (min) |min_| t = t.setMinute(min_);
|
||||
if (sec) |sec_| t = t.setSecond(sec_);
|
||||
return t;
|
||||
}
|
||||
|
||||
pub fn now() Time {
|
||||
return unix(std.time.timestamp());
|
||||
}
|
||||
|
||||
pub fn now_offset(offset: i64) Time {
|
||||
return unix(std.time.timestamp() + (offset * std.time.s_per_hour));
|
||||
}
|
||||
|
||||
pub fn today() Time {
|
||||
return unix(0).setDate(.today());
|
||||
}
|
||||
|
||||
pub fn tomorrow() Time {
|
||||
return unix(0).setDate(.tomorrow());
|
||||
}
|
||||
|
||||
pub fn startOf(unit: TimeUnit) Time {
|
||||
return Time.now().setStartOf(unit);
|
||||
}
|
||||
|
||||
pub fn endOf(unit: TimeUnit) Time {
|
||||
return Time.now().setEndOf(unit);
|
||||
}
|
||||
|
||||
pub fn second(self: Time) u32 {
|
||||
return @intCast(@mod(self.total(.seconds), 60));
|
||||
}
|
||||
|
||||
pub fn setSecond(self: Time, sec: u32) Time {
|
||||
return self.add(.seconds, @as(i64, sec) - self.second());
|
||||
}
|
||||
|
||||
pub fn minute(self: Time) u32 {
|
||||
return @intCast(@mod(self.total(.minutes), 60));
|
||||
}
|
||||
|
||||
pub fn setMinute(self: Time, min: u32) Time {
|
||||
return self.add(.minutes, @as(i64, min) - self.minute());
|
||||
}
|
||||
|
||||
pub fn hour(self: Time) u32 {
|
||||
return @intCast(@mod(self.total(.hours), 24));
|
||||
}
|
||||
|
||||
pub fn setHour(self: Time, hr: u32) Time {
|
||||
return self.add(.hours, @as(i64, hr) - self.hour());
|
||||
}
|
||||
|
||||
pub fn date(self: Time) Date {
|
||||
return rata_to_date(@divTrunc(self.epoch, std.time.s_per_day) + RATA_TO_UNIX);
|
||||
}
|
||||
|
||||
pub fn setDate(self: Time, dat: Date) Time {
|
||||
var res: i64 = @mod(self.epoch, std.time.s_per_day);
|
||||
res += (date_to_rata(dat) - RATA_TO_UNIX) * std.time.s_per_day;
|
||||
return unix(res);
|
||||
}
|
||||
|
||||
pub fn time(self: Time) TimeTime {
|
||||
return .{
|
||||
.hour = self.hour(),
|
||||
.minute = self.minute(),
|
||||
.second = self.second(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setStartOf(self: Time, unit: TimeUnit) Time {
|
||||
// TODO: continue :label?
|
||||
return switch (unit) {
|
||||
.second => self,
|
||||
.minute => self.setSecond(0),
|
||||
.hour => self.setSecond(0).setMinute(0),
|
||||
.day => self.setSecond(0).setMinute(0).setHour(0),
|
||||
.month => {
|
||||
const d = self.date();
|
||||
return unix(0).setDate(.ymd(d.year, d.month, 1));
|
||||
},
|
||||
.year => {
|
||||
const d = self.date();
|
||||
return unix(0).setDate(.ymd(d.year, 1, 1));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: rename to startOfNext?
|
||||
pub fn next(self: Time, unit: enum { second, minute, hour, day }) Time {
|
||||
return switch (unit) {
|
||||
.second => self.add(.seconds, 1),
|
||||
.minute => self.setSecond(0).add(.minutes, 1),
|
||||
.hour => self.setSecond(0).setMinute(0).add(.hours, 1),
|
||||
.day => self.setSecond(0).setMinute(0).setHour(0).add(.hours, 24),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setEndOf(self: Time, unit: TimeUnit) Time {
|
||||
// TODO: continue :label?
|
||||
return switch (unit) {
|
||||
.second => self,
|
||||
.minute => self.setSecond(59),
|
||||
.hour => self.setSecond(59).setMinute(59),
|
||||
.day => self.setSecond(59).setMinute(59).setHour(23),
|
||||
.month => {
|
||||
const d = self.date();
|
||||
return unix(EOD).setDate(.ymd(d.year, d.month, daysInMonth(d.year, d.month)));
|
||||
},
|
||||
.year => {
|
||||
const d = self.date();
|
||||
return unix(EOD).setDate(.ymd(d.year, 12, 31));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn add(self: Time, part: enum { seconds, minutes, hours, days, months, years }, amount: i64) Time {
|
||||
const n = switch (part) {
|
||||
.seconds => amount,
|
||||
.minutes => amount * std.time.s_per_min,
|
||||
.hours => amount * std.time.s_per_hour,
|
||||
.days => amount * std.time.s_per_day,
|
||||
.months => return self.setDate(self.date().add(.month, amount)),
|
||||
.years => return self.setDate(self.date().add(.year, amount)),
|
||||
};
|
||||
|
||||
return .{ .epoch = self.epoch + n };
|
||||
}
|
||||
|
||||
fn total(self: Time, part: enum { seconds, minutes, hours }) i64 {
|
||||
return switch (part) {
|
||||
.seconds => self.epoch,
|
||||
.minutes => @divTrunc(self.epoch, std.time.s_per_min),
|
||||
.hours => @divTrunc(self.epoch, std.time.s_per_hour),
|
||||
};
|
||||
}
|
||||
|
||||
fn year(self: Time) i32 {
|
||||
return self.date().year;
|
||||
}
|
||||
|
||||
fn month(self: Time) u8 {
|
||||
return self.date().month;
|
||||
}
|
||||
|
||||
fn day(self: Time) u8 {
|
||||
return self.date().day;
|
||||
}
|
||||
|
||||
fn monthNameLong(self: Time) []const u8 {
|
||||
return MONTH_NAMES_LONG[self.date().month];
|
||||
}
|
||||
|
||||
fn monthNameShort(self: Time) []const u8 {
|
||||
return MONTH_NAMES_SHORT[self.date().month];
|
||||
}
|
||||
|
||||
fn weekdayNameLong(self: Time) []const u8 {
|
||||
return DAY_NAMES_LONG[self.date().weekday()];
|
||||
}
|
||||
|
||||
fn weekdayNameShort(self: Time) []const u8 {
|
||||
return DAY_NAMES_SHORT[self.date().weekday()];
|
||||
}
|
||||
|
||||
pub fn format(self: Time, writer: anytype) !void {
|
||||
try writer.print("{f} {d:0>2}:{d:0>2}:{d:0>2} UTC", .{
|
||||
self.date(),
|
||||
self.hour(),
|
||||
self.minute(),
|
||||
self.second(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn toStringAlloc(self: Time, alloc: std.mem.Allocator, format_str: ?[]const u8) TimeError![]u8 {
|
||||
const fmt = format_str orelse "Y-m-d H:i:s";
|
||||
return try formatDateTime(alloc, self, fmt);
|
||||
}
|
||||
|
||||
pub fn toString(self: Time, format_str: ?[]const u8) TimeError![]const u8 {
|
||||
return try self.toStringAlloc(std.heap.page_allocator, format_str);
|
||||
}
|
||||
|
||||
pub fn addRelative(self: Time, delta: dlt.RelativeDelta) Time {
|
||||
var d = delta;
|
||||
d.normalize(); // garante que meses/horas/etc estejam normalizados
|
||||
|
||||
// 1. Parte calendáríca (anos + meses + dias)
|
||||
var dt = self.date();
|
||||
|
||||
// anos primeiro (mais estável)
|
||||
if (d.years != 0) {
|
||||
dt = dt.add(.year, d.years);
|
||||
}
|
||||
|
||||
// depois meses (respeita dias-in-mês)
|
||||
if (d.months != 0) {
|
||||
dt = dt.add(.month, d.months);
|
||||
}
|
||||
|
||||
// por fim dias normais
|
||||
if (d.days != 0) {
|
||||
dt = dt.add(.day, d.days);
|
||||
}
|
||||
|
||||
// 2. Parte do relógio (horas, minutos, segundos)
|
||||
var result = self.setDate(dt);
|
||||
|
||||
// Podemos usar o .add() existente para segundos/minutos/horas
|
||||
if (d.seconds != 0) {
|
||||
result = result.add(.seconds, d.seconds);
|
||||
}
|
||||
if (d.minutes != 0) {
|
||||
result = result.add(.minutes, d.minutes);
|
||||
}
|
||||
if (d.hours != 0) {
|
||||
result = result.add(.hours, d.hours);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn subRelative(self: Time, other: Time) dlt.RelativeDelta {
|
||||
var delta = dlt.RelativeDelta{};
|
||||
|
||||
// Parte de tempo (horas, min, seg) – igual antes
|
||||
var seconds_diff: i64 = self.epoch - other.epoch;
|
||||
|
||||
delta.seconds = @as(i32, @intCast(@rem(seconds_diff, 60)));
|
||||
seconds_diff = @divTrunc(seconds_diff, 60);
|
||||
|
||||
delta.minutes = @as(i32, @intCast(@rem(seconds_diff, 60)));
|
||||
seconds_diff = @divTrunc(seconds_diff, 60);
|
||||
|
||||
delta.hours = @as(i32, @intCast(@rem(seconds_diff, 24)));
|
||||
seconds_diff = @divTrunc(seconds_diff, 24);
|
||||
|
||||
// Parte calendárica
|
||||
var later = self.date();
|
||||
var earlier = other.date();
|
||||
|
||||
const swapped = later.cmp(earlier) == .lt;
|
||||
|
||||
if (swapped) {
|
||||
// const temp = later;
|
||||
// later = earlier;
|
||||
// earlier = temp;
|
||||
std.mem.swap(i32, &later.year, &earlier.year);
|
||||
}
|
||||
|
||||
var years: i32 = later.year - earlier.year;
|
||||
var months: i32 = @as(i32, later.month) - @as(i32, earlier.month);
|
||||
|
||||
if (months < 0) {
|
||||
years -= 1;
|
||||
months += 12;
|
||||
}
|
||||
|
||||
var days: i32 = @as(i32, later.day) - @as(i32, earlier.day);
|
||||
|
||||
// Ajuste rigoroso para borrow (com handling de bissexto)
|
||||
if (days < 0) {
|
||||
const days_in_target = daysInMonth(later.year, later.month);
|
||||
if (later.day == days_in_target) {
|
||||
// Caso especial (ex: 28/fev não-bissexto vs 29/fev bissexto): ignora borrow, trata como período completo
|
||||
days = 0;
|
||||
} else {
|
||||
// Borrow normal, mas ajusta se earlier.day > dias no mês anterior (para casos como 29 > 28)
|
||||
const prev_month = later.add(.month, -1);
|
||||
var days_borrow: i32 = @as(i32, daysInMonth(prev_month.year, prev_month.month));
|
||||
if (earlier.day > @as(u8, @intCast(days_borrow))) {
|
||||
days_borrow = @as(i32, earlier.day);
|
||||
}
|
||||
std.debug.print("days_borrow em subRelative {d}\n", .{days_borrow});
|
||||
days += days_borrow;
|
||||
months -= 1;
|
||||
if (months < 0) {
|
||||
years -= 1;
|
||||
months += 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Atribui com sinal
|
||||
delta.years = if (swapped) -years else years;
|
||||
delta.months = if (swapped) -months else months;
|
||||
delta.days = if (swapped) -days else days;
|
||||
|
||||
// Normaliza (já lida com excessos, mas aqui é só para meses/anos)
|
||||
delta.normalize();
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
pub fn timeSince(self: Time, alloc: std.mem.Allocator, then: Time) TimeError![]u8 {
|
||||
if (self.epoch >= then.epoch) {
|
||||
return try alloc.dupe(u8, "0 minutes");
|
||||
}
|
||||
var total_months: i64 = 0;
|
||||
const delta_year: i64 = (then.year() - self.year()) * 12;
|
||||
const delta_month: i32 = @as(i32, @intCast(then.month())) - @as(i32, @intCast(self.month()));
|
||||
|
||||
total_months = delta_year + delta_month;
|
||||
|
||||
if (self.day() > then.day() or (self.day() == then.day() and self.time().cmp(then.time()) == .gt)) {
|
||||
total_months -= 1;
|
||||
}
|
||||
const months = @rem(total_months, 12);
|
||||
const years = @divTrunc(total_months, 12);
|
||||
var pivot_year: i64 = 0;
|
||||
var pivot_month: i64 = 0;
|
||||
var pivot: Time = undefined;
|
||||
|
||||
if (years > 0 or months > 0) {
|
||||
pivot_year = @as(i64, self.year()) + years;
|
||||
pivot_month = @as(i64, self.month()) + months;
|
||||
if (pivot_month > 12) {
|
||||
pivot_year += 1;
|
||||
pivot_month -= 12;
|
||||
}
|
||||
const d: u8 = @min(MONTH_DAYS[@intCast(pivot_month - 1)], self.day());
|
||||
pivot = Time.new(
|
||||
@as(i32, @intCast(pivot_year)),
|
||||
@as(u8, @intCast(pivot_month)),
|
||||
d,
|
||||
self.hour(),
|
||||
self.minute(),
|
||||
self.second(),
|
||||
);
|
||||
} else {
|
||||
pivot = self;
|
||||
}
|
||||
var remaining_time = then.epoch - pivot.epoch;
|
||||
|
||||
var partials = std.ArrayList(i64){};
|
||||
errdefer partials.deinit(alloc);
|
||||
|
||||
try partials.append(alloc, years);
|
||||
try partials.append(alloc, months);
|
||||
|
||||
for (TIME_CHUNKS) |chunk| {
|
||||
const count: i32 = @intCast(@divFloor(remaining_time, chunk));
|
||||
try partials.append(alloc, count);
|
||||
remaining_time -= count * @as(i32, @intCast(chunk));
|
||||
}
|
||||
|
||||
const min: i64 = std.mem.min(i64, partials.items);
|
||||
const max: i64 = std.mem.max(i64, partials.items);
|
||||
|
||||
if (min == 0 and max == 0) {
|
||||
return try alloc.dupe(u8, "0 minutes");
|
||||
}
|
||||
|
||||
var buf = std.ArrayList(u8){};
|
||||
errdefer buf.deinit(alloc);
|
||||
|
||||
var count: i32 = 0;
|
||||
for (partials.items, 0..) |partial, i| {
|
||||
if (partial > 0) {
|
||||
if (count >= 2) break;
|
||||
try buf.appendSlice(alloc, try std.fmt.allocPrint(alloc, "{d} {s}{s}", .{ partial, TIME_STRINGS[i], if (partial > 1) "s" else "" }));
|
||||
if (count == 0 and i < partials.items.len - 1) try buf.appendSlice(alloc, ", ");
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return try buf.toOwnedSlice(alloc);
|
||||
}
|
||||
};
|
||||
|
||||
// https://github.com/cassioneri/eaf/blob/1509faf37a0e0f59f5d4f11d0456fd0973c08f85/eaf/gregorian.hpp#L42
|
||||
fn rata_to_date(N: i64) Date {
|
||||
checkRange(N, RATA_MIN, RATA_MAX);
|
||||
|
||||
// Century.
|
||||
const N_1: i64 = 4 * N + 3;
|
||||
const C: i64 = quotient(N_1, 146097);
|
||||
const N_C: u32 = remainder(N_1, 146097) / 4;
|
||||
|
||||
// Year.
|
||||
const N_2 = 4 * N_C + 3;
|
||||
const Z: u32 = N_2 / 1461;
|
||||
const N_Y: u32 = N_2 % 1461 / 4;
|
||||
const Y: i64 = 100 * C + Z;
|
||||
|
||||
// Month and day.
|
||||
const N_3: u32 = 5 * N_Y + 461;
|
||||
const M: u32 = N_3 / 153;
|
||||
const D: u32 = N_3 % 153 / 5;
|
||||
|
||||
// Map.
|
||||
const J: u32 = @intFromBool(M >= 13);
|
||||
|
||||
return .{
|
||||
.year = @intCast(Y + J),
|
||||
.month = @intCast(M - 12 * J),
|
||||
.day = @intCast(D + 1),
|
||||
};
|
||||
}
|
||||
|
||||
// https://github.com/cassioneri/eaf/blob/1509faf37a0e0f59f5d4f11d0456fd0973c08f85/eaf/gregorian.hpp#L88
|
||||
fn date_to_rata(date: Date) i32 {
|
||||
checkRange(date, Date.MIN, Date.MAX);
|
||||
|
||||
// Map.
|
||||
const J: u32 = @intFromBool(date.month <= 2);
|
||||
const Y: i32 = date.year - @as(i32, @intCast(J));
|
||||
const M: u32 = date.month + 12 * J;
|
||||
const D: u32 = date.day - 1;
|
||||
const C: i32 = @intCast(quotient(Y, 100));
|
||||
|
||||
// Rata die.
|
||||
const y_star: i32 = @intCast(quotient(1461 * @as(i64, Y), 4) - C + quotient(C, 4)); // n_days in all prev. years
|
||||
const m_star: u32 = (153 * M - 457) / 5; // n_days in prev. months
|
||||
|
||||
return y_star + @as(i32, @intCast(m_star)) + @as(i32, @intCast(D));
|
||||
}
|
||||
|
||||
fn quotient(n: i64, d: u32) i64 {
|
||||
return if (n >= 0) @divTrunc(n, d) else @divTrunc((n + 1), d) - 1;
|
||||
}
|
||||
|
||||
fn remainder(n: i64, d: u32) u32 {
|
||||
return @intCast(if (n >= 0) @mod(n, d) else (n + d) - d * quotient((n + d), d));
|
||||
}
|
||||
|
||||
// const testing = @import("testing.zig");
|
||||
/// Attempts to print `arg` into a buf and then compare those strings.
|
||||
pub const allocator = std.testing.allocator;
|
||||
pub fn expectFmt(arg: anytype, expected: []const u8) !void {
|
||||
var wb = std.io.Writer.Allocating.init(allocator);
|
||||
defer wb.deinit();
|
||||
|
||||
try wb.writer.print("{f}", .{arg});
|
||||
try std.testing.expectEqualStrings(expected, wb.written());
|
||||
}
|
||||
|
||||
pub fn expectEqual(res: anytype, expected: meta.Const(@TypeOf(res))) TimeError!void {
|
||||
if (meta.isOptional(@TypeOf(res))) {
|
||||
if (expected) |e| return expectEqual(res orelse return error.ExpectedValue, e);
|
||||
if (res != null) return error.ExpectedNull;
|
||||
}
|
||||
|
||||
// TODO: find all usages of expectEqualStrings and replace it with our expectEqual
|
||||
if (meta.isString(@TypeOf(res))) {
|
||||
return std.testing.expectEqualStrings(expected, res);
|
||||
}
|
||||
|
||||
return std.testing.expectEqual(expected, res);
|
||||
}
|
||||
|
||||
// test "basic usage" {
|
||||
// const t1 = Time.unix(1234567890);
|
||||
// try expectFmt(t1, "2009-02-13 23:31:30 UTC");
|
||||
//
|
||||
// try expectEqual(t1.date(), .{
|
||||
// .year = 2009,
|
||||
// .month = 2,
|
||||
// .day = 13,
|
||||
// });
|
||||
//
|
||||
// try expectEqual(t1.hour(), 23);
|
||||
// try expectEqual(t1.minute(), 31);
|
||||
// try expectEqual(t1.second(), 30);
|
||||
//
|
||||
// const t2 = t1.setHour(10).setMinute(15).setSecond(45);
|
||||
// try expectFmt(t2, "2009-02-13 10:15:45 UTC");
|
||||
//
|
||||
// const t3 = t2.add(.hours, 14).add(.minutes, 46).add(.seconds, 18);
|
||||
// try expectFmt(t3, "2009-02-14 01:02:03 UTC");
|
||||
//
|
||||
// // t.next()
|
||||
// try expectFmt(t3.next(.second), "2009-02-14 01:02:04 UTC");
|
||||
// try expectFmt(t3.next(.minute), "2009-02-14 01:03:00 UTC");
|
||||
// try expectFmt(t3.next(.hour), "2009-02-14 02:00:00 UTC");
|
||||
// try expectFmt(t3.next(.day), "2009-02-15 00:00:00 UTC");
|
||||
//
|
||||
// // t.setStartOf()
|
||||
// try expectFmt(t3.setStartOf(.minute), "2009-02-14 01:02:00 UTC");
|
||||
// try expectFmt(t3.setStartOf(.hour), "2009-02-14 01:00:00 UTC");
|
||||
// try expectFmt(t3.setStartOf(.day), "2009-02-14 00:00:00 UTC");
|
||||
// try expectFmt(t3.setStartOf(.month), "2009-02-01 00:00:00 UTC");
|
||||
// try expectFmt(t3.setStartOf(.year), "2009-01-01 00:00:00 UTC");
|
||||
//
|
||||
// // t.setEndOf()
|
||||
// try expectFmt(t3.setEndOf(.minute), "2009-02-14 01:02:59 UTC");
|
||||
// try expectFmt(t3.setEndOf(.hour), "2009-02-14 01:59:59 UTC");
|
||||
// try expectFmt(t3.setEndOf(.day), "2009-02-14 23:59:59 UTC");
|
||||
// try expectFmt(t3.setEndOf(.month), "2009-02-28 23:59:59 UTC");
|
||||
// try expectFmt(t3.setEndOf(.year), "2009-12-31 23:59:59 UTC");
|
||||
// }
|
||||
//
|
||||
// test "edge-cases" {
|
||||
// const jan31 = Date.ymd(2023, 1, 31);
|
||||
// try expectEqual(jan31.add(.month, 1), Date.ymd(2023, 2, 28));
|
||||
// try expectEqual(jan31.add(.month, 2), Date.ymd(2023, 3, 31));
|
||||
// try expectEqual(jan31.add(.month, -1), Date.ymd(2022, 12, 31));
|
||||
// try expectEqual(jan31.add(.month, -2), Date.ymd(2022, 11, 30));
|
||||
// try expectEqual(jan31.add(.year, 1).add(.month, 1), Date.ymd(2024, 2, 29));
|
||||
//
|
||||
// const feb29 = Time.unix(951782400); // 2000-02-29 00:00:00
|
||||
// try expectFmt(feb29.setEndOf(.month), "2000-02-29 23:59:59 UTC");
|
||||
// try expectFmt(feb29.add(.years, 1), "2001-02-28 00:00:00 UTC");
|
||||
// try expectFmt(feb29.add(.years, 4), "2004-02-29 00:00:00 UTC");
|
||||
// }
|
||||
//
|
||||
// test isLeapYear {
|
||||
// try testing.expect(!isLeapYear(1999));
|
||||
// try testing.expect(isLeapYear(2000));
|
||||
// try testing.expect(isLeapYear(2004));
|
||||
// }
|
||||
//
|
||||
// test daysInMonth {
|
||||
// try expectEqual(daysInMonth(1999, 2), 28);
|
||||
// try expectEqual(daysInMonth(2000, 2), 29);
|
||||
// try expectEqual(daysInMonth(2000, 7), 31);
|
||||
// try expectEqual(daysInMonth(2000, 8), 31);
|
||||
// }
|
||||
//
|
||||
// test rata_to_date {
|
||||
// try expectEqual(rata_to_date(RATA_MIN), Date.MIN);
|
||||
// try expectEqual(rata_to_date(RATA_MAX), Date.MAX);
|
||||
//
|
||||
// try expectEqual(rata_to_date(0), .ymd(0, 3, 1));
|
||||
// try expectEqual(rata_to_date(RATA_TO_UNIX), .ymd(1970, 1, 1));
|
||||
// }
|
||||
//
|
||||
// test date_to_rata {
|
||||
// try expectEqual(date_to_rata(Date.MIN), RATA_MIN);
|
||||
// try expectEqual(date_to_rata(Date.MAX), RATA_MAX);
|
||||
//
|
||||
// try expectEqual(date_to_rata(.ymd(0, 3, 1)), 0);
|
||||
// try expectEqual(date_to_rata(.ymd(1970, 1, 1)), RATA_TO_UNIX);
|
||||
// }
|
||||
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));
|
||||
}
|
||||
26
templates/base.html
Normal file
26
templates/base.html
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="pt-br">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>
|
||||
{% block title %}Meu Site{% endblock %}
|
||||
</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; }
|
||||
header { background: #333; color: white; padding: 20px; }
|
||||
nav ul { list-style: none; padding: 0; }
|
||||
nav li { display: inline; margin: 0 10px; }
|
||||
nav a { color: white; text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{% include "partials/header.html" %}
|
||||
<main>
|
||||
{% block content %}<p>Conteúdo padrão</p>{% endblock %}
|
||||
</main>
|
||||
<footer>
|
||||
<p>© 2025 - Feito com ❤️ e Zig</p>
|
||||
<span>{% now "d/m/Y H:i:s" %}</span>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
28
templates/home.html
Normal file
28
templates/home.html
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Home - Meu Site{% endblock %}
|
||||
|
||||
{% block menu %}
|
||||
{{ block.super }}
|
||||
<li><a href="/blog">Blog</a></li>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Bem-vindo, {{ user.name }}!</h2>
|
||||
<span>{% now "d/m/Y H:i:s" %}</span>
|
||||
<p>Você tem {{ user.notifications }} notificações pendentes.</p>
|
||||
|
||||
<h3>Últimos itens:</h3>
|
||||
<ul>
|
||||
{% for item in itens %}
|
||||
<li>{{ forloop.counter }}: {{ item }}</li>
|
||||
{% empty %}
|
||||
<li>Nenhum item encontrado.</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% with msg="Olá mundo!" %}
|
||||
<p>Mensagem temporária: {{ msg|upper }}</p>
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
12
templates/partials/header.html
Normal file
12
templates/partials/header.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<header>
|
||||
<h1>{{ site_title|upper }}</h1>
|
||||
<nav>
|
||||
<ul>
|
||||
{% block menu %}
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/sobre">Sobre</a></li>
|
||||
{% endblock %}
|
||||
<li><a href="/contato">Contato</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
Loading…
Add table
Add a link
Reference in a new issue