Compare commits
16 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f38f43dbc4 | ||
|
|
47f52fea58 | ||
|
|
20e4bacab0 | ||
|
|
8bf9902ebf | ||
|
|
777b13ed98 | ||
|
|
fadae5d39c | ||
|
|
ea30958701 | ||
|
|
a6d8795c79 | ||
|
|
59e543ca89 | ||
|
|
7beb8758a5 | ||
|
|
1181f0fa68 | ||
|
|
f0115a15b9 | ||
|
|
a0645cb0d1 | ||
|
|
8fd9086d98 | ||
|
|
cce0c6a954 | ||
|
|
966035dccb |
12 changed files with 428 additions and 33 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,11 +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);
|
||||
|
|
@ -16,7 +18,6 @@ pub const TemplateCache = struct {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
pub fn deinit(self: *TemplateCache) void {
|
||||
self.arena.deinit();
|
||||
}
|
||||
|
|
@ -51,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 = .{};
|
||||
|
|
|
|||
|
|
@ -42,6 +42,18 @@ pub const Context = struct {
|
|||
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) },
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ 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);
|
||||
|
|
@ -17,6 +20,9 @@ test "relativedelta rigoroso - meses" {
|
|||
}
|
||||
|
||||
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");
|
||||
|
||||
|
|
@ -27,6 +33,9 @@ test "relativedelta - overflow de dia" {
|
|||
}
|
||||
|
||||
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);
|
||||
|
|
@ -36,6 +45,9 @@ test "bissexto: 2021-02-28 - 2020-02-29" {
|
|||
}
|
||||
|
||||
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);
|
||||
|
|
@ -45,6 +57,9 @@ test "bissexto: 2021-03-01 - 2020-02-29" {
|
|||
}
|
||||
|
||||
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);
|
||||
|
|
@ -54,6 +69,9 @@ test "bissexto: 2021-02-27 - 2020-02-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);
|
||||
|
|
@ -63,6 +81,9 @@ test "bissexto reverso: 2020-02-29 - 2021-02-28" {
|
|||
}
|
||||
|
||||
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");
|
||||
|
|
@ -77,6 +98,9 @@ test "addRelative: anos normais (não bissexto)" {
|
|||
}
|
||||
|
||||
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");
|
||||
|
|
@ -91,6 +115,9 @@ test "addRelative: anos normais com overflow de dia" {
|
|||
}
|
||||
|
||||
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");
|
||||
|
|
@ -105,6 +132,9 @@ test "addRelative: de 29/fev bissexto + 1 ano (vai para 28/fev)" {
|
|||
}
|
||||
|
||||
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");
|
||||
|
|
@ -119,6 +149,9 @@ test "addRelative: de 29/fev bissexto + 4 anos (permanece 29/fev)" {
|
|||
}
|
||||
|
||||
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");
|
||||
|
|
@ -133,6 +166,9 @@ test "addRelative: de 29/fev + 1 ano + 1 mês (vai para março)" {
|
|||
}
|
||||
|
||||
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
|
||||
|
|
@ -156,6 +192,9 @@ test "addRelative: meses com overflow (31 → 28/30)" {
|
|||
}
|
||||
|
||||
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");
|
||||
|
|
@ -171,6 +210,9 @@ test "addRelative: combinação anos + meses + dias (bissexto envolvido)" {
|
|||
}
|
||||
|
||||
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);
|
||||
|
|
|
|||
102
src/parser.zig
102
src/parser.zig
|
|
@ -45,6 +45,7 @@ pub const TagNodeBody = union(enum) {
|
|||
resetcycle: ResetCycleNode,
|
||||
spaceless: SpacelessNode,
|
||||
super: bool,
|
||||
svg: SvgNode,
|
||||
templatetag: TemplateTagNode,
|
||||
url: UrlNode,
|
||||
verbatim: VerbatimNode,
|
||||
|
|
@ -88,6 +89,7 @@ pub const TagKind = enum {
|
|||
resetcycle,
|
||||
spaceless,
|
||||
super,
|
||||
svg,
|
||||
templatetag,
|
||||
url,
|
||||
verbatim,
|
||||
|
|
@ -135,6 +137,7 @@ fn getTagKindByName(name: []const u8) TagKind {
|
|||
if (std.mem.eql(u8, name, "resetcycle")) return .resetcycle;
|
||||
if (std.mem.eql(u8, name, "spaceless")) return .spaceless;
|
||||
if (std.mem.eql(u8, name, "super")) return .super;
|
||||
if (std.mem.eql(u8, name, "svg")) return .svg;
|
||||
if (std.mem.eql(u8, name, "templatetag")) return .templatetag;
|
||||
if (std.mem.eql(u8, name, "url")) return .url;
|
||||
if (std.mem.eql(u8, name, "verbatim")) return .verbatim;
|
||||
|
|
@ -271,6 +274,11 @@ pub const SpacelessNode = struct {
|
|||
raw_close: []const u8,
|
||||
};
|
||||
|
||||
pub const SvgNode = struct {
|
||||
kind: []const u8,
|
||||
name: []const u8,
|
||||
};
|
||||
|
||||
pub const TagNode = struct {
|
||||
kind: TagKind,
|
||||
args: []const u8,
|
||||
|
|
@ -379,6 +387,10 @@ pub const Node = struct {
|
|||
for (body_copy) |n| n.deinit(allocator);
|
||||
allocator.free(body_copy);
|
||||
},
|
||||
.svg => {
|
||||
allocator.free(t.body.svg.kind);
|
||||
allocator.free(t.body.svg.name);
|
||||
},
|
||||
.url => {
|
||||
allocator.free(t.body.url.args);
|
||||
},
|
||||
|
|
@ -697,6 +709,23 @@ pub const Node = struct {
|
|||
},
|
||||
};
|
||||
},
|
||||
.svg => {
|
||||
return Node{
|
||||
.type = .tag,
|
||||
.tag = .{
|
||||
.kind = .svg,
|
||||
.args = try allocator.dupe(u8, self.tag.?.args),
|
||||
.raw = try allocator.dupe(u8, self.tag.?.raw),
|
||||
|
||||
.body = .{
|
||||
.svg = .{
|
||||
.name = try allocator.dupe(u8, self.tag.?.body.svg.name),
|
||||
.kind = try allocator.dupe(u8, self.tag.?.body.svg.kind),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
.url => {
|
||||
const args_copy = try allocator.alloc([]const u8, self.tag.?.body.url.args.len);
|
||||
errdefer allocator.free(args_copy);
|
||||
|
|
@ -931,7 +960,7 @@ pub const Node = struct {
|
|||
},
|
||||
};
|
||||
},
|
||||
else => unreachable,
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -1667,6 +1696,19 @@ pub const Parser = struct {
|
|||
current_body = &false_body;
|
||||
continue;
|
||||
}
|
||||
|
||||
var tag: Node = tag_node;
|
||||
if (tag_node.tag.?.kind == .comment) {
|
||||
try self.parseComment();
|
||||
continue;
|
||||
} else {
|
||||
if (try self.parseTagContent(allocator, tag_node)) |tn| {
|
||||
tag.tag.?.body = tn;
|
||||
try current_body.append(allocator, tag);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Qualquer outra tag
|
||||
try current_body.append(allocator, tag_node);
|
||||
} else {
|
||||
|
|
@ -1734,6 +1776,18 @@ pub const Parser = struct {
|
|||
continue;
|
||||
}
|
||||
|
||||
var tag: Node = tag_node;
|
||||
if (tag_node.tag.?.kind == .comment) {
|
||||
try self.parseComment();
|
||||
continue;
|
||||
} else {
|
||||
if (try self.parseTagContent(allocator, tag_node)) |tn| {
|
||||
tag.tag.?.body = tn;
|
||||
try body.append(allocator, tag);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
try current_body.append(allocator, tag_node);
|
||||
} else {
|
||||
self.advance(1);
|
||||
|
|
@ -2088,6 +2142,50 @@ pub const Parser = struct {
|
|||
const spaceless_node = try self.parseSpacelessBlock(allocator, raw_open);
|
||||
return spaceless_node;
|
||||
},
|
||||
.svg => {
|
||||
const args = tag_node.tag.?.args;
|
||||
|
||||
var values = std.ArrayList([]const u8){};
|
||||
defer values.deinit(allocator);
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < args.len) {
|
||||
while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {}
|
||||
|
||||
if (i >= args.len) break;
|
||||
|
||||
const start = i;
|
||||
var in_quote = false;
|
||||
var quote_char: u8 = 0;
|
||||
if (args[i] == '"' or args[i] == '\'') {
|
||||
in_quote = true;
|
||||
quote_char = args[i];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
while (i < args.len) {
|
||||
if (in_quote) {
|
||||
if (args[i] == quote_char) {
|
||||
i += 1;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (std.ascii.isWhitespace(args[i])) break;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
const value = std.mem.trim(u8, args[start..i], " \t\r\n\"'");
|
||||
// std.debug.print("value: {s}\n", .{value});
|
||||
try values.append(allocator, value);
|
||||
}
|
||||
return TagNodeBody{
|
||||
.svg = .{
|
||||
.kind = try allocator.dupe(u8, values.items[0]),
|
||||
.name = try allocator.dupe(u8, values.items[1]),
|
||||
},
|
||||
};
|
||||
},
|
||||
.templatetag => {
|
||||
const args = tag_node.tag.?.args;
|
||||
const templatetag = TemplateTagNode.parse(args);
|
||||
|
|
@ -2220,7 +2318,7 @@ pub const Parser = struct {
|
|||
try self.parseComment();
|
||||
continue;
|
||||
} else {
|
||||
std.log.debug("Tag: {s}", .{tag.?.tag.?.raw});
|
||||
// std.log.debug("Tag: {s}", .{tag.?.tag.?.raw});
|
||||
if (try self.parseTagContent(allocator, tag.?)) |tn| {
|
||||
tag.?.tag.?.body = tn;
|
||||
try list.append(allocator, tag.?);
|
||||
|
|
|
|||
|
|
@ -712,7 +712,7 @@ test "parse simple lorem" {
|
|||
const l = nodes[0].tag.?.body.lorem;
|
||||
try testing.expect(l.count == null);
|
||||
try testing.expect(l.method == null);
|
||||
try testing.expect(l.format == null);
|
||||
try testing.expect(l.random == false);
|
||||
}
|
||||
|
||||
test "parse lorem with arguments" {
|
||||
|
|
@ -720,7 +720,7 @@ test "parse lorem with arguments" {
|
|||
std.debug.print("32 - parse lorem with arguments\n", .{});
|
||||
|
||||
const allocator = testing.allocator;
|
||||
const template = "{% lorem 5 p html %}";
|
||||
const template = "{% lorem 5 p true %}";
|
||||
var p = parser.Parser.init(template);
|
||||
const nodes = try p.parse(allocator);
|
||||
defer {
|
||||
|
|
@ -734,7 +734,7 @@ test "parse lorem with arguments" {
|
|||
const l = nodes[0].tag.?.body.lorem;
|
||||
try testing.expectEqualStrings("5", l.count.?);
|
||||
try testing.expectEqualStrings("p", l.method.?);
|
||||
try testing.expectEqualStrings("html", l.format.?);
|
||||
try testing.expect(l.random == true);
|
||||
}
|
||||
|
||||
test "parse simple now" {
|
||||
|
|
@ -1225,6 +1225,22 @@ test "parse simple with block" {
|
|||
try testing.expect(w.body[0].type == .text);
|
||||
}
|
||||
|
||||
|
||||
test "parse svg" {
|
||||
std.debug.print("____________________________________________________\n", .{});
|
||||
std.debug.print("54 - parse svg\n", .{});
|
||||
|
||||
const allocator = testing.allocator;
|
||||
const template = "{% svg \"material\" \"account_arrow_left\" %}";
|
||||
var p = parser.Parser.init(template);
|
||||
const nodes = try p.parse(allocator);
|
||||
defer {
|
||||
for (nodes) |node| node.deinit(allocator);
|
||||
allocator.free(nodes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// test "parse simple tag" {
|
||||
// std.debug.print("____________________________________________________\n", .{});
|
||||
// std.debug.print("54 - parse simple tag\n", .{});
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ 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 = error{
|
||||
InvalidCharacter,
|
||||
|
|
@ -20,6 +21,7 @@ pub const RenderError = error{
|
|||
Overflow,
|
||||
Unexpected,
|
||||
UnsupportedExpression,
|
||||
// } || FilterError || parser.ParserError || icons.SvgError || std.fs.File.OpenError;
|
||||
} || FilterError || parser.ParserError || std.fs.File.OpenError;
|
||||
|
||||
pub const Renderer = struct {
|
||||
|
|
@ -82,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);
|
||||
|
|
@ -100,15 +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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
|
@ -192,15 +195,15 @@ pub const Renderer = struct {
|
|||
.tag => {
|
||||
switch (node.tag.?.kind) {
|
||||
.if_block => {
|
||||
const condition = try self.evaluateCondition(alloc, node.tag.?.body.@"if".condition);
|
||||
const condition = try self.evaluateCondition(alloc, node.tag.?.body.@"if".condition, context);
|
||||
|
||||
if (condition) {
|
||||
for (node.tag.?.body.@"if".true_body) |child| {
|
||||
try self.renderNode(alloc, nodes, child, writer, null, null);
|
||||
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, null, null);
|
||||
try self.renderNode(alloc, nodes, child, writer, context, null);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -227,11 +230,31 @@ pub const Renderer = struct {
|
|||
else => return,
|
||||
};
|
||||
|
||||
for (list) |item| {
|
||||
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);
|
||||
|
|
@ -374,7 +397,28 @@ pub const Renderer = struct {
|
|||
return;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
.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(" -->");
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -433,7 +477,7 @@ pub const Renderer = struct {
|
|||
_ = 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}),
|
||||
|
|
@ -456,7 +500,7 @@ pub const Renderer = struct {
|
|||
};
|
||||
}
|
||||
|
||||
fn evaluateCondition(self: *const Renderer, allocator: Allocator, expr: []const u8) RenderError!bool {
|
||||
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;
|
||||
|
||||
|
|
@ -489,7 +533,12 @@ pub const Renderer = struct {
|
|||
const op = tokens.items[1];
|
||||
const right_str = tokens.items[2];
|
||||
|
||||
const left_value = self.context.get(left) orelse Value.null;
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -1068,3 +1068,33 @@ test "renderer - lorem paragraphs random" {
|
|||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ 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");
|
||||
|
|
|
|||
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>
|
||||
;
|
||||
|
|
@ -350,6 +350,10 @@ pub const Time = struct {
|
|||
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());
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue