backup ideas

This commit is contained in:
Lucas F. 2026-01-12 09:09:51 -03:00
parent 9bde4370a6
commit efd19a8a81
9 changed files with 2991 additions and 382 deletions

51
src/cache_bkp.zig Normal file
View file

@ -0,0 +1,51 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const parser = @import("parser.zig");
pub const TemplateCache = struct {
allocator: Allocator,
cache: std.StringHashMapUnmanaged([]parser.Node),
pub fn init(allocator: Allocator) TemplateCache {
return .{
.allocator = allocator,
.cache = .{},
};
}
pub fn deinit(self: *TemplateCache) void {
var it = self.cache.iterator();
while (it.next()) |entry| {
self.allocator.free(entry.key_ptr.*);
for (entry.value_ptr.*) |node| node.deinit(self.allocator);
self.allocator.free(entry.value_ptr.*);
}
self.cache.deinit(self.allocator);
}
pub fn get(self: *const TemplateCache, key: []const u8) ?[]parser.Node {
return self.cache.get(key);
}
pub fn add(self: *TemplateCache, key: []const u8, nodes: []parser.Node) !void {
const key_copy = try self.allocator.dupe(u8, key);
errdefer self.allocator.free(key_copy);
try self.cache.put(self.allocator, key_copy, nodes);
}
pub fn invalidate(self: *TemplateCache, key: []const u8) void {
if (self.cache.getEntry(key)) |entry| {
self.allocator.free(entry.key_ptr.*);
for (entry.value_ptr.*) |node| node.deinit(self.allocator);
self.allocator.free(entry.value_ptr.*);
_ = self.cache.remove(key);
}
}
pub fn clear(self: *TemplateCache) void {
self.deinit();
self.cache = .{};
}
};

81
src/context_proposta.zig Normal file
View file

@ -0,0 +1,81 @@
const std = @import("std");
pub const Value = union(enum) {
null,
bool: bool,
int: i64,
float: f64,
string: []const u8,
list: []Value,
dict: std.HashMapUnmanaged([]const u8, Value, std.hash_map.StringContext, 80),
struct_: std.HashMapUnmanaged([]const u8, Value, std.hash_map.StringContext, 80),
pub fn deinit(self: *Value, allocator: std.mem.Allocator) void {
switch (self.*) {
.string => allocator.free(self.string),
.list => {
for (self.list) |*v| v.deinit(allocator);
allocator.free(self.list);
},
.dict => {
var iter = self.dict.iterator();
while (iter.next()) |entry| {
allocator.free(entry.key_ptr.*);
entry.value_ptr.deinit(allocator);
}
self.dict.deinit(allocator);
},
.struct_ => {
var iter = self.struct_.iterator();
while (iter.next()) |entry| {
allocator.free(entry.key_ptr.*);
entry.value_ptr.deinit(allocator);
}
self.struct_.deinit(allocator);
},
else => {},
}
}
};
pub const Context = struct {
allocator: std.mem.Allocator,
map: std.HashMapUnmanaged([]const u8, Value, std.hash_map.StringContext, 80),
parent: ?*Context = null,
pub fn init(allocator: std.mem.Allocator) Context {
return .{
.allocator = allocator,
.map = .{},
};
}
pub fn deinit(self: *Context) void {
var iter = self.map.iterator();
while (iter.next()) |entry| {
allocator.free(entry.key_ptr.*);
entry.value_ptr.deinit(self.allocator);
}
self.map.deinit(self.allocator);
if (self.parent) |p| p.deinit();
}
pub fn get(self: *const Context, key: []const u8) ?Value {
return self.map.get(key) or (self.parent orelse return null).get(key);
}
pub fn set(self: *Context, key: []const u8, value: Value) !void {
const duped_key = try self.allocator.dupe(u8, key);
try self.map.put(self.allocator, duped_key, value);
}
pub fn pushScope(self: *Context) *Context {
var child = Context.init(self.allocator);
child.parent = self;
return &child;
}
pub fn popScope(self: *Context) void {
self.deinit(); // libera o escopo atual
}
};

386
src/old/renderer.zig Normal file
View file

@ -0,0 +1,386 @@
const std = @import("std");
const builtin_filters = @import("filters.zig").builtin_filters;
const Context = @import("context.zig").Context;
const FilterError = @import("filters.zig").FilterError;
const Value = @import("context.zig").Value;
pub const RenderError = FilterError || error{
OutOfMemory,
InvalidTemplate,
BlockNotFound,
CircularExtends,
FileNotFound,
AccessDenied,
FileTooBig,
NoSpaceLeft,
Unexpected,
};
const TemplateBlock = struct {
name: []const u8,
content: []const u8,
};
const TemplateParser = struct {
template: []const u8,
pos: usize = 0,
last_position: usize = 0,
const Block = struct {
kind: enum { text, variable, tag },
content: []const u8,
};
fn init(template: []const u8) TemplateParser {
return .{ .template = template };
}
fn nextBlock(self: *TemplateParser) ?Block {
if (self.pos >= self.template.len) return null;
const start = self.pos;
if (std.mem.startsWith(u8, self.template[self.pos..], "{{")) {
self.pos += 2;
const end = std.mem.indexOfPos(u8, self.template, self.pos, "}}") orelse self.template.len;
self.pos = end + 2;
return .{ .kind = .variable, .content = self.template[start + 2 .. end] };
} else if (std.mem.startsWith(u8, self.template[self.pos..], "{%")) {
self.pos += 2;
const end = std.mem.indexOfPos(u8, self.template, self.pos, "%}") orelse self.template.len;
self.pos = end + 2;
return .{ .kind = .tag, .content = self.template[start + 2 .. end] };
} else {
const end = std.mem.indexOfAnyPos(u8, self.template, self.pos, "{%") orelse self.template.len;
self.pos = end;
return .{ .kind = .text, .content = self.template[start..end] };
}
}
fn skipTo(self: *TemplateParser, tag: []const u8) void {
while (self.nextBlock()) |block| {
if (block.kind == .tag) {
const trimmed = std.mem.trim(u8, block.content, " \t\r\n");
if (std.mem.eql(u8, trimmed, tag)) {
break;
}
}
}
}
fn reset(self: *TemplateParser) void {
self.pos = 0;
}
fn resetToLastPosition(self: *TemplateParser) void {
self.pos = self.last_position;
}
};
pub const Renderer = struct {
context: *Context,
blocks: std.ArrayList(TemplateBlock),
allocator: std.mem.Allocator,
pub fn init(context: *Context) Renderer {
return .{
.context = context,
.blocks = std.ArrayList(TemplateBlock){},
.allocator = context.allocator(),
};
}
pub fn deinit(self: *Renderer) void {
for (self.blocks.items) |block| {
self.allocator.free(block.name);
}
self.blocks.deinit();
}
pub fn render(self: *Renderer, template_path: []const u8, writer: anytype) RenderError!void {
const max_size = 10 * 1024 * 1024; // 10MB
const template = std.fs.cwd().readFileAlloc(self.allocator, template_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,
else => return RenderError.Unexpected,
};
defer self.allocator.free(template);
try self.renderTemplate(template, writer);
}
pub fn renderString(self: *Renderer, template: []const u8, writer: anytype) RenderError!void {
try self.renderTemplate(template, writer);
}
fn renderTemplate(self: *Renderer, template: []const u8, writer: anytype) RenderError!void {
var parser = TemplateParser.init(template);
var extends_path: ?[]const u8 = null;
while (parser.nextBlock()) |block| {
const trimmed = std.mem.trim(u8, block.content, " \t\r\n");
if (block.kind == .tag) {
if (std.mem.startsWith(u8, trimmed, "extends ")) {
const quoted = trimmed["extends ".len..];
extends_path = std.mem.trim(u8, quoted, " \"'");
} else if (std.mem.startsWith(u8, trimmed, "block ")) {
const block_name = std.mem.trim(u8, trimmed["block ".len..], " \t\r\n");
const name_copy = try self.allocator.dupe(u8, block_name);
// Agora coletamos o conteúdo real do block
// Começa **após** o fechamento da tag de abertura
const block_start = parser.pos; // está após o %}
// Avança até o endblock
parser.skipTo("endblock");
// O conteúdo vai do início do corpo até o início do endblock
const endblock_start = parser.pos;
const block_content = template[block_start..endblock_start];
try self.blocks.append(self.allocator, .{ .name = name_copy, .content = block_content });
}
}
}
if (extends_path) |path| {
const max_size = 10 * 1024 * 1024;
const base_template = 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,
else => return RenderError.Unexpected,
};
defer self.allocator.free(base_template);
var base_parser = TemplateParser.init(base_template);
try self.renderWithInheritance(&base_parser, writer);
} else {
parser.reset();
try self.renderBlocks(&parser, writer);
}
}
fn renderWithInheritance(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void {
while (parser.nextBlock()) |block| {
if (block.kind == .tag) {
const trimmed = std.mem.trim(u8, block.content, " \t\r\n");
if (std.mem.startsWith(u8, trimmed, "block ")) {
const block_name = std.mem.trim(u8, trimmed["block ".len..], " \t\r\n");
if (self.findBlock(block_name)) |child| {
try writer.writeAll(child.content);
parser.skipTo("endblock");
} else {
try self.renderBlockContent(parser, writer);
}
} else {
try self.renderTag(block.content, parser, writer);
}
} else {
switch (block.kind) {
.text => try writer.writeAll(block.content),
.variable => try self.renderExpression(block.content, writer),
.tag => unreachable,
}
}
}
}
fn findBlock(self: *Renderer, name: []const u8) ?TemplateBlock {
for (self.blocks.items) |b| {
if (std.mem.eql(u8, b.name, name)) return b;
}
return null;
}
fn renderBlockContent(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void {
while (parser.nextBlock()) |block| {
if (block.kind == .tag and std.mem.eql(u8, std.mem.trim(u8, block.content, " \t\r\n"), "endblock")) {
break;
}
switch (block.kind) {
.text => try writer.writeAll(block.content),
.variable => try self.renderExpression(block.content, writer),
.tag => try self.renderTag(block.content, parser, writer),
}
}
}
fn renderBlocks(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void {
while (parser.nextBlock()) |block| {
switch (block.kind) {
.text => try writer.writeAll(block.content),
.variable => try self.renderExpression(block.content, writer),
.tag => try self.renderTag(block.content, parser, writer),
}
}
}
/// Renderiza uma expressão: var ou var|filtro|...
fn renderExpression(self: *Renderer, expr: []const u8, writer: anytype) !void {
var parts = std.mem.splitScalar(u8, expr, '|');
var var_name = parts.next() orelse return;
var_name = std.mem.trim(u8, var_name, " \t\r\n");
var value = self.context.get(var_name) orelse Value.null;
var is_safe = false;
while (parts.next()) |part| {
const trimmed = std.mem.trim(u8, part, " \t\r\n");
if (trimmed.len == 0) continue;
const colon_pos = std.mem.indexOf(u8, trimmed, ":");
const filter_name = if (colon_pos) |c| trimmed[0..c] else trimmed;
const arg_str = if (colon_pos) |c| std.mem.trim(u8, trimmed[c + 1 ..], " \t\r\n") else null;
if (std.mem.eql(u8, filter_name, "safe")) {
is_safe = true;
continue;
}
const filter_fn = builtin_filters.get(filter_name) orelse continue;
const arg = if (arg_str) |a| blk: {
if (std.mem.eql(u8, a, "null")) break :blk Value.null;
if (std.fmt.parseInt(i64, a, 10)) |num| break :blk Value{ .int = num } else |_| {}
break :blk Value{ .string = a };
} else null;
value = try filter_fn(self.allocator, value, arg);
}
// Autoescape: escapa se não for safe
const str = if (is_safe) blk: {
break :blk try self.valueToString(value);
} else blk: {
const escaped = try self.escapeHtml(value);
break :blk try self.valueToString(escaped);
};
try writer.writeAll(str);
}
fn renderTag(self: *Renderer, tag_content: []const u8, parser: *TemplateParser, writer: anytype) !void {
const trimmed = std.mem.trim(u8, tag_content, " \t\r\n");
if (std.mem.startsWith(u8, trimmed, "if ")) {
try self.renderIf(trimmed["if ".len..], parser, writer);
} else if (std.mem.startsWith(u8, trimmed, "for ")) {
try self.renderFor(trimmed["for ".len..], parser, writer);
} else if (std.mem.eql(u8, trimmed, "endif") or std.mem.eql(u8, trimmed, "endfor")) {
// Nada a fazer o bloco foi consumido no if/for
} else {
// Tag desconhecida ignora
}
}
fn renderIf(self: *Renderer, condition_expr: []const u8, parser: *TemplateParser, writer: anytype) !void {
const condition = try self.evaluateCondition(condition_expr);
if (condition) {
try self.renderBlocksUntilEndif(parser, writer);
} else {
parser.skipTo("endif"); // sem try retorna void
}
}
fn renderFor(self: *Renderer, for_expr: []const u8, parser: *TemplateParser, writer: anytype) !void {
var parts = std.mem.splitSequence(u8, for_expr, " in ");
const item_name = std.mem.trim(u8, parts.next() orelse return, " \t\r\n");
const list_expr = std.mem.trim(u8, parts.next() orelse return, " \t\r\n");
const list_value = self.context.get(list_expr) orelse Value.null;
const list = switch (list_value) {
.list => |l| l,
else => return,
};
for (list) |item| {
try self.context.set(item_name, item);
try self.renderBlocksUntilEndfor(parser, writer);
parser.resetToLastPosition();
}
}
fn evaluateCondition(self: *Renderer, expr: []const u8) !bool {
const value = self.context.get(expr) orelse Value.null;
return switch (value) {
.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,
.null => false,
};
}
fn renderBlocksUntilEndif(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void {
while (parser.nextBlock()) |block| {
if (block.kind == .tag and std.mem.eql(u8, std.mem.trim(u8, block.content, " \t\r\n"), "endif")) {
break;
}
switch (block.kind) {
.text => try writer.writeAll(block.content),
.variable => try self.renderExpression(block.content, writer),
.tag => try self.renderTag(block.content, parser, writer),
}
}
}
fn renderBlocksUntilEndfor(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void {
const start_pos = parser.pos;
while (parser.nextBlock()) |block| {
if (block.kind == .tag and std.mem.eql(u8, std.mem.trim(u8, block.content, " \t\r\n"), "endfor")) {
parser.last_position = start_pos;
break;
}
switch (block.kind) {
.text => try writer.writeAll(block.content),
.variable => try self.renderExpression(block.content, writer),
.tag => try self.renderTag(block.content, parser, writer),
}
}
}
/// Escapa HTML manualmente (sem depender de filtro externo)
fn escapeHtml(self: *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, "&lt;"),
'>' => try result.appendSlice(self.allocator, "&gt;"),
'"' => try result.appendSlice(self.allocator, "&quot;"),
'\'' => try result.appendSlice(self.allocator, "&#x27;"),
else => try result.append(self.allocator, c),
}
}
return Value{ .string = try result.toOwnedSlice(self.allocator) };
}
/// Converte Value para string de forma segura
fn valueToString(self: *Renderer, value: Value) ![]const u8 {
return switch (value) {
.string => |s| s,
.int => |i| try std.fmt.allocPrint(self.allocator, "{d}", .{i}),
.float => |f| try std.fmt.allocPrint(self.allocator, "{d}", .{f}),
.bool => |b| if (b) "true" else "false",
.null => "",
else => "[object]",
};
}
};

395
src/old/renderer_bkp.zig Normal file
View file

@ -0,0 +1,395 @@
const std = @import("std");
const builtin_filters = @import("filters.zig").builtin_filters;
const Context = @import("context.zig").Context;
const FilterError = @import("filters.zig").FilterError;
const Value = @import("context.zig").Value;
pub const RenderError = FilterError || error{
OutOfMemory,
InvalidTemplate,
BlockNotFound,
CircularExtends,
FileNotFound,
AccessDenied,
FileTooBig,
NoSpaceLeft,
Unexpected,
};
const TemplateBlock = struct {
name: []const u8,
content: []const u8,
};
const TemplateParser = struct {
template: []const u8,
pos: usize = 0,
last_position: usize = 0,
const Block = struct {
kind: enum { text, variable, tag },
content: []const u8,
};
fn init(template: []const u8) TemplateParser {
return .{ .template = template };
}
fn nextBlock(self: *TemplateParser) ?Block {
if (self.pos >= self.template.len) return null;
const start = self.pos;
if (std.mem.startsWith(u8, self.template[self.pos..], "{{")) {
self.pos += 2;
const end = std.mem.indexOfPos(u8, self.template, self.pos, "}}") orelse self.template.len;
self.pos = end + 2;
return .{ .kind = .variable, .content = self.template[start + 2 .. end] };
} else if (std.mem.startsWith(u8, self.template[self.pos..], "{%")) {
self.pos += 2;
const end = std.mem.indexOfPos(u8, self.template, self.pos, "%}") orelse self.template.len;
self.pos = end + 2;
return .{ .kind = .tag, .content = self.template[start + 2 .. end] };
} else {
const end = std.mem.indexOfAnyPos(u8, self.template, self.pos, "{%") orelse self.template.len;
self.pos = end;
return .{ .kind = .text, .content = self.template[start..end] };
}
}
fn skipTo(self: *TemplateParser, tag: []const u8) void {
while (self.nextBlock()) |block| {
if (block.kind == .tag) {
const trimmed = std.mem.trim(u8, block.content, " \t\r\n");
if (std.mem.eql(u8, trimmed, tag)) {
break;
}
}
}
}
fn reset(self: *TemplateParser) void {
self.pos = 0;
}
fn resetToLastPosition(self: *TemplateParser) void {
self.pos = self.last_position;
}
};
pub const Renderer = struct {
context: *Context,
blocks: std.ArrayList(TemplateBlock),
pub fn init(context: *Context) Renderer {
return .{
.context = context,
.blocks = std.ArrayList(TemplateBlock){},
};
}
pub fn deinit(self: *Renderer) void {
for (self.blocks.items) |block| {
self.context.allocator().free(block.name);
}
self.blocks.deinit();
}
pub fn render(self: *Renderer, template_path: []const u8, writer: anytype) RenderError!void {
const max_size = 10 * 1024 * 1024; // 10MB
const template = std.fs.cwd().readFileAlloc(self.context.allocator(), template_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,
else => return RenderError.Unexpected,
};
defer self.context.allocator().free(template);
try self.renderTemplate(template, writer);
}
pub fn renderString(self: *Renderer, template: []const u8, writer: anytype) RenderError!void {
try self.renderTemplate(template, writer);
}
fn renderTemplate(self: *Renderer, template: []const u8, writer: anytype) RenderError!void {
var parser = TemplateParser.init(template);
var extends_path: ?[]const u8 = null;
while (parser.nextBlock()) |block| {
const trimmed = std.mem.trim(u8, block.content, " \t\r\n");
if (block.kind == .tag) {
if (std.mem.startsWith(u8, trimmed, "extends ")) {
const quoted = trimmed["extends ".len..];
extends_path = std.mem.trim(u8, quoted, " \"'");
} else if (std.mem.startsWith(u8, trimmed, "block ")) {
const block_name = std.mem.trim(u8, trimmed["block ".len..], " \t\r\n");
const name_copy = try self.context.allocator().dupe(u8, block_name);
// Agora extrai o conteúdo real do block (após o nome até endblock)
const block_start = parser.pos; // está após o %}
parser.skipTo("endblock");
const endblock_start = parser.pos; // início do {% endblock %}
const block_content = template[block_start..endblock_start];
try self.blocks.append(self.context.allocator(), .{ .name = name_copy, .content = block_content });
}
} else if (std.mem.startsWith(u8, trimmed, "block ")) {
const block_name = std.mem.trim(u8, trimmed["block ".len..], " \t\r\n");
const name_copy = try self.context.allocator().dupe(u8, block_name);
// Agora coletamos o conteúdo real do block
// Começa **após** o fechamento da tag de abertura
const block_start = parser.pos; // está após o %}
// Avança até o endblock
parser.skipTo("endblock");
// O conteúdo vai do início do corpo até o início do endblock
const endblock_start = parser.pos;
const block_content = template[block_start..endblock_start];
try self.blocks.append(self.context.allocator(), .{ .name = name_copy, .content = block_content });
}
}
if (extends_path) |path| {
const max_size = 10 * 1024 * 1024;
const base_template = std.fs.cwd().readFileAlloc(self.context.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,
else => return RenderError.Unexpected,
};
defer self.context.allocator().free(base_template);
var base_parser = TemplateParser.init(base_template);
try self.renderWithInheritance(&base_parser, writer);
} else {
parser.reset();
try self.renderBlocks(&parser, writer);
}
}
fn renderWithInheritance(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void {
while (parser.nextBlock()) |block| {
if (block.kind == .tag) {
const trimmed = std.mem.trim(u8, block.content, " \t\r\n");
if (std.mem.startsWith(u8, trimmed, "block ")) {
const block_name = std.mem.trim(u8, trimmed["block ".len..], " \t\r\n");
if (self.findBlock(block_name)) |child| {
try writer.writeAll(child.content);
parser.skipTo("endblock");
} else {
try self.renderBlockContent(parser, writer);
}
} else {
try self.renderTag(block.content, parser, writer);
}
} else {
switch (block.kind) {
.text => try writer.writeAll(block.content),
.variable => try self.renderExpression(block.content, writer),
.tag => unreachable,
}
}
}
}
fn findBlock(self: *Renderer, name: []const u8) ?TemplateBlock {
for (self.blocks.items) |b| {
if (std.mem.eql(u8, b.name, name)) return b;
}
return null;
}
fn renderBlockContent(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void {
while (parser.nextBlock()) |block| {
if (block.kind == .tag and std.mem.eql(u8, std.mem.trim(u8, block.content, " \t\r\n"), "endblock")) {
break;
}
switch (block.kind) {
.text => try writer.writeAll(block.content),
.variable => try self.renderExpression(block.content, writer),
.tag => try self.renderTag(block.content, parser, writer),
}
}
}
fn renderBlocks(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void {
while (parser.nextBlock()) |block| {
switch (block.kind) {
.text => try writer.writeAll(block.content),
.variable => try self.renderExpression(block.content, writer),
.tag => try self.renderTag(block.content, parser, writer),
}
}
}
/// Renderiza uma expressão: var ou var|filtro|...
fn renderExpression(self: *Renderer, expr: []const u8, writer: anytype) !void {
var parts = std.mem.splitScalar(u8, expr, '|');
var var_name = parts.next() orelse return;
var_name = std.mem.trim(u8, var_name, " \t\r\n");
var value = self.context.get(var_name) orelse Value.null;
var is_safe = false;
while (parts.next()) |part| {
const trimmed = std.mem.trim(u8, part, " \t\r\n");
if (trimmed.len == 0) continue;
const colon_pos = std.mem.indexOf(u8, trimmed, ":");
const filter_name = if (colon_pos) |c| trimmed[0..c] else trimmed;
const arg_str = if (colon_pos) |c| std.mem.trim(u8, trimmed[c + 1 ..], " \t\r\n") else null;
if (std.mem.eql(u8, filter_name, "safe")) {
is_safe = true;
continue;
}
const filter_fn = builtin_filters.get(filter_name) orelse continue;
const arg = if (arg_str) |a| blk: {
if (std.mem.eql(u8, a, "null")) break :blk Value.null;
if (std.fmt.parseInt(i64, a, 10)) |num| break :blk Value{ .int = num } else |_| {}
break :blk Value{ .string = a };
} else null;
value = try filter_fn(self.context.allocator(), value, arg);
}
// Autoescape: escapa se não for safe
const str = if (is_safe) blk: {
break :blk try self.valueToString(value);
} else blk: {
const escaped = try self.escapeHtml(value);
break :blk try self.valueToString(escaped);
};
try writer.writeAll(str);
}
fn renderTag(self: *Renderer, tag_content: []const u8, parser: *TemplateParser, writer: anytype) !void {
const trimmed = std.mem.trim(u8, tag_content, " \t\r\n");
if (std.mem.startsWith(u8, trimmed, "if ")) {
try self.renderIf(trimmed["if ".len..], parser, writer);
} else if (std.mem.startsWith(u8, trimmed, "for ")) {
try self.renderFor(trimmed["for ".len..], parser, writer);
} else if (std.mem.eql(u8, trimmed, "endif") or std.mem.eql(u8, trimmed, "endfor")) {
// Nada a fazer o bloco foi consumido no if/for
} else {
// Tag desconhecida ignora
}
}
fn renderIf(self: *Renderer, condition_expr: []const u8, parser: *TemplateParser, writer: anytype) !void {
const condition = try self.evaluateCondition(condition_expr);
if (condition) {
try self.renderBlocksUntilEndif(parser, writer);
} else {
parser.skipTo("endif"); // sem try retorna void
}
}
fn renderFor(self: *Renderer, for_expr: []const u8, parser: *TemplateParser, writer: anytype) !void {
var parts = std.mem.splitSequence(u8, for_expr, " in ");
const item_name = std.mem.trim(u8, parts.next() orelse return, " \t\r\n");
const list_expr = std.mem.trim(u8, parts.next() orelse return, " \t\r\n");
const list_value = self.context.get(list_expr) orelse Value.null;
const list = switch (list_value) {
.list => |l| l,
else => return,
};
for (list) |item| {
try self.context.set(item_name, item);
try self.renderBlocksUntilEndfor(parser, writer);
parser.resetToLastPosition();
}
}
fn evaluateCondition(self: *Renderer, expr: []const u8) !bool {
const value = self.context.get(expr) orelse Value.null;
return switch (value) {
.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,
.null => false,
};
}
fn renderBlocksUntilEndif(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void {
while (parser.nextBlock()) |block| {
if (block.kind == .tag and std.mem.eql(u8, std.mem.trim(u8, block.content, " \t\r\n"), "endif")) {
break;
}
switch (block.kind) {
.text => try writer.writeAll(block.content),
.variable => try self.renderExpression(block.content, writer),
.tag => try self.renderTag(block.content, parser, writer),
}
}
}
fn renderBlocksUntilEndfor(self: *Renderer, parser: *TemplateParser, writer: anytype) RenderError!void {
const start_pos = parser.pos;
while (parser.nextBlock()) |block| {
if (block.kind == .tag and std.mem.eql(u8, std.mem.trim(u8, block.content, " \t\r\n"), "endfor")) {
parser.last_position = start_pos;
break;
}
switch (block.kind) {
.text => try writer.writeAll(block.content),
.variable => try self.renderExpression(block.content, writer),
.tag => try self.renderTag(block.content, parser, writer),
}
}
}
/// Escapa HTML manualmente (sem depender de filtro externo)
fn escapeHtml(self: *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.context.allocator(), "&amp;"),
'<' => try result.appendSlice(self.context.allocator(), "&lt;"),
'>' => try result.appendSlice(self.context.allocator(), "&gt;"),
'"' => try result.appendSlice(self.context.allocator(), "&quot;"),
'\'' => try result.appendSlice(self.context.allocator(), "&#x27;"),
else => try result.append(self.context.allocator(), c),
}
}
return Value{ .string = try result.toOwnedSlice(self.context.allocator()) };
}
/// Converte Value para string de forma segura
fn valueToString(self: *Renderer, value: Value) ![]const u8 {
return switch (value) {
.string => |s| s,
.int => |i| try std.fmt.allocPrint(self.context.allocator(), "{d}", .{i}),
.float => |f| try std.fmt.allocPrint(self.context.allocator(), "{d}", .{f}),
.bool => |b| if (b) "true" else "false",
.null => "",
else => "[object]",
};
}
};

156
src/old/renderer_test.zig Normal file
View file

@ -0,0 +1,156 @@
const std = @import("std");
const testing = std.testing;
const Context = @import("context.zig").Context;
const Renderer = @import("renderer.zig").Renderer;
const Value = @import("context.zig").Value;
test "renderer - texto literal e variável simples" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("nome", "Fulano");
try ctx.set("idade", 30);
var renderer = Renderer.init(&ctx);
const template = "Olá {{ nome }}! Você tem {{ idade }} anos.";
var buffer = std.ArrayList(u8){};
defer buffer.deinit(alloc);
try renderer.renderString(template, buffer.writer(alloc));
try testing.expectEqualStrings("Olá Fulano! Você tem 30 anos.", buffer.items);
}
test "renderer - filtros com argumentos" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("titulo", "Olá Mundo Legal");
var renderer = Renderer.init(&ctx);
const template = "{{ titulo|lower|slugify }}";
var buffer = std.ArrayList(u8){};
defer buffer.deinit(alloc);
try renderer.renderString(template, buffer.writer(alloc));
try testing.expectEqualStrings("ola-mundo-legal", buffer.items);
}
test "renderer - literal, variável e autoescape" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("nome", "Lucas");
try ctx.set("xss", "<script>alert('xss')</script>");
try ctx.set("html", "<b>negrito</b>");
var renderer = Renderer.init(&ctx);
const template = "Olá {{ nome }}! Perigo: {{ xss }} Seguro: {{ html|safe }}";
var buffer = std.ArrayList(u8){};
defer buffer.deinit(alloc);
try renderer.renderString(template, buffer.writer(alloc));
const output = buffer.items;
try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas!") != null);
try testing.expect(std.mem.indexOf(u8, output, "&lt;script&gt;") != null); // escapado
try testing.expect(std.mem.indexOf(u8, output, "<b>negrito</b>") != null); // safe
}
test "renderer - if and for" {
const alloc = testing.allocator;
var ctx = Context.init(alloc);
defer ctx.deinit();
try ctx.set("ativo", true);
try ctx.set("inativo", false);
try ctx.set("nomes", [_]Value{ Value{ .string = "Ana" }, Value{ .string = "Bia" }, Value{ .string = "Cris" } });
var renderer = Renderer.init(&ctx);
const template =
\\{% if ativo %}Sim!{% endif %}
\\{% if inativo %}Não{% endif %}
\\Lista:
\\{% for nome in nomes %}
\\- {{ nome }}
\\{% endfor %}
;
var buffer = std.ArrayList(u8){};
defer buffer.deinit(alloc);
try renderer.renderString(template, buffer.writer(alloc));
const output = buffer.items;
try testing.expect(std.mem.indexOf(u8, output, "Sim!") != null);
try testing.expect(std.mem.indexOf(u8, output, "Não") == null);
try testing.expect(std.mem.indexOf(u8, output, "- Ana") != null);
try testing.expect(std.mem.indexOf(u8, output, "- Bia") != null);
try testing.expect(std.mem.indexOf(u8, output, "- Cris") != null);
}
// test "renderer - block and extends" {
// const alloc = testing.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 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 {};
//
// var ctx = Context.init(alloc);
// defer ctx.deinit();
//
// try ctx.set("nome", "Lucas");
//
// var renderer = Renderer.init(&ctx);
//
// var buffer = std.ArrayList(u8){};
// defer buffer.deinit(ctx.allocator());
//
// try renderer.render("child.html", buffer.writer(ctx.allocator()));
//
// const output = buffer.items;
//
// std.debug.print("{s}", .{output});
//
// try testing.expect(std.mem.indexOf(u8, output, "Meu Título") != null);
// try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas!") != null);
// try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") == null);
// try testing.expect(std.mem.indexOf(u8, output, "Título Padrão") == null);
// }

1208
src/parser_bkp.zig Normal file

File diff suppressed because it is too large Load diff

View file

@ -205,10 +205,12 @@ pub const Renderer = struct {
if (condition) { if (condition) {
for (node.@"if".?.true_body) |child| { for (node.@"if".?.true_body) |child| {
std.debug.print("caí no true\n", .{});
try self.renderNode(alloc, nodes, child, writer, null, null); try self.renderNode(alloc, nodes, child, writer, null, null);
} }
} else { } else {
for (node.@"if".?.false_body) |child| { for (node.@"if".?.false_body) |child| {
std.debug.print("caí no false\n", .{});
try self.renderNode(alloc, nodes, child, writer, null, null); try self.renderNode(alloc, nodes, child, writer, null, null);
} }
} }

275
src/renderer_bkp.zig Normal file
View file

@ -0,0 +1,275 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const ArrayListUnmanaged = std.ArrayListUnmanaged;
const Context = @import("context.zig").Context;
const Value = @import("context.zig").Value;
const builtin_filters = @import("filters.zig").builtin_filters;
const FilterError = @import("filters.zig").FilterError;
const parser = @import("parser.zig");
pub const RenderError = FilterError || error{
OutOfMemory,
InvalidSyntax,
UnknownVariable,
UnknownFilter,
InvalidTemplate,
BlockNotFound,
CircularExtends,
FileNotFound,
AccessDenied,
FileTooBig,
NoSpaceLeft,
Unexpected,
UnclosedTag,
InvalidAutoescapeArgument,
UnclosedVariable,
UnclosedBlock,
UnclosedComment,
InvalidAssignmentSyntax,
UnclosedQuoteInAssignment,
InvalidForSyntax,
UnclosedVerbatim,
InvalidUrlSyntax,
UnclosedQuoteInUrl,
InvalidDebugArgs,
InvalidRegroupSyntax,
InvalidWidthRatioSyntax,
InvalidTemplateTag,
InvalidCsrfTokenArgs,
};
pub const Renderer = struct {
context: *Context,
allocator: Allocator,
pub fn init(context: *Context) Renderer {
return .{
.context = context,
.allocator = context.allocator(),
};
}
fn checkForExtends(self: *const Renderer, nodes: []parser.Node) bool {
_ = self;
for (nodes) |n| {
if (n.type == .extends) return true;
}
return false;
}
fn readTemplateFile(self: *const Renderer, path: []const u8) RenderError![]const u8 {
const max_size = 10 * 1024 * 1024;
const base_template = 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,
else => return RenderError.Unexpected,
};
return base_template;
}
pub fn render(self: *const Renderer, template: []const u8, writer: anytype) RenderError!void {
const base_template = try self.readTemplateFile(template);
return self.renderString(base_template, writer);
}
pub fn renderString_bkp(self: *const Renderer, template: []const u8, writer: anytype) RenderError!void {
var arena = std.heap.ArenaAllocator.init(self.allocator);
defer arena.deinit();
const alloc = arena.allocator();
var p = parser.Parser.init(template);
const nodes = try p.parse(alloc);
defer {
for (nodes) |node| node.deinit(alloc);
alloc.free(nodes);
}
for (nodes) |node| {
try self.renderNode(alloc, nodes, node, writer, null);
}
}
pub fn renderString(self: *const Renderer, template: []const u8, writer: anytype) RenderError!void {
var arena = std.heap.ArenaAllocator.init(self.allocator);
defer arena.deinit();
const alloc = arena.allocator();
var p = parser.Parser.init(template);
const nodes = try p.parse(alloc);
defer {
for (nodes) |node| node.deinit(alloc);
alloc.free(nodes);
}
const has_extends = self.checkForExtends(nodes);
if (has_extends) {
for (nodes) |node| {
if (node.type == .extends) {
const base_template = try self.readTemplateFile(node.extends.?.parent_name);
defer self.allocator.free(base_template);
var base_parser = parser.Parser.init(base_template);
const base_nodes = try base_parser.parse(alloc);
defer {
for (base_nodes) |n| n.deinit(alloc);
alloc.free(base_nodes);
}
try self.renderWithInheritance(alloc, base_nodes, nodes, writer);
return;
}
}
} else {
for (nodes) |node| {
try self.renderNode(alloc, nodes, node, writer, null);
}
}
}
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;
// Procura no filho
const child_block = self.findChildBlock(child_nodes, block_name);
if (child_block) |child| {
// Renderiza o filho
for (child.body) |child_node| {
try self.renderNode(alloc, child_nodes, child_node, writer, null);
}
} else {
// Renderiza o do pai
for (base_node.block.?.body) |child| {
try self.renderNode(alloc, child_nodes, child, writer, null);
}
}
} else {
// Qualquer outra coisa no base
try self.renderNode(alloc, child_nodes, base_node, writer, null);
}
}
}
fn renderNode(self: *const Renderer, alloc: Allocator, nodes: []parser.Node, node: parser.Node, writer: anytype, context: ?*Context) RenderError!void {
// std.debug.print("Vou renderizar o node: {any}\n", .{node});
switch (node.type) {
.text => try writer.writeAll(node.text.?.content),
.variable => {
const var_name = node.variable.?.expr;
var value: Value = Value.null;
if (context != null) {
value = context.?.get(var_name) orelse Value.null;
} else {
value = self.context.get(var_name) orelse Value.null;
}
var is_safe = false;
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;
value = try filter_fn(alloc, value, arg);
if (std.mem.eql(u8, f.name, "safe")) is_safe = true;
}
if (!is_safe) {
if (builtin_filters.get("escape")) |escape_fn| {
value = try escape_fn(alloc, value, null);
}
}
var buf = ArrayListUnmanaged(u8){};
defer buf.deinit(alloc);
try self.valueToString(alloc, &buf, value);
try writer.writeAll(buf.items);
},
.if_block => {
const condition = self.evaluateCondition(node.@"if".?.condition) catch return false;
if (condition) {
for (node.@"if".?.true_body) |child| {
try self.renderNode(alloc, nodes, child, writer, null);
}
} else {
for (node.@"if".?.false_body) |child| {
try self.renderNode(alloc, nodes, child, writer, 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);
}
for (node.@"for".?.empty_body) |child| {
try self.renderNode(alloc, nodes, child, writer, &ctx);
}
}
},
.block => {
for (node.block.?.body) |child| {
try self.renderNode(alloc, nodes, child, writer, null);
}
},
else => {},
}
}
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.?;
}
return null;
}
fn valueToString(self: *const Renderer, alloc: Allocator, buf: *ArrayListUnmanaged(u8), value: Value) !void {
_ = self;
var w = buf.writer(alloc);
switch (value) {
.null => try w.writeAll("null"),
.bool => |b| try w.print("{}", .{b}),
.int => |n| try w.print("{d}", .{n}),
.float => |f| try w.print("{d}", .{f}),
.string => |s| try w.writeAll(s),
.list => try w.writeAll("[list]"),
.dict => try w.writeAll("{dict}"),
}
}
fn evaluateCondition(self: *const Renderer, expr: []const u8) !bool {
const value = self.context.get(expr) orelse Value.null;
return switch (value) {
.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,
.null => false,
};
}
};

View file

@ -8,390 +8,390 @@ const Context = @import("context.zig").Context;
const Value = @import("context.zig").Value; const Value = @import("context.zig").Value;
const TemplateCache = @import("cache.zig").TemplateCache; const TemplateCache = @import("cache.zig").TemplateCache;
test "renderer: literal + variável simples" { // test "renderer: literal + variável simples" {
const alloc = testing.allocator; // const alloc = testing.allocator;
var ctx = Context.init(alloc); // var ctx = Context.init(alloc);
defer ctx.deinit(); // defer ctx.deinit();
//
var cache = TemplateCache.init(alloc); // var cache = TemplateCache.init(alloc);
defer cache.deinit(); // defer cache.deinit();
//
const renderer = Renderer.init(&ctx, &cache); // const renderer = Renderer.init(&ctx, &cache);
//
try ctx.set("nome", Value{ .string = "Mariana" }); // try ctx.set("nome", Value{ .string = "Mariana" });
//
var buf = std.ArrayList(u8){}; // var buf = std.ArrayList(u8){};
defer buf.deinit(alloc); // defer buf.deinit(alloc);
//
const template = // const template =
\\Olá, {{ nome }}! Bem-vinda. // \\Olá, {{ nome }}! Bem-vinda.
; // ;
//
try renderer.renderString(template, buf.writer(alloc)); // try renderer.renderString(template, buf.writer(alloc));
//
try testing.expectEqualStrings("Olá, Mariana! Bem-vinda.", buf.items); // try testing.expectEqualStrings("Olá, Mariana! Bem-vinda.", buf.items);
} // }
//
test "renderer: filtros + autoescape" { // test "renderer: filtros + autoescape" {
const alloc = testing.allocator; // const alloc = testing.allocator;
var ctx = Context.init(alloc); // var ctx = Context.init(alloc);
defer ctx.deinit(); // defer ctx.deinit();
//
var cache = TemplateCache.init(alloc); // var cache = TemplateCache.init(alloc);
defer cache.deinit(); // defer cache.deinit();
//
const renderer = Renderer.init(&ctx, &cache); // const renderer = Renderer.init(&ctx, &cache);
//
try ctx.set("html", Value{ .string = "<script>alert()</script>" }); // try ctx.set("html", Value{ .string = "<script>alert()</script>" });
try ctx.set("texto", Value{ .string = "maiusculo e slug" }); // try ctx.set("texto", Value{ .string = "maiusculo e slug" });
//
var buf = std.ArrayList(u8){}; // var buf = std.ArrayList(u8){};
defer buf.deinit(alloc); // defer buf.deinit(alloc);
//
const template = // const template =
\\Normal: {{ html }} // \\Normal: {{ html }}
\\Safe: {{ html|safe }} // \\Safe: {{ html|safe }}
\\Filtrado: {{ texto|upper|slugify }} // \\Filtrado: {{ texto|upper|slugify }}
; // ;
//
try renderer.renderString(template, buf.writer(alloc)); // try renderer.renderString(template, buf.writer(alloc));
//
const expected = // const expected =
\\Normal: &lt;script&gt;alert()&lt;/script&gt; // \\Normal: &lt;script&gt;alert()&lt;/script&gt;
\\Safe: <script>alert()</script> // \\Safe: <script>alert()</script>
\\Filtrado: maiusculo-e-slug // \\Filtrado: maiusculo-e-slug
; // ;
//
try testing.expectEqualStrings(expected, buf.items); // try testing.expectEqualStrings(expected, buf.items);
} // }
//
test "literal simples" { // test "literal simples" {
const alloc = testing.allocator; // const alloc = testing.allocator;
var ctx = Context.init(alloc); // var ctx = Context.init(alloc);
defer ctx.deinit(); // defer ctx.deinit();
//
var cache = TemplateCache.init(alloc); // var cache = TemplateCache.init(alloc);
defer cache.deinit(); // defer cache.deinit();
//
const renderer = Renderer.init(&ctx, &cache); // const renderer = Renderer.init(&ctx, &cache);
//
var buf = std.ArrayList(u8){}; // var buf = std.ArrayList(u8){};
defer buf.deinit(alloc); // defer buf.deinit(alloc);
//
const template = "Texto literal com acentos: Olá, mundo!"; // const template = "Texto literal com acentos: Olá, mundo!";
//
try renderer.renderString(template, buf.writer(alloc)); // try renderer.renderString(template, buf.writer(alloc));
//
try testing.expectEqualStrings("Texto literal com acentos: Olá, mundo!", buf.items); // try testing.expectEqualStrings("Texto literal com acentos: Olá, mundo!", buf.items);
} // }
//
test "variável com filtro encadeado e autoescape" { // test "variável com filtro encadeado e autoescape" {
const alloc = testing.allocator; // const alloc = testing.allocator;
var ctx = Context.init(alloc); // var ctx = Context.init(alloc);
defer ctx.deinit(); // defer ctx.deinit();
//
var cache = TemplateCache.init(alloc); // var cache = TemplateCache.init(alloc);
defer cache.deinit(); // defer cache.deinit();
//
const renderer = Renderer.init(&ctx, &cache); // const renderer = Renderer.init(&ctx, &cache);
//
try ctx.set("texto", Value{ .string = "Exemplo de Texto" }); // try ctx.set("texto", Value{ .string = "Exemplo de Texto" });
//
var buf = std.ArrayList(u8){}; // var buf = std.ArrayList(u8){};
defer buf.deinit(alloc); // defer buf.deinit(alloc);
//
const template = "Resultado: {{ texto|lower|upper }}"; // const template = "Resultado: {{ texto|lower|upper }}";
//
try renderer.renderString(template, buf.writer(alloc)); // try renderer.renderString(template, buf.writer(alloc));
//
try testing.expectEqualStrings("Resultado: EXEMPLO DE TEXTO", buf.items); // assume lower then upper // try testing.expectEqualStrings("Resultado: EXEMPLO DE TEXTO", buf.items); // assume lower then upper
} // }
//
test "autoescape com safe" { // test "autoescape com safe" {
const alloc = testing.allocator; // const alloc = testing.allocator;
var ctx = Context.init(alloc); // var ctx = Context.init(alloc);
defer ctx.deinit(); // defer ctx.deinit();
//
var cache = TemplateCache.init(alloc); // var cache = TemplateCache.init(alloc);
defer cache.deinit(); // defer cache.deinit();
//
const renderer = Renderer.init(&ctx, &cache); // const renderer = Renderer.init(&ctx, &cache);
//
try ctx.set("html", Value{ .string = "<div>conteúdo</div>" }); // try ctx.set("html", Value{ .string = "<div>conteúdo</div>" });
//
var buf = std.ArrayList(u8){}; // var buf = std.ArrayList(u8){};
defer buf.deinit(alloc); // defer buf.deinit(alloc);
//
const template = "Escape: {{ html }} | Safe: {{ html|safe }}"; // const template = "Escape: {{ html }} | Safe: {{ html|safe }}";
//
try renderer.renderString(template, buf.writer(alloc)); // try renderer.renderString(template, buf.writer(alloc));
//
try testing.expectEqualStrings("Escape: &lt;div&gt;conteúdo&lt;/div&gt; | Safe: <div>conteúdo</div>", buf.items); // try testing.expectEqualStrings("Escape: &lt;div&gt;conteúdo&lt;/div&gt; | Safe: <div>conteúdo</div>", buf.items);
} // }
//
test "renderer - if and for" { // test "renderer - if and for" {
const alloc = testing.allocator; // const alloc = testing.allocator;
var ctx = Context.init(alloc); // var ctx = Context.init(alloc);
defer ctx.deinit(); // defer ctx.deinit();
//
var cache = TemplateCache.init(alloc); // var cache = TemplateCache.init(alloc);
defer cache.deinit(); // defer cache.deinit();
//
const renderer = Renderer.init(&ctx, &cache); // const renderer = Renderer.init(&ctx, &cache);
//
try ctx.set("ativo", Value{ .bool = true }); // try ctx.set("ativo", Value{ .bool = true });
try ctx.set("nomes", Value{ .list = &[_]Value{ // try ctx.set("nomes", Value{ .list = &[_]Value{
Value{ .string = "Ana" }, // Value{ .string = "Ana" },
Value{ .string = "Bia" }, // Value{ .string = "Bia" },
Value{ .string = "Cris" }, // Value{ .string = "Cris" },
} }); // } });
//
const template = // const template =
\\{% if ativo %}Sim!{% endif %} // \\{% if ativo %}Sim!{% endif %}
\\{% if not ativo %}Não{% endif %} // \\{% if not ativo %}Não{% endif %}
\\Lista: // \\Lista:
\\{% for nome in nomes %} // \\{% for nome in nomes %}
\\- {{ nome }} // \\- {{ nome }}
\\{% endfor %} // \\{% endfor %}
; // ;
//
var buf = std.ArrayList(u8){}; // var buf = std.ArrayList(u8){};
defer buf.deinit(alloc); // defer buf.deinit(alloc);
//
try renderer.renderString(template, buf.writer(alloc)); // try renderer.renderString(template, buf.writer(alloc));
//
try testing.expect(std.mem.indexOf(u8, buf.items, "Sim!") != null); // 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, "Não") == null);
try testing.expect(std.mem.indexOf(u8, buf.items, "- Ana") != null); // try testing.expect(std.mem.indexOf(u8, buf.items, "- Ana") != null);
try testing.expect(std.mem.indexOf(u8, buf.items, "- Bia") != null); // try testing.expect(std.mem.indexOf(u8, buf.items, "- Bia") != null);
try testing.expect(std.mem.indexOf(u8, buf.items, "- Cris") != null); // try testing.expect(std.mem.indexOf(u8, buf.items, "- Cris") != null);
} // }
//
test "renderer - block and extends" { // test "renderer - block and extends" {
const alloc = testing.allocator; // const alloc = testing.allocator;
var ctx = Context.init(alloc); // var ctx = Context.init(alloc);
defer ctx.deinit(); // defer ctx.deinit();
//
var cache = TemplateCache.init(alloc); // var cache = TemplateCache.init(alloc);
defer cache.deinit(); // defer cache.deinit();
//
const renderer = Renderer.init(&ctx, &cache); // const renderer = Renderer.init(&ctx, &cache);
//
const base = // const base =
\\<html> // \\<html>
\\<head><title>{% block title %}Título Padrão{% endblock %}</title></head> // \\<head><title>{% block title %}Título Padrão{% endblock %}</title></head>
\\<body> // \\<body>
\\{% block content %}Conteúdo padrão{% endblock %} // \\{% block content %}Conteúdo padrão{% endblock %}
\\</body> // \\</body>
\\</html> // \\</html>
; // ;
//
const child = // const child =
\\{% extends "base.html" %} // \\{% extends "base.html" %}
\\{% block title %}Meu Título{% endblock %} // \\{% block title %}Meu Título{% endblock %}
\\{% block content %} // \\{% block content %}
\\Olá {{ nome }}! // \\Olá {{ nome }}!
\\{% endblock %} // \\{% endblock %}
; // ;
//
const expected = // const expected =
\\<html> // \\<html>
\\<head><title>Meu Título</title></head> // \\<head><title>Meu Título</title></head>
\\<body> // \\<body>
\\ // \\
\\Olá Lucas! // \\Olá Lucas!
\\ // \\
\\</body> // \\</body>
\\</html> // \\</html>
; // ;
//
// Simula arquivos (ou usa renderString com base se quiser simplificar) // // Simula arquivos (ou usa renderString com base se quiser simplificar)
try std.fs.cwd().writeFile(.{ .sub_path = "base.html", .data = base }); // try std.fs.cwd().writeFile(.{ .sub_path = "base.html", .data = base });
try std.fs.cwd().writeFile(.{ .sub_path = "child.html", .data = child }); // try std.fs.cwd().writeFile(.{ .sub_path = "child.html", .data = child });
defer std.fs.cwd().deleteFile("base.html") catch {}; // defer std.fs.cwd().deleteFile("base.html") catch {};
defer std.fs.cwd().deleteFile("child.html") catch {}; // defer std.fs.cwd().deleteFile("child.html") catch {};
//
try ctx.set("nome", Value{ .string = "Lucas" }); // try ctx.set("nome", Value{ .string = "Lucas" });
//
var buf = std.ArrayList(u8){}; // var buf = std.ArrayList(u8){};
defer buf.deinit(alloc); // defer buf.deinit(alloc);
//
try renderer.render("child.html", buf.writer(alloc)); // try renderer.render("child.html", buf.writer(alloc));
//
const output = buf.items; // const output = buf.items;
//
std.debug.print("OUTPUT:\n{s}\n", .{output}); // std.debug.print("OUTPUT:\n{s}\n", .{output});
//
try testing.expect(std.mem.indexOf(u8, output, "<html>") != null); // try testing.expect(std.mem.indexOf(u8, output, "<html>") != null);
try testing.expect(std.mem.indexOf(u8, output, "Meu Título") != null); // try testing.expect(std.mem.indexOf(u8, output, "Meu Título") != null);
try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas!") != null); // try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas!") != null);
try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") == null); // try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") == null);
try testing.expectEqualStrings(expected, output); // try testing.expectEqualStrings(expected, output);
} // }
//
test "renderer - block and extends with super" { // test "renderer - block and extends with super" {
const alloc = testing.allocator; // const alloc = testing.allocator;
var ctx = Context.init(alloc); // var ctx = Context.init(alloc);
defer ctx.deinit(); // defer ctx.deinit();
//
var cache = TemplateCache.init(alloc); // var cache = TemplateCache.init(alloc);
defer cache.deinit(); // defer cache.deinit();
//
const renderer = Renderer.init(&ctx, &cache); // const renderer = Renderer.init(&ctx, &cache);
//
const base = // const base =
\\<html> // \\<html>
\\<head><title>{% block title %}Título Padrão{% endblock %}</title></head> // \\<head><title>{% block title %}Título Padrão{% endblock %}</title></head>
\\<body> // \\<body>
\\{% block content %}Conteúdo padrão{% endblock %} // \\{% block content %}Conteúdo padrão{% endblock %}
\\</body> // \\</body>
\\</html> // \\</html>
; // ;
//
const child = // const child =
\\{% extends "base.html" %} // \\{% extends "base.html" %}
\\{% block title %}Meu Título{% endblock %} // \\{% block title %}Meu Título{% endblock %}
\\{% block content %} // \\{% block content %}
\\{{ block.super }} // \\{{ block.super }}
\\Olá {{ nome }}! // \\Olá {{ nome }}!
\\{% endblock %} // \\{% endblock %}
; // ;
//
const expected = // const expected =
\\<html> // \\<html>
\\<head><title>Meu Título</title></head> // \\<head><title>Meu Título</title></head>
\\<body> // \\<body>
\\ // \\
\\Conteúdo padrão // \\Conteúdo padrão
\\Olá Lucas! // \\Olá Lucas!
\\ // \\
\\</body> // \\</body>
\\</html> // \\</html>
; // ;
//
// Simula arquivos (ou usa renderString com base se quiser simplificar) // // Simula arquivos (ou usa renderString com base se quiser simplificar)
try std.fs.cwd().writeFile(.{ .sub_path = "base.html", .data = base }); // try std.fs.cwd().writeFile(.{ .sub_path = "base.html", .data = base });
try std.fs.cwd().writeFile(.{ .sub_path = "child.html", .data = child }); // try std.fs.cwd().writeFile(.{ .sub_path = "child.html", .data = child });
defer std.fs.cwd().deleteFile("base.html") catch {}; // defer std.fs.cwd().deleteFile("base.html") catch {};
defer std.fs.cwd().deleteFile("child.html") catch {}; // defer std.fs.cwd().deleteFile("child.html") catch {};
//
try ctx.set("nome", Value{ .string = "Lucas" }); // try ctx.set("nome", Value{ .string = "Lucas" });
//
var buf = std.ArrayList(u8){}; // var buf = std.ArrayList(u8){};
defer buf.deinit(alloc); // defer buf.deinit(alloc);
//
try renderer.render("child.html", buf.writer(alloc)); // try renderer.render("child.html", buf.writer(alloc));
//
const output = buf.items; // const output = buf.items;
//
std.debug.print("{s}\n", .{output}); // std.debug.print("{s}\n", .{output});
//
try testing.expect(std.mem.indexOf(u8, output, "<html>") != null); // try testing.expect(std.mem.indexOf(u8, output, "<html>") != null);
try testing.expect(std.mem.indexOf(u8, output, "Meu Título") != null); // try testing.expect(std.mem.indexOf(u8, output, "Meu Título") != null);
try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas!") != null); // try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas!") != null);
try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") != null); // try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") != null);
try testing.expectEqualStrings(expected, output); // try testing.expectEqualStrings(expected, output);
} // }
//
test "renderer - include" { // test "renderer - include" {
const alloc = testing.allocator; // const alloc = testing.allocator;
//
const header = // const header =
\\<header> // \\<header>
\\ <h1>Bem-vindo</h1> // \\ <h1>Bem-vindo</h1>
\\</header> // \\</header>
; // ;
//
const main = // const main =
\\{% include "header.html" %} // \\{% include "header.html" %}
\\<main> // \\<main>
\\ <p>Conteúdo principal</p> // \\ <p>Conteúdo principal</p>
\\ Olá {{ nome }}! // \\ Olá {{ nome }}!
\\</main> // \\</main>
; // ;
//
const expected = // const expected =
\\<header> // \\<header>
\\ <h1>Bem-vindo</h1> // \\ <h1>Bem-vindo</h1>
\\</header> // \\</header>
\\<main> // \\<main>
\\ <p>Conteúdo principal</p> // \\ <p>Conteúdo principal</p>
\\ Olá Lucas! // \\ Olá Lucas!
\\</main> // \\</main>
; // ;
//
try std.fs.cwd().writeFile(.{ .sub_path = "header.html", .data = header }); // try std.fs.cwd().writeFile(.{ .sub_path = "header.html", .data = header });
try std.fs.cwd().writeFile(.{ .sub_path = "main.html", .data = main }); // try std.fs.cwd().writeFile(.{ .sub_path = "main.html", .data = main });
defer std.fs.cwd().deleteFile("header.html") catch {}; // defer std.fs.cwd().deleteFile("header.html") catch {};
defer std.fs.cwd().deleteFile("main.html") catch {}; // defer std.fs.cwd().deleteFile("main.html") catch {};
//
var ctx = Context.init(alloc); // var ctx = Context.init(alloc);
defer ctx.deinit(); // defer ctx.deinit();
//
var cache = TemplateCache.init(alloc); // var cache = TemplateCache.init(alloc);
defer cache.deinit(); // defer cache.deinit();
//
const renderer = Renderer.init(&ctx, &cache); // const renderer = Renderer.init(&ctx, &cache);
//
try ctx.set("nome", Value{ .string = "Lucas" }); // try ctx.set("nome", Value{ .string = "Lucas" });
//
var buf = std.ArrayList(u8){}; // var buf = std.ArrayList(u8){};
defer buf.deinit(alloc); // defer buf.deinit(alloc);
//
try renderer.render("main.html", buf.writer(alloc)); // try renderer.render("main.html", buf.writer(alloc));
//
const output = buf.items; // const output = buf.items;
//
try testing.expect(std.mem.indexOf(u8, output, "<h1>Bem-vindo</h1>") != null); // 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.expect(std.mem.indexOf(u8, output, "Olá Lucas!") != null);
try testing.expectEqualStrings(expected, output); // try testing.expectEqualStrings(expected, output);
} // }
//
test "renderer - comment" { // test "renderer - comment" {
const alloc = testing.allocator; // const alloc = testing.allocator;
//
const template = // const template =
\\Normal: Olá {{ nome }} // \\Normal: Olá {{ nome }}
\\{% comment %} // \\{% comment %}
\\ Isso é um comentário // \\ Isso é um comentário
\\ que não deve aparecer // \\ que não deve aparecer
\\ nem processar variáveis {{ nome }} // \\ nem processar variáveis {{ nome }}
\\{% endcomment %} // \\{% endcomment %}
\\Fim: {{ nome }} // \\Fim: {{ nome }}
; // ;
const expected = // const expected =
\\Normal: Olá Lucas // \\Normal: Olá Lucas
\\ // \\
\\Fim: Lucas // \\Fim: Lucas
; // ;
//
var ctx = Context.init(alloc); // var ctx = Context.init(alloc);
defer ctx.deinit(); // defer ctx.deinit();
//
var cache = TemplateCache.init(alloc); // var cache = TemplateCache.init(alloc);
defer cache.deinit(); // defer cache.deinit();
//
const renderer = Renderer.init(&ctx, &cache); // const renderer = Renderer.init(&ctx, &cache);
//
try ctx.set("nome", Value{ .string = "Lucas" }); // try ctx.set("nome", Value{ .string = "Lucas" });
//
var buf = std.ArrayList(u8){}; // var buf = std.ArrayList(u8){};
defer buf.deinit(alloc); // defer buf.deinit(alloc);
//
try renderer.renderString(template, buf.writer(alloc)); // try renderer.renderString(template, buf.writer(alloc));
//
const output = buf.items; // const output = buf.items;
//
try testing.expect(std.mem.indexOf(u8, output, "Olá Lucas") != null); // 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, "Fim: Lucas") != null);
try testing.expect(std.mem.indexOf(u8, output, "Isso é um comentário") == null); // try testing.expect(std.mem.indexOf(u8, output, "Isso é um comentário") == null);
try testing.expect(std.mem.indexOf(u8, output, "que não deve aparecer") == null); // try testing.expect(std.mem.indexOf(u8, output, "que não deve aparecer") == null);
try testing.expect(std.mem.indexOf(u8, output, "nem processar variáveis") == null); // try testing.expect(std.mem.indexOf(u8, output, "nem processar variáveis") == null);
try testing.expectEqualStrings(expected, output); // try testing.expectEqualStrings(expected, output);
} // }
// FIX: comment inside block // FIX: comment inside block
//
// test "renderer - full template with extends, super, include, comment" { // test "renderer - full template with extends, super, include, comment" {
// const alloc = testing.allocator; // const alloc = testing.allocator;
// //
@ -436,8 +436,63 @@ test "renderer - comment" {
// //
// const output = buf.items; // const output = buf.items;
// //
// std.debug.print("OUTPUT:\n{s}\n", .{output});
//
// try testing.expect(std.mem.indexOf(u8, output, "<header>Bem-vindo</header>") != null); // try testing.expect(std.mem.indexOf(u8, output, "<header>Bem-vindo</header>") != null);
// try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") != null); // try testing.expect(std.mem.indexOf(u8, output, "Conteúdo padrão") != null);
// try testing.expect(std.mem.indexOf(u8, output, "Conteúdo do filho") != null); // try testing.expect(std.mem.indexOf(u8, output, "Conteúdo do filho") != null);
// try testing.expect(std.mem.indexOf(u8, output, "Isso não aparece") == null); // try testing.expect(std.mem.indexOf(u8, output, "Isso não aparece") == null);
// } // }
test "renderer - if inside block" {
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();
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{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);
}