294 lines
11 KiB
Zig
294 lines
11 KiB
Zig
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");
|
|
const TemplateCache = @import("cache.zig").TemplateCache;
|
|
|
|
pub const RenderError = error{
|
|
InvalidCharacter,
|
|
InvalidSyntax,
|
|
OutOfMemory,
|
|
Overflow,
|
|
Unexpected,
|
|
UnsupportedExpression,
|
|
} || FilterError || parser.ParserError || std.fs.File.OpenError;
|
|
|
|
pub const Renderer = struct {
|
|
context: *Context,
|
|
allocator: Allocator,
|
|
cache: *TemplateCache,
|
|
|
|
pub fn init(context: *Context, cache: *TemplateCache) Renderer {
|
|
return .{
|
|
.context = context,
|
|
.allocator = context.allocator(),
|
|
.cache = cache,
|
|
};
|
|
}
|
|
|
|
fn checkForExtends(self: *const Renderer, nodes: []parser.Node) ?parser.Node {
|
|
_ = self;
|
|
for (nodes) |n| {
|
|
if (n.type == .extends) return n;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
fn readTemplateFile(self: *const Renderer, path: []const u8) ![]const u8 {
|
|
const max_size = 10 * 1024 * 1024;
|
|
|
|
const full_path = try std.fs.path.join(self.allocator, &.{ self.cache.default_path.?, path });
|
|
defer self.allocator.free(full_path);
|
|
|
|
const file = try std.fs.cwd().openFile(full_path, .{});
|
|
defer file.close();
|
|
|
|
// return std.fs.cwd().readFileAlloc(self.allocator, path, max_size) catch |err| switch (err) {
|
|
return file.readToEndAlloc(self.allocator, max_size) catch |err| switch (err) {
|
|
else => return RenderError.Unexpected,
|
|
};
|
|
}
|
|
|
|
fn renderNodes(self: *const Renderer, alloc: Allocator, nodes: []parser.Node, writer: anytype) RenderError!void {
|
|
const extends_node = self.checkForExtends(nodes);
|
|
|
|
if (extends_node) |ext| {
|
|
const base_template = try self.readTemplateFile(ext.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);
|
|
} else {
|
|
for (nodes) |node| {
|
|
try self.renderNode(alloc, nodes, node, writer, null, null);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn renderTemplate(self: *const Renderer, template: []const u8, writer: anytype, cache_key: ?[]const u8) RenderError!void {
|
|
var arena = std.heap.ArenaAllocator.init(self.allocator);
|
|
defer arena.deinit();
|
|
const alloc = arena.allocator();
|
|
|
|
if (cache_key) |ck| {
|
|
if (self.cache.get(ck)) |cached_nodes| {
|
|
try self.renderNodes(alloc, cached_nodes, writer);
|
|
return;
|
|
}
|
|
}
|
|
|
|
var p = parser.Parser.init(template);
|
|
const nodes = try p.parse(alloc);
|
|
defer {
|
|
for (nodes) |node| node.deinit(alloc);
|
|
alloc.free(nodes);
|
|
}
|
|
|
|
if (cache_key) |ck| {
|
|
var alc = self.cache.allocator();
|
|
var cached_nodes = try alc.alloc(parser.Node, nodes.len);
|
|
errdefer alc.free(cached_nodes);
|
|
for (nodes, 0..) |node, i| {
|
|
cached_nodes[i] = try node.clone(self.allocator);
|
|
std.debug.print("clonou {any}\n", .{cached_nodes[i]});
|
|
}
|
|
try self.cache.add(ck, nodes);
|
|
}
|
|
|
|
return try self.renderNodes(alloc, nodes, writer);
|
|
}
|
|
|
|
pub fn render(self: *const Renderer, template_path: []const u8, writer: anytype) RenderError!void {
|
|
const base_template = try self.readTemplateFile(template_path);
|
|
defer self.allocator.free(base_template);
|
|
|
|
return try self.renderTemplate(base_template, writer, template_path);
|
|
}
|
|
|
|
pub fn renderString(self: *const Renderer, template: []const u8, writer: anytype) RenderError!void {
|
|
return try self.renderTemplate(template, 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, passando o conteúdo do pai para block.super
|
|
for (child.body) |child_node| {
|
|
try self.renderNode(alloc, child_nodes, child_node, writer, null, base_node.block.?.body);
|
|
}
|
|
} else {
|
|
// Renderiza o do pai
|
|
for (base_node.block.?.body) |child| {
|
|
try self.renderNode(alloc, child_nodes, child, writer, null, null);
|
|
}
|
|
}
|
|
} else {
|
|
// Qualquer outra coisa no base
|
|
try self.renderNode(alloc, child_nodes, base_node, writer, null, null);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn renderNode(self: *const Renderer, alloc: Allocator, nodes: []parser.Node, node: parser.Node, writer: anytype, context: ?*Context, parent_block_nodes: ?[]parser.Node) RenderError!void {
|
|
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;
|
|
var arg: Value = Value.null;
|
|
if (f.arg) |a| {
|
|
arg = Value{ .string = a };
|
|
const result = try std.fmt.parseInt(i64, a, 10);
|
|
if (std.math.maxInt(i64) < result) return error.Overflow;
|
|
arg = Value{ .int = result };
|
|
}
|
|
value = try filter_fn(alloc, value, arg);
|
|
|
|
if (std.mem.eql(u8, f.name, "safe")) is_safe = true;
|
|
}
|
|
|
|
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, null);
|
|
}
|
|
} else {
|
|
for (node.@"if".?.false_body) |child| {
|
|
try self.renderNode(alloc, nodes, child, writer, null, null);
|
|
}
|
|
}
|
|
},
|
|
.include => {
|
|
const included_template = try self.readTemplateFile(node.include.?.template_name);
|
|
defer alloc.free(included_template);
|
|
|
|
var included_parser = parser.Parser.init(included_template);
|
|
const included_nodes = try included_parser.parse(alloc);
|
|
defer {
|
|
for (included_nodes) |n| n.deinit(alloc);
|
|
alloc.free(included_nodes);
|
|
}
|
|
|
|
// Renderiza o include no contexto atual (sem novo contexto)
|
|
for (included_nodes) |included_node| {
|
|
try self.renderNode(alloc, nodes, included_node, writer, context, null);
|
|
}
|
|
},
|
|
.for_block => {
|
|
const list_value = self.context.get(node.@"for".?.iterable) orelse Value.null;
|
|
const list = switch (list_value) {
|
|
.list => |l| l,
|
|
else => return,
|
|
};
|
|
|
|
for (list) |item| {
|
|
var ctx = Context.init(alloc);
|
|
defer ctx.deinit();
|
|
|
|
try ctx.set(node.@"for".?.loop_var, item);
|
|
|
|
for (node.@"for".?.body) |child| {
|
|
try self.renderNode(alloc, nodes, child, writer, &ctx, null);
|
|
}
|
|
|
|
for (node.@"for".?.empty_body) |child| {
|
|
try self.renderNode(alloc, nodes, child, writer, &ctx, null);
|
|
}
|
|
}
|
|
},
|
|
.super => {
|
|
if (parent_block_nodes) |parent| {
|
|
for (parent) |child| {
|
|
try self.renderNode(alloc, nodes, child, writer, null, null);
|
|
}
|
|
}
|
|
},
|
|
.block => {
|
|
for (node.block.?.body) |child| {
|
|
const parent_content = parent_block_nodes orelse node.block.?.body;
|
|
try self.renderNode(alloc, nodes, child, writer, null, parent_content);
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
fn 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,
|
|
};
|
|
}
|
|
};
|