update: renderer initial idea
This commit is contained in:
parent
d1c63cddda
commit
0e74ddb278
2 changed files with 225 additions and 0 deletions
105
src/renderer.zig
Normal file
105
src/renderer.zig
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
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(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn renderString(self: *const Renderer, template: []const u8, writer: anytype) RenderError!void {
|
||||||
|
var p = parser.Parser.init(template);
|
||||||
|
const nodes = try p.parse(self.allocator);
|
||||||
|
for (nodes) |node| {
|
||||||
|
try self.renderNode(node, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn renderNode(self: *const Renderer, node: parser.Node, writer: anytype) RenderError!void {
|
||||||
|
switch (node.type) {
|
||||||
|
.text => try writer.writeAll(node.text.?.content),
|
||||||
|
.variable => {
|
||||||
|
const var_name = node.variable.?.expr;
|
||||||
|
var value: Value = self.context.get(var_name) orelse Value.null;
|
||||||
|
var buf = ArrayListUnmanaged(u8){};
|
||||||
|
defer buf.deinit(self.allocator);
|
||||||
|
|
||||||
|
var is_safe = false;
|
||||||
|
|
||||||
|
for (node.variable.?.filters) |f| {
|
||||||
|
const filter_fn = builtin_filters.get(f.name) orelse continue;
|
||||||
|
value = try filter_fn(self.allocator, value, Value{.string = f.arg orelse ""});
|
||||||
|
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(self.allocator, value, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.valueToString(&buf, value);
|
||||||
|
try writer.writeAll(buf.items);
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valueToString(self: *const Renderer, buf: *ArrayListUnmanaged(u8), value: Value) !void {
|
||||||
|
var w = buf.writer(self.allocator);
|
||||||
|
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}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
120
src/renderer_test.zig
Normal file
120
src/renderer_test.zig
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
const Renderer = @import("renderer.zig").Renderer;
|
||||||
|
const RenderError = @import("renderer.zig").RenderError;
|
||||||
|
|
||||||
|
const Context = @import("context.zig").Context;
|
||||||
|
const Value = @import("context.zig").Value; // ajuste o caminho se estiver diferente
|
||||||
|
|
||||||
|
test "renderer: literal + variável simples" {
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var ctx = Context.init(alloc);
|
||||||
|
defer ctx.deinit();
|
||||||
|
|
||||||
|
try ctx.set("nome", Value{ .string = "Mariana" });
|
||||||
|
|
||||||
|
const renderer = Renderer.init(&ctx);
|
||||||
|
|
||||||
|
var buf = std.ArrayList(u8){};
|
||||||
|
defer buf.deinit(alloc);
|
||||||
|
|
||||||
|
const template =
|
||||||
|
\\Olá, {{ nome }}! Bem-vinda.
|
||||||
|
;
|
||||||
|
|
||||||
|
try renderer.renderString(template, buf.writer(alloc));
|
||||||
|
|
||||||
|
try testing.expectEqualStrings("Olá, Mariana! Bem-vinda.", buf.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "renderer: filtros + autoescape" {
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var ctx = Context.init(alloc);
|
||||||
|
|
||||||
|
defer ctx.deinit();
|
||||||
|
|
||||||
|
try ctx.set("html", Value{ .string = "<script>alert()</script>" });
|
||||||
|
try ctx.set("texto", Value{ .string = "maiusculo e slug" });
|
||||||
|
|
||||||
|
const renderer = Renderer.init(&ctx);
|
||||||
|
|
||||||
|
var buf = std.ArrayList(u8){};
|
||||||
|
defer buf.deinit(alloc);
|
||||||
|
|
||||||
|
const template =
|
||||||
|
\\Normal: {{ html }}
|
||||||
|
\\Safe: {{ html|safe }}
|
||||||
|
\\Filtrado: {{ texto|upper|slugify }}
|
||||||
|
;
|
||||||
|
|
||||||
|
try renderer.renderString(template, buf.writer(alloc));
|
||||||
|
|
||||||
|
const expected =
|
||||||
|
\\Normal: <script>alert()</script>
|
||||||
|
\\Safe: <script>alert()</script>
|
||||||
|
\\Filtrado: maiusculo-e-slug
|
||||||
|
;
|
||||||
|
|
||||||
|
try testing.expectEqualStrings(expected, buf.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "literal simples" {
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var ctx = Context.init(alloc);
|
||||||
|
|
||||||
|
defer ctx.deinit();
|
||||||
|
|
||||||
|
const renderer = Renderer.init(&ctx);
|
||||||
|
|
||||||
|
var buf = std.ArrayList(u8){};
|
||||||
|
defer buf.deinit(alloc);
|
||||||
|
|
||||||
|
const template = "Texto literal com acentos: Olá, mundo!";
|
||||||
|
|
||||||
|
try renderer.renderString(template, buf.writer(alloc));
|
||||||
|
|
||||||
|
try testing.expectEqualStrings("Texto literal com acentos: Olá, mundo!", buf.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "variável com filtro encadeado e autoescape" {
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var ctx = Context.init(alloc);
|
||||||
|
|
||||||
|
defer ctx.deinit();
|
||||||
|
|
||||||
|
const renderer = Renderer.init(&ctx);
|
||||||
|
|
||||||
|
try ctx.set("texto", Value{ .string = "Exemplo de Texto" });
|
||||||
|
|
||||||
|
|
||||||
|
var buf = std.ArrayList(u8){};
|
||||||
|
defer buf.deinit(alloc);
|
||||||
|
|
||||||
|
const template = "Resultado: {{ texto|lower|upper }}";
|
||||||
|
|
||||||
|
try renderer.renderString(template, buf.writer(alloc));
|
||||||
|
|
||||||
|
try testing.expectEqualStrings("Resultado: EXEMPLO DE TEXTO", buf.items); // assume lower then upper
|
||||||
|
}
|
||||||
|
|
||||||
|
test "autoescape com safe" {
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
var ctx = Context.init(alloc);
|
||||||
|
|
||||||
|
defer ctx.deinit();
|
||||||
|
|
||||||
|
const renderer = Renderer.init(&ctx);
|
||||||
|
|
||||||
|
try ctx.set("html", Value{ .string = "<div>conteúdo</div>" });
|
||||||
|
|
||||||
|
|
||||||
|
var buf = std.ArrayList(u8){};
|
||||||
|
defer buf.deinit(alloc);
|
||||||
|
|
||||||
|
const template = "Escape: {{ html }} | Safe: {{ html|safe }}";
|
||||||
|
|
||||||
|
try renderer.renderString(template, buf.writer(alloc));
|
||||||
|
|
||||||
|
try testing.expectEqualStrings("Escape: <div>conteúdo</div> | Safe: <div>conteúdo</div>", buf.items);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue