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,
|
.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
|
// This declares intent for the executable to be installed into the
|
||||||
// install prefix when running `zig build` (i.e. when executing the default
|
// 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
|
// 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 Allocator = std.heap.ArenaAllocator;
|
||||||
|
|
||||||
const parser = @import("parser.zig");
|
const parser = @import("parser.zig");
|
||||||
|
const icons = @import("svg/icons.zig");
|
||||||
|
|
||||||
pub const TemplateCache = struct {
|
pub const TemplateCache = struct {
|
||||||
arena: Allocator,
|
arena: Allocator,
|
||||||
cache: std.StringHashMapUnmanaged([]parser.Node),
|
cache: std.StringHashMapUnmanaged([]parser.Node),
|
||||||
default_path: ?[]const u8 = "templates",
|
default_path: ?[]const u8 = "templates",
|
||||||
|
icons: ?icons.SvgIcon =null,
|
||||||
|
|
||||||
pub fn init(child_allocator: std.mem.Allocator) TemplateCache {
|
pub fn init(child_allocator: std.mem.Allocator) TemplateCache {
|
||||||
const arena = std.heap.ArenaAllocator.init(child_allocator);
|
const arena = std.heap.ArenaAllocator.init(child_allocator);
|
||||||
|
|
@ -16,7 +18,6 @@ pub const TemplateCache = struct {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn deinit(self: *TemplateCache) void {
|
pub fn deinit(self: *TemplateCache) void {
|
||||||
self.arena.deinit();
|
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 {
|
pub fn clear(self: *TemplateCache) void {
|
||||||
self.deinit();
|
self.deinit();
|
||||||
self.cache = .{};
|
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") };
|
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)) {
|
return switch (@typeInfo(T)) {
|
||||||
.bool => Value{ .bool = value },
|
.bool => Value{ .bool = value },
|
||||||
.int, .comptime_int => Value{ .int = @intCast(value) },
|
.int, .comptime_int => Value{ .int = @intCast(value) },
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ const Context = @import("context.zig").Context;
|
||||||
const Value = @import("context.zig").Value;
|
const Value = @import("context.zig").Value;
|
||||||
|
|
||||||
test "context set amigável e get com ponto" {
|
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;
|
const allocator = testing.allocator;
|
||||||
var ctx = Context.init(allocator);
|
var ctx = Context.init(allocator);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
@ -17,17 +20,26 @@ test "context set amigável e get com ponto" {
|
||||||
// struct
|
// struct
|
||||||
const Person = struct { nome: []const u8, idade: i64 };
|
const Person = struct { nome: []const u8, idade: i64 };
|
||||||
const p = Person{ .nome = "Ana", .idade = 25 };
|
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
|
// list
|
||||||
const numeros = [_]i64{ 1, 2, 3 };
|
const numeros = [_]i64{ 1, 2, 3 };
|
||||||
try ctx.set("lista", numeros);
|
try ctx.set("lista", numeros);
|
||||||
|
|
||||||
|
for (ctx.get("user").?.list) |item| {
|
||||||
|
std.debug.print("user {any}\n", .{item.dict.get("nome").?});
|
||||||
|
}
|
||||||
|
|
||||||
// acesso
|
// acesso
|
||||||
try testing.expectEqualStrings("Lucas", ctx.get("nome").?.string);
|
try testing.expectEqualStrings("Lucas", ctx.get("nome").?.string);
|
||||||
try testing.expect(ctx.get("idade").?.int == 30);
|
try testing.expect(ctx.get("idade").?.int == 30);
|
||||||
try testing.expectEqualStrings("Ana", ctx.get("user.nome").?.string);
|
// try testing.expectEqualStrings("Ana", ctx.get("user.nome").?.string);
|
||||||
try testing.expect(ctx.get("user.idade").?.int == 25);
|
// try testing.expect(ctx.get("user.idade").?.int == 25);
|
||||||
try testing.expect(ctx.get("lista.1").?.int == 2);
|
try testing.expect(ctx.get("lista.1").?.int == 2);
|
||||||
try testing.expect(ctx.get("vazio").?.string.len == 0);
|
try testing.expect(ctx.get("vazio").?.string.len == 0);
|
||||||
try testing.expect(ctx.get("preco").?.float == 99.99);
|
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;
|
const RelativeDelta = @import("delta.zig").RelativeDelta;
|
||||||
|
|
||||||
test "relativedelta rigoroso - meses" {
|
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 a = try Time.parse("2025-03-31");
|
||||||
const b = try Time.parse("2025-01-31");
|
const b = try Time.parse("2025-01-31");
|
||||||
const delta = a.subRelative(b);
|
const delta = a.subRelative(b);
|
||||||
|
|
@ -17,6 +20,9 @@ test "relativedelta rigoroso - meses" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "relativedelta - overflow de dia" {
|
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 jan31 = try Time.parse("2023-01-31");
|
||||||
const mar01 = try Time.parse("2023-03-01");
|
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" {
|
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 a = try Time.parse("2021-02-28");
|
||||||
const b = try Time.parse("2020-02-29");
|
const b = try Time.parse("2020-02-29");
|
||||||
const delta = a.subRelative(b);
|
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" {
|
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 a = try Time.parse("2021-03-01");
|
||||||
const b = try Time.parse("2020-02-29");
|
const b = try Time.parse("2020-02-29");
|
||||||
const delta = a.subRelative(b);
|
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" {
|
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 a = try Time.parse("2021-02-27");
|
||||||
const b = try Time.parse("2020-02-29");
|
const b = try Time.parse("2020-02-29");
|
||||||
const delta = a.subRelative(b);
|
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" {
|
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 a = try Time.parse("2020-02-29");
|
||||||
const b = try Time.parse("2021-02-28");
|
const b = try Time.parse("2021-02-28");
|
||||||
const delta = a.subRelative(b);
|
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)" {
|
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
|
// 2023 não é bissexto
|
||||||
const base = try Time.parse("2023-06-15");
|
const base = try Time.parse("2023-06-15");
|
||||||
const expected = try Time.parse("2026-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" {
|
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)
|
// Janeiro 31 + 2 anos → deve ir para 31/jan (2025 não bissexto)
|
||||||
const base = try Time.parse("2023-01-31");
|
const base = try Time.parse("2023-01-31");
|
||||||
const expected = try Time.parse("2025-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)" {
|
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)
|
// 2020 foi bissexto → +1 ano deve ir para 2021-02-28 (não bissexto)
|
||||||
const base = try Time.parse("2020-02-29");
|
const base = try Time.parse("2020-02-29");
|
||||||
const expected = try Time.parse("2021-02-28");
|
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)" {
|
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)
|
// 2020 → 2024 (ambos bissextos)
|
||||||
const base = try Time.parse("2020-02-29");
|
const base = try Time.parse("2020-02-29");
|
||||||
const expected = try Time.parse("2024-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)" {
|
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
|
// 2020-02-29 + 1 ano → 2021-02-28 + 1 mês → 2021-03-28
|
||||||
const base = try Time.parse("2020-02-29");
|
const base = try Time.parse("2020-02-29");
|
||||||
const expected = try Time.parse("2021-03-28");
|
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)" {
|
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 }{
|
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 = "2023-01-31", .months = 1, .expected = "2023-02-28" }, // não bissexto
|
||||||
.{ .base = "2024-01-31", .months = 1, .expected = "2024-02-29" }, // 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)" {
|
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
|
// 2024-02-29 + 1 ano + 2 meses + 3 dias
|
||||||
// → 2025-02-28 + 2 meses → 2025-04-28 + 3 dias → 2025-05-01
|
// → 2025-02-28 + 2 meses → 2025-04-28 + 3 dias → 2025-05-01
|
||||||
const base = try Time.parse("2024-02-29");
|
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" {
|
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 base = try Time.parse("2025-07-20");
|
||||||
const delta = RelativeDelta.init(.{});
|
const delta = RelativeDelta.init(.{});
|
||||||
const result = base.addRelative(delta);
|
const result = base.addRelative(delta);
|
||||||
|
|
|
||||||
102
src/parser.zig
102
src/parser.zig
|
|
@ -45,6 +45,7 @@ pub const TagNodeBody = union(enum) {
|
||||||
resetcycle: ResetCycleNode,
|
resetcycle: ResetCycleNode,
|
||||||
spaceless: SpacelessNode,
|
spaceless: SpacelessNode,
|
||||||
super: bool,
|
super: bool,
|
||||||
|
svg: SvgNode,
|
||||||
templatetag: TemplateTagNode,
|
templatetag: TemplateTagNode,
|
||||||
url: UrlNode,
|
url: UrlNode,
|
||||||
verbatim: VerbatimNode,
|
verbatim: VerbatimNode,
|
||||||
|
|
@ -88,6 +89,7 @@ pub const TagKind = enum {
|
||||||
resetcycle,
|
resetcycle,
|
||||||
spaceless,
|
spaceless,
|
||||||
super,
|
super,
|
||||||
|
svg,
|
||||||
templatetag,
|
templatetag,
|
||||||
url,
|
url,
|
||||||
verbatim,
|
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, "resetcycle")) return .resetcycle;
|
||||||
if (std.mem.eql(u8, name, "spaceless")) return .spaceless;
|
if (std.mem.eql(u8, name, "spaceless")) return .spaceless;
|
||||||
if (std.mem.eql(u8, name, "super")) return .super;
|
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, "templatetag")) return .templatetag;
|
||||||
if (std.mem.eql(u8, name, "url")) return .url;
|
if (std.mem.eql(u8, name, "url")) return .url;
|
||||||
if (std.mem.eql(u8, name, "verbatim")) return .verbatim;
|
if (std.mem.eql(u8, name, "verbatim")) return .verbatim;
|
||||||
|
|
@ -271,6 +274,11 @@ pub const SpacelessNode = struct {
|
||||||
raw_close: []const u8,
|
raw_close: []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const SvgNode = struct {
|
||||||
|
kind: []const u8,
|
||||||
|
name: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
pub const TagNode = struct {
|
pub const TagNode = struct {
|
||||||
kind: TagKind,
|
kind: TagKind,
|
||||||
args: []const u8,
|
args: []const u8,
|
||||||
|
|
@ -379,6 +387,10 @@ pub const Node = struct {
|
||||||
for (body_copy) |n| n.deinit(allocator);
|
for (body_copy) |n| n.deinit(allocator);
|
||||||
allocator.free(body_copy);
|
allocator.free(body_copy);
|
||||||
},
|
},
|
||||||
|
.svg => {
|
||||||
|
allocator.free(t.body.svg.kind);
|
||||||
|
allocator.free(t.body.svg.name);
|
||||||
|
},
|
||||||
.url => {
|
.url => {
|
||||||
allocator.free(t.body.url.args);
|
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 => {
|
.url => {
|
||||||
const args_copy = try allocator.alloc([]const u8, self.tag.?.body.url.args.len);
|
const args_copy = try allocator.alloc([]const u8, self.tag.?.body.url.args.len);
|
||||||
errdefer allocator.free(args_copy);
|
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;
|
current_body = &false_body;
|
||||||
continue;
|
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
|
// Qualquer outra tag
|
||||||
try current_body.append(allocator, tag_node);
|
try current_body.append(allocator, tag_node);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1734,6 +1776,18 @@ pub const Parser = struct {
|
||||||
continue;
|
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);
|
try current_body.append(allocator, tag_node);
|
||||||
} else {
|
} else {
|
||||||
self.advance(1);
|
self.advance(1);
|
||||||
|
|
@ -2088,6 +2142,50 @@ pub const Parser = struct {
|
||||||
const spaceless_node = try self.parseSpacelessBlock(allocator, raw_open);
|
const spaceless_node = try self.parseSpacelessBlock(allocator, raw_open);
|
||||||
return spaceless_node;
|
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 => {
|
.templatetag => {
|
||||||
const args = tag_node.tag.?.args;
|
const args = tag_node.tag.?.args;
|
||||||
const templatetag = TemplateTagNode.parse(args);
|
const templatetag = TemplateTagNode.parse(args);
|
||||||
|
|
@ -2220,7 +2318,7 @@ pub const Parser = struct {
|
||||||
try self.parseComment();
|
try self.parseComment();
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
std.log.debug("Tag: {s}", .{tag.?.tag.?.raw});
|
// std.log.debug("Tag: {s}", .{tag.?.tag.?.raw});
|
||||||
if (try self.parseTagContent(allocator, tag.?)) |tn| {
|
if (try self.parseTagContent(allocator, tag.?)) |tn| {
|
||||||
tag.?.tag.?.body = tn;
|
tag.?.tag.?.body = tn;
|
||||||
try list.append(allocator, tag.?);
|
try list.append(allocator, tag.?);
|
||||||
|
|
|
||||||
|
|
@ -712,7 +712,7 @@ test "parse simple lorem" {
|
||||||
const l = nodes[0].tag.?.body.lorem;
|
const l = nodes[0].tag.?.body.lorem;
|
||||||
try testing.expect(l.count == null);
|
try testing.expect(l.count == null);
|
||||||
try testing.expect(l.method == null);
|
try testing.expect(l.method == null);
|
||||||
try testing.expect(l.format == null);
|
try testing.expect(l.random == false);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parse lorem with arguments" {
|
test "parse lorem with arguments" {
|
||||||
|
|
@ -720,7 +720,7 @@ test "parse lorem with arguments" {
|
||||||
std.debug.print("32 - parse lorem with arguments\n", .{});
|
std.debug.print("32 - parse lorem with arguments\n", .{});
|
||||||
|
|
||||||
const allocator = testing.allocator;
|
const allocator = testing.allocator;
|
||||||
const template = "{% lorem 5 p html %}";
|
const template = "{% lorem 5 p true %}";
|
||||||
var p = parser.Parser.init(template);
|
var p = parser.Parser.init(template);
|
||||||
const nodes = try p.parse(allocator);
|
const nodes = try p.parse(allocator);
|
||||||
defer {
|
defer {
|
||||||
|
|
@ -734,7 +734,7 @@ test "parse lorem with arguments" {
|
||||||
const l = nodes[0].tag.?.body.lorem;
|
const l = nodes[0].tag.?.body.lorem;
|
||||||
try testing.expectEqualStrings("5", l.count.?);
|
try testing.expectEqualStrings("5", l.count.?);
|
||||||
try testing.expectEqualStrings("p", l.method.?);
|
try testing.expectEqualStrings("p", l.method.?);
|
||||||
try testing.expectEqualStrings("html", l.format.?);
|
try testing.expect(l.random == true);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parse simple now" {
|
test "parse simple now" {
|
||||||
|
|
@ -1225,6 +1225,22 @@ test "parse simple with block" {
|
||||||
try testing.expect(w.body[0].type == .text);
|
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" {
|
// test "parse simple tag" {
|
||||||
// std.debug.print("____________________________________________________\n", .{});
|
// std.debug.print("____________________________________________________\n", .{});
|
||||||
// std.debug.print("54 - parse simple tag\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 TemplateCache = @import("cache.zig").TemplateCache;
|
||||||
const time = @import("time.zig");
|
const time = @import("time.zig");
|
||||||
const lorem = @import("lorem.zig");
|
const lorem = @import("lorem.zig");
|
||||||
|
const icons = @import("svg/icons.zig");
|
||||||
|
|
||||||
pub const RenderError = error{
|
pub const RenderError = error{
|
||||||
InvalidCharacter,
|
InvalidCharacter,
|
||||||
|
|
@ -20,6 +21,7 @@ pub const RenderError = error{
|
||||||
Overflow,
|
Overflow,
|
||||||
Unexpected,
|
Unexpected,
|
||||||
UnsupportedExpression,
|
UnsupportedExpression,
|
||||||
|
// } || FilterError || parser.ParserError || icons.SvgError || std.fs.File.OpenError;
|
||||||
} || FilterError || parser.ParserError || std.fs.File.OpenError;
|
} || FilterError || parser.ParserError || std.fs.File.OpenError;
|
||||||
|
|
||||||
pub const Renderer = struct {
|
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 {
|
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);
|
var arena = std.heap.ArenaAllocator.init(self.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
const alloc = arena.allocator();
|
const alloc = arena.allocator();
|
||||||
|
|
||||||
if (cache_key) |ck| {
|
// if (cache_key) |ck| {
|
||||||
if (self.cache.get(ck)) |cached_nodes| {
|
// if (self.cache.get(ck)) |cached_nodes| {
|
||||||
try self.renderNodes(alloc, cached_nodes, writer);
|
// try self.renderNodes(alloc, cached_nodes, writer);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
var p = parser.Parser.init(template);
|
var p = parser.Parser.init(template);
|
||||||
const nodes = try p.parse(alloc);
|
const nodes = try p.parse(alloc);
|
||||||
|
|
@ -100,15 +103,15 @@ pub const Renderer = struct {
|
||||||
alloc.free(nodes);
|
alloc.free(nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cache_key) |ck| {
|
// if (cache_key) |ck| {
|
||||||
var alc = self.cache.allocator();
|
// var alc = self.cache.allocator();
|
||||||
var cached_nodes = try alc.alloc(parser.Node, nodes.len);
|
// var cached_nodes = try alc.alloc(parser.Node, nodes.len);
|
||||||
errdefer alc.free(cached_nodes);
|
// errdefer alc.free(cached_nodes);
|
||||||
for (nodes, 0..) |node, i| {
|
// for (nodes, 0..) |node, i| {
|
||||||
cached_nodes[i] = try node.clone(self.allocator);
|
// cached_nodes[i] = try node.clone(self.allocator);
|
||||||
}
|
// }
|
||||||
try self.cache.add(ck, nodes);
|
// try self.cache.add(ck, nodes);
|
||||||
}
|
// }
|
||||||
|
|
||||||
return try self.renderNodes(alloc, nodes, writer);
|
return try self.renderNodes(alloc, nodes, writer);
|
||||||
}
|
}
|
||||||
|
|
@ -192,15 +195,15 @@ pub const Renderer = struct {
|
||||||
.tag => {
|
.tag => {
|
||||||
switch (node.tag.?.kind) {
|
switch (node.tag.?.kind) {
|
||||||
.if_block => {
|
.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) {
|
if (condition) {
|
||||||
for (node.tag.?.body.@"if".true_body) |child| {
|
for (node.tag.?.body.@"if".true_body) |child| {
|
||||||
try self.renderNode(alloc, nodes, child, writer, null, null);
|
try self.renderNode(alloc, nodes, child, writer, context, null);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (node.tag.?.body.@"if".false_body) |child| {
|
for (node.tag.?.body.@"if".false_body) |child| {
|
||||||
try self.renderNode(alloc, nodes, child, writer, null, null);
|
try self.renderNode(alloc, nodes, child, writer, context, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -227,11 +230,31 @@ pub const Renderer = struct {
|
||||||
else => return,
|
else => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (list) |item| {
|
for (list, 0..) |item, i| {
|
||||||
var ctx = Context.init(alloc);
|
var ctx = Context.init(alloc);
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
try ctx.set(node.tag.?.body.@"for".loop_var, item);
|
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| {
|
for (node.tag.?.body.@"for".body) |child| {
|
||||||
try self.renderNode(alloc, nodes, child, writer, &ctx, null);
|
try self.renderNode(alloc, nodes, child, writer, &ctx, null);
|
||||||
|
|
@ -357,7 +380,7 @@ pub const Renderer = struct {
|
||||||
if (random == false) {
|
if (random == false) {
|
||||||
try writer.writeAll(lorem.LOREM_COMMON_P);
|
try writer.writeAll(lorem.LOREM_COMMON_P);
|
||||||
return;
|
return;
|
||||||
}else {
|
} else {
|
||||||
try writer.writeAll(try lorem.sentence(alloc));
|
try writer.writeAll(try lorem.sentence(alloc));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -374,7 +397,28 @@ pub const Renderer = struct {
|
||||||
return;
|
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;
|
_ = self;
|
||||||
var w = buf.writer(alloc);
|
var w = buf.writer(alloc);
|
||||||
switch (value) {
|
switch (value) {
|
||||||
.null => try w.writeAll("null"),
|
.null => try w.writeAll(""),
|
||||||
.bool => |b| try w.print("{}", .{b}),
|
.bool => |b| try w.print("{}", .{b}),
|
||||||
.int => |n| try w.print("{d}", .{n}),
|
.int => |n| try w.print("{d}", .{n}),
|
||||||
.float => |f| try w.print("{d}", .{f}),
|
.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");
|
const trimmed = std.mem.trim(u8, expr, " \t\r\n");
|
||||||
if (trimmed.len == 0) return false;
|
if (trimmed.len == 0) return false;
|
||||||
|
|
||||||
|
|
@ -489,7 +533,12 @@ pub const Renderer = struct {
|
||||||
const op = tokens.items[1];
|
const op = tokens.items[1];
|
||||||
const right_str = tokens.items[2];
|
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);
|
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, .gt);
|
||||||
|
|
|
||||||
|
|
@ -1068,3 +1068,33 @@ test "renderer - lorem paragraphs random" {
|
||||||
const spaces = std.mem.count(u8, buf.items, "<p>");
|
const spaces = std.mem.count(u8, buf.items, "<p>");
|
||||||
try testing.expect(spaces == 3);
|
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 context = @import("context.zig");
|
||||||
pub const delta = @import("delta.zig");
|
pub const delta = @import("delta.zig");
|
||||||
pub const filters = @import("filters.zig");
|
pub const filters = @import("filters.zig");
|
||||||
|
pub const icons = @import("svg/icons.zig");
|
||||||
pub const lorem = @import("lorem.zig");
|
pub const lorem = @import("lorem.zig");
|
||||||
pub const meta = @import("meta.zig");
|
pub const meta = @import("meta.zig");
|
||||||
pub const parser = @import("parser.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());
|
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 {
|
pub fn today() Time {
|
||||||
return unix(0).setDate(.today());
|
return unix(0).setDate(.today());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue