diff --git a/src/lorem.zig b/src/lorem.zig new file mode 100644 index 0000000..b3ad1e0 --- /dev/null +++ b/src/lorem.zig @@ -0,0 +1,338 @@ +const std = @import("std"); +const rand = std.crypto.random; + +pub const LOREM_COMMON_P = + \\Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod + \\tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim + \\veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea + \\commodo consequat. Duis aute irure dolor in reprehenderit in voluptate + \\velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint + \\occaecat cupidatat non proident, sunt in culpa qui officia deserunt + \\mollit anim id est laborum. +; + +pub const LOREM_WORDS = [182][]const u8{ + "exercitationem", + "perferendis", + "perspiciatis", + "laborum", + "eveniet", + "sunt", + "iure", + "nam", + "nobis", + "eum", + "cum", + "officiis", + "excepturi", + "odio", + "consectetur", + "quasi", + "aut", + "quisquam", + "vel", + "eligendi", + "itaque", + "non", + "odit", + "tempore", + "quaerat", + "dignissimos", + "facilis", + "neque", + "nihil", + "expedita", + "vitae", + "vero", + "ipsum", + "nisi", + "animi", + "cumque", + "pariatur", + "velit", + "modi", + "natus", + "iusto", + "eaque", + "sequi", + "illo", + "sed", + "ex", + "et", + "voluptatibus", + "tempora", + "veritatis", + "ratione", + "assumenda", + "incidunt", + "nostrum", + "placeat", + "aliquid", + "fuga", + "provident", + "praesentium", + "rem", + "necessitatibus", + "suscipit", + "adipisci", + "quidem", + "possimus", + "voluptas", + "debitis", + "sint", + "accusantium", + "unde", + "sapiente", + "voluptate", + "qui", + "aspernatur", + "laudantium", + "soluta", + "amet", + "quo", + "aliquam", + "saepe", + "culpa", + "libero", + "ipsa", + "dicta", + "reiciendis", + "nesciunt", + "doloribus", + "autem", + "impedit", + "minima", + "maiores", + "repudiandae", + "ipsam", + "obcaecati", + "ullam", + "enim", + "totam", + "delectus", + "ducimus", + "quis", + "voluptates", + "dolores", + "molestiae", + "harum", + "dolorem", + "quia", + "voluptatem", + "molestias", + "magni", + "distinctio", + "omnis", + "illum", + "dolorum", + "voluptatum", + "ea", + "quas", + "quam", + "corporis", + "quae", + "blanditiis", + "atque", + "deserunt", + "laboriosam", + "earum", + "consequuntur", + "hic", + "cupiditate", + "quibusdam", + "accusamus", + "ut", + "rerum", + "error", + "minus", + "eius", + "ab", + "ad", + "nemo", + "fugit", + "officia", + "at", + "in", + "id", + "quos", + "reprehenderit", + "numquam", + "iste", + "fugiat", + "sit", + "inventore", + "beatae", + "repellendus", + "magnam", + "recusandae", + "quod", + "explicabo", + "doloremque", + "aperiam", + "consequatur", + "asperiores", + "commodi", + "optio", + "dolor", + "labore", + "temporibus", + "repellat", + "veniam", + "architecto", + "est", + "esse", + "mollitia", + "nulla", + "a", + "similique", + "eos", + "alias", + "dolore", + "tenetur", + "deleniti", + "porro", + "facere", + "maxime", + "corrupti", +}; + +pub const LOREM_COMMON_WORDS = [19][]const u8{ + "lorem", + "ipsum", + "dolor", + "sit", + "amet", + "consectetur", + "adipisicing", + "elit", + "sed", + "do", + "eiusmod", + "tempor", + "incididunt", + "ut", + "labore", + "et", + "dolore", + "magna", + "aliqua", +}; + +pub fn sentence(allocator: std.mem.Allocator) ![]const u8 { + const num_sections = rand.intRangeAtMost(u32, 1, 4); + + var parts = std.ArrayList([]u8){}; + defer { + for (parts.items) |p| allocator.free(p); + parts.deinit(allocator); + } + + var i: u32 = 0; + while (i < num_sections) : (i += 1) { + const num_words = rand.intRangeAtMost(u32, 3, 12); + + var wds = std.ArrayList([]const u8){}; + defer wds.deinit(allocator); + try wds.ensureTotalCapacity(allocator, num_words); + + var j: u32 = 0; + while (j < num_words) : (j += 1) { + const idx = rand.intRangeAtMost(usize, 0, LOREM_WORDS.len - 1); + try wds.append(allocator, LOREM_WORDS[idx]); + } + + const section = try std.mem.join(allocator, " ", wds.items); + try parts.append(allocator, section); + } + + const text = try std.mem.join(allocator, ", ", parts.items); + defer allocator.free(text); + + var result = try allocator.alloc(u8, text.len + 1); + if (text.len > 0) { + result[0] = std.ascii.toUpper(text[0]); + @memcpy(result[1..text.len], text[1..]); + } + result[text.len] = if (rand.boolean()) '.' else '?'; + + return result; +} + +pub fn paragraph(allocator: std.mem.Allocator) ![]const u8 { + const num_sentences = rand.intRangeAtMost(u32, 1, 4); + var sentences = std.ArrayList([]const u8){}; + defer sentences.deinit(allocator); + + for (0..num_sentences) |_| { + try sentences.append(allocator, try sentence(allocator)); + } + + return try std.mem.join(allocator, ". ", sentences.items); +} + +pub fn paragraphs(allocator: std.mem.Allocator, count: u32, random: bool) ![]const u8 { + var pa = std.ArrayList([]const u8){}; + defer pa.deinit(allocator); + + if (count == 0) return ""; + + if (random == true) { + for (0..count) |_| { + const pg = try paragraph(allocator); + if (pg.len > 0) { + try pa.append(allocator, try std.fmt.allocPrint(allocator, "
{s}
", .{pg})); + } + } + + return try std.mem.join(allocator, "\n", pa.items); + } + + const first = try std.fmt.allocPrint(allocator, "{s}
", .{LOREM_COMMON_P}); + if (count == 1) { + return first; + } + + const ncount: u32 = count - 1; + try pa.append(allocator, first); + + for (0..ncount) |_| { + const pg = try paragraph(allocator); + if (pg.len > 0) { + try pa.append(allocator, try std.fmt.allocPrint(allocator, "{s}
", .{pg})); + } + } + + return try std.mem.join(allocator, "\n", pa.items); +} + +pub fn words(allocator: std.mem.Allocator, count: u32, random: bool) ![]const u8 { + var wd = std.ArrayList([]const u8){}; + defer wd.deinit(allocator); + + if (random == true) { + for (0..count) |_| { + const idx = rand.intRangeAtMost(usize, 0, LOREM_COMMON_WORDS.len - 1); + try wd.append(allocator, LOREM_COMMON_WORDS[idx]); + } + return try std.mem.join(allocator, " ", wd.items); + } + + var inc: u32 = 0; + + for (LOREM_COMMON_WORDS) |word| { + try wd.append(allocator, word); + inc += 1; + if (inc >= count or inc >= 20) break; + } + + if (count >= 20) { + const ncount = count - inc; + + for (0..ncount) |_| { + const idx = rand.intRangeAtMost(usize, 0, LOREM_COMMON_WORDS.len - 1); + try wd.append(allocator, LOREM_COMMON_WORDS[idx]); + } + } + + return try std.mem.join(allocator, " ", wd.items); +} diff --git a/src/parser.zig b/src/parser.zig index ef075ef..f2f7c3e 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -202,6 +202,7 @@ pub const FilterBlockNode = struct { pub const FirstOfNode = struct { values: []const []const u8, + fallback: []const u8, }; pub const ForNode = struct { @@ -232,7 +233,7 @@ pub const LoadNode = struct { pub const LoremNode = struct { count: ?[]const u8 = null, // "3" ou null method: ?[]const u8 = null, // "p" ou "w" ou null - format: ?[]const u8 = null, // "html" ou null + random: bool = false, }; pub const NowNode = struct { @@ -450,10 +451,12 @@ pub const Node = struct { values_copy[i] = try allocator.dupe(u8, f); } + const fallback_copy = try allocator.dupe(u8, self.tag.?.body.firstof.fallback); + return Node{ .type = .tag, .tag = .{ .kind = .firstof, .args = try allocator.dupe(u8, self.tag.?.args), - .body = .{ .firstof = .{ .values = values_copy } }, + .body = .{ .firstof = .{ .values = values_copy, .fallback = fallback_copy } }, .raw = try allocator.dupe(u8, self.tag.?.raw), } }; }, @@ -776,7 +779,7 @@ pub const Node = struct { .lorem = .{ .count = if (self.tag.?.body.lorem.count) |c| try allocator.dupe(u8, c) else null, .method = if (self.tag.?.body.lorem.method) |m| try allocator.dupe(u8, m) else null, - .format = if (self.tag.?.body.lorem.format) |f| try allocator.dupe(u8, f) else null, + .random = self.tag.?.body.lorem.random, }, }, }, @@ -1375,7 +1378,7 @@ pub const Parser = struct { if (tag_node.tag.?.kind == .comment) { try self.parseComment(); continue; - }else { + } else { if (try self.parseTagContent(allocator, tag_node)) |tn| { tag.tag.?.body = tn; try body.append(allocator, tag); @@ -1843,6 +1846,7 @@ pub const Parser = struct { var values = std.ArrayList([]const u8){}; defer values.deinit(allocator); var i: usize = 0; + var fallback: []const u8 = ""; while (i < args.len) { while (i < args.len and std.ascii.isWhitespace(args[i])) : (i += 1) {} @@ -1870,12 +1874,17 @@ pub const Parser = struct { } const value = std.mem.trim(u8, args[start..i], " \t\r\n\"'"); - try values.append(allocator, value); + if (in_quote) { + fallback = value; + } else { + try values.append(allocator, value); + } } return TagNodeBody{ .firstof = .{ .values = try values.toOwnedSlice(allocator), + .fallback = fallback, }, }; }, @@ -1936,7 +1945,7 @@ pub const Parser = struct { const args = tag_node.tag.?.args; var count: ?[]const u8 = null; var method: ?[]const u8 = null; - var format: ?[]const u8 = null; + var random: bool = false; var parts = std.mem.splitScalar(u8, args, ' '); while (parts.next()) |part| { @@ -1950,8 +1959,8 @@ pub const Parser = struct { if (std.mem.eql(u8, trimmed, "p") or std.mem.eql(u8, trimmed, "w")) { method = trimmed; - } else if (std.mem.eql(u8, trimmed, "html") or std.mem.eql(u8, trimmed, "text")) { - format = trimmed; + } else if (std.mem.eql(u8, trimmed, "true")) { + random = true; } } @@ -1959,7 +1968,7 @@ pub const Parser = struct { .lorem = .{ .count = count, .method = method, - .format = format, + .random = random, }, }; }, diff --git a/src/renderer.zig b/src/renderer.zig index 9cbca52..d317617 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -11,6 +11,7 @@ const FilterError = @import("filters.zig").FilterError; const parser = @import("parser.zig"); const TemplateCache = @import("cache.zig").TemplateCache; const time = @import("time.zig"); +const lorem = @import("lorem.zig"); pub const RenderError = error{ InvalidCharacter, @@ -309,181 +310,91 @@ pub const Renderer = struct { try writer.writeAll(std.fmt.allocPrint(alloc, "{d}", .{ratio}) catch "0"); }, - .now =>{ + .now => { var format: []const u8 = node.tag.?.body.now.format; if (format.len == 0) format = "Y-m-d H:i:s"; const datetime = try time.Time.now().toStringAlloc(alloc, format); try writer.writeAll(datetime); }, + .csrf_token => { + const token = self.context.get("csrf_token"); + if (token == null) return; + try writer.writeAll(std.fmt.allocPrint(alloc, "", .{token.?.string}) catch ""); + }, + .firstof => { + const values = node.tag.?.body.firstof.values; + for (values) |value| { + if (self.context.get(value)) |v| { + if (!isTruthy(v)) continue; + + var buf = ArrayListUnmanaged(u8){}; + defer buf.deinit(alloc); + + try self.valueToString(alloc, &buf, v); + + try writer.writeAll(buf.items); + return; + } else { + const check_value = self.resolveStringVariable(value).?; + if (!isTruthy(check_value)) continue; + + var buf = ArrayListUnmanaged(u8){}; + defer buf.deinit(alloc); + + try self.valueToString(alloc, &buf, Value{ .string = value }); + try writer.writeAll(buf.items); + return; + } + } + try writer.writeAll(node.tag.?.body.firstof.fallback); + }, + .lorem => { + const count = node.tag.?.body.lorem.count; + const method = node.tag.?.body.lorem.method; + const random = node.tag.?.body.lorem.random; + + if (count == null and method == null) { + if (random == false) { + try writer.writeAll(lorem.LOREM_COMMON_P); + return; + }else { + try writer.writeAll(try lorem.sentence(alloc)); + return; + } + } + + const ncount: u32 = std.fmt.parseInt(u32, count.?, 10) catch 1; + if (std.mem.eql(u8, method.?, "p")) { + const lorem_ = try lorem.paragraphs(alloc, ncount, random); + try writer.writeAll(lorem_); + return; + } else { + const lorem_ = try lorem.words(alloc, ncount, random); + try writer.writeAll(lorem_); + return; + } + }, else => {}, } }, } } - fn renderNode_old(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; - } + fn resolveStringVariable(self: *const Renderer, value: []const u8) ?Value { + _ = self; + if (std.mem.eql(u8, value, "true")) return Value{ .bool = true }; + if (std.mem.eql(u8, value, "false")) return Value{ .bool = false }; + const is_int = std.fmt.parseInt(i64, value, 10) catch |err| switch (err) { + error.InvalidCharacter => null, + error.Overflow => null, + }; + if (is_int != null) return Value{ .int = is_int.? }; - 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 = try self.evaluateCondition(alloc, node.@"if".?.condition); - - 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); - } - - if (node.@"for".?.body.len == 0) { - 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); - } - }, - .widthratio => { - var divisor: Value = Value{ .float = 1.0 }; - var float_divisor: f64 = 1.0; - - var value: Value = Value{ .float = 1.0 }; - var float_value: f64 = 1.0; - - var max_value: Value = Value{ .float = 1.0 }; - var float_max_value: f64 = 1.0; - - if (!std.mem.eql(u8, node.widthratio.?.value, "")) { - value = Value{ .string = node.widthratio.?.value }; - if (self.context.get(node.widthratio.?.value)) |v| { - value = v; - } - float_value = switch (value) { - .int => @as(f64, @floatFromInt(value.int)), - .float => value.float, - .string => std.fmt.parseFloat(f64, value.string) catch 1.0, - else => 1.0, - }; - } - - if (!std.mem.eql(u8, node.widthratio.?.max_value, "")) { - max_value = Value{ .string = node.widthratio.?.max_value }; - if (self.context.get(node.widthratio.?.max_value)) |v| { - max_value = v; - } - float_max_value = switch (max_value) { - .int => @as(f64, @floatFromInt(max_value.int)), - .float => max_value.float, - .string => std.fmt.parseFloat(f64, max_value.string) catch 1.0, - else => 1.0, - }; - } - - if (node.widthratio.?.divisor) |div| { - divisor = Value{ .string = div }; - if (self.context.get(div)) |d| { - divisor = d; - } - float_divisor = switch (divisor) { - .int => @as(f64, @floatFromInt(divisor.int)), - .float => divisor.float, - .string => std.fmt.parseFloat(f64, divisor.string) catch 0.0, - else => 1.0, - }; - } - - const ratio = (float_value / float_max_value) * float_divisor; - - try writer.writeAll(std.fmt.allocPrint(alloc, "{d}", .{ratio}) catch "0"); - }, - else => {}, - } + const is_float = std.fmt.parseFloat(f64, value) catch |err| switch (err) { + error.InvalidCharacter => null, + }; + if (is_float != null) return Value{ .float = is_float.? }; + return Value{ .string = value }; } fn findChildBlock(self: *const Renderer, nodes: []parser.Node, name: []const u8) ?parser.BlockNode { @@ -496,35 +407,26 @@ pub const Renderer = struct { return null; } - fn toValue(self: *Context, value: anytype) RenderError!Value { - const T = @TypeOf(value); - return switch (@typeInfo(T)) { - .bool => Value{ .bool = value }, - .int, .comptime_int => Value{ .int = @intCast(value) }, - .float, .comptime_float => Value{ .float = @floatCast(value) }, - .pointer => Value{ .string = try std.fmt.allocPrint(self.allocator(), "{s}", .{value}) }, - .@"struct" => blk: { - var dict = std.StringHashMapUnmanaged(Value){}; - inline for (std.meta.fields(T)) |field| { - const field_val = @field(value, field.name); - const converted = try self.toValue(field_val); - try dict.put(self.allocator(), field.name, converted); - } - break :blk Value{ .dict = dict }; - }, - .array => blk: { - var list = try self.allocator().alloc(Value, value.len); - for (value, 0..) |item, i| { - list[i] = try self.toValue(item); - } - break :blk Value{ .list = list }; - }, - .optional => if (value) |v| try self.toValue(v) else .null, - .null => .null, - // CASO ESPECIAL: o valor já é um Value (ex: lista de Value) - .@"union" => if (T == Value) value else @compileError("Unsupported union type: " ++ @typeName(T)), - else => @compileError("Unsupported type: " ++ @typeName(T)), + fn escapeHtml(self: *const 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, "<"), + '>' => try result.appendSlice(self.allocator, ">"), + '"' => try result.appendSlice(self.allocator, """), + '\'' => try result.appendSlice(self.allocator, "'"), + else => try result.append(self.allocator, c), + } + } + + return Value{ .string = try result.toOwnedSlice(self.allocator) }; } fn valueToString(self: *const Renderer, alloc: Allocator, buf: *ArrayListUnmanaged(u8), value: Value) RenderError!void { diff --git a/src/renderer_test.zig b/src/renderer_test.zig index 17d2251..2c5a847 100644 --- a/src/renderer_test.zig +++ b/src/renderer_test.zig @@ -652,8 +652,25 @@ test "renderer - now" { try ctx.set("idade", Value{ .int = 20 }); const template = - // \\{% now \"Y-m-d H:i:s\" %} \\{% now %} + \\{% now \"Y-m-d H:i:s\" %} + \\{% now \"Y" %} + \\{% now \"m\" %} + \\{% now \"n\" %} + \\{% now \"d\" %} + \\{% now \"j\" %} + \\{% now \"F\" %} + \\{% now \"M\" %} + \\{% now \"l\" %} + \\{% now \"D\" %} + \\{% now \"H:i:s\" %} + \\{% now \"H\" %} + \\{% now \"G\" %} + \\{% now \"i\" %} + \\{% now \"s\" %} + \\{% now \"a\" %} + \\{% now \"A\" %} + \\{% now \"P\" %} ; var buf = std.ArrayList(u8){}; @@ -665,3 +682,389 @@ test "renderer - now" { // try testing.expect(std.mem.indexOf(u8, buf.items, "Maior") != null); } + +test "renderer - csrf_token in context" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("15 - csrf_token in context\n", .{}); + + const alloc = testing.allocator; + var ctx = Context.init(alloc); + defer ctx.deinit(); + + var cache = TemplateCache.init(alloc); + defer cache.deinit(); + + const renderer = Renderer.init(&ctx, &cache); + + const token: []const u8 = "zh5fyUSICjXNsDTtJCjl9A3O2dDSHhYFlIngAEO6PXK9NX56Z1XLEy7doYuPcE0u"; + + try ctx.set("csrf_token", token); + const template = + \\{% csrf_token %} + ; + + const expected = + \\ + ; + 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.expectEqualStrings(expected, buf.items); +} + +test "renderer - csrf_token not in context" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("16 - csrf_token not in context\n", .{}); + + const alloc = testing.allocator; + var ctx = Context.init(alloc); + defer ctx.deinit(); + + var cache = TemplateCache.init(alloc); + defer cache.deinit(); + + const renderer = Renderer.init(&ctx, &cache); + + const template = + \\{% csrf_token %} + ; + + const expected = + \\ + ; + 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.expectEqualStrings(expected, buf.items); +} + +// TODO: add parse filters to variables +test "renderer - firstof withtout fallback" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("17 - firstof without fallback\n", .{}); + + const alloc = testing.allocator; + 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("var1", ""); + try ctx.set("var2", "baz"); + + const template = + \\{% firstof var1 var2 %} + ; + + const expected = + \\baz + ; + 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.expectEqualStrings(expected, buf.items); +} + +test "renderer - firstof with fallback" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("18 - firstof with fallback\n", .{}); + + const alloc = testing.allocator; + 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("var1", ""); + try ctx.set("var2", 0); + + const template = + \\{% firstof var1 var2 "Oops no value!" %} + ; + + const expected = + \\Oops no value! + ; + 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.expectEqualStrings(expected, buf.items); +} + +test "renderer - firstof without value in context" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("19 - firstof without value in context\n", .{}); + + const alloc = testing.allocator; + var ctx = Context.init(alloc); + defer ctx.deinit(); + + var cache = TemplateCache.init(alloc); + defer cache.deinit(); + + const renderer = Renderer.init(&ctx, &cache); + + const template = + \\{% firstof 0 true "Oops no value!" %} + ; + + const expected = + \\true + ; + 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.expectEqualStrings(expected, buf.items); +} + +test "renderer - firstof missing vars" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("20 - firstof missing vars\n", .{}); + + const alloc = testing.allocator; + var ctx = Context.init(alloc); + defer ctx.deinit(); + + var cache = TemplateCache.init(alloc); + defer cache.deinit(); + + const renderer = Renderer.init(&ctx, &cache); + + const template = + \\{% firstof %} + ; + + const expected = + \\ + ; + 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.expectEqualStrings(expected, buf.items); +} + +test "renderer - firstof missing vars with fallback" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("21 - firstof missing vars with fallback\n", .{}); + + const alloc = testing.allocator; + var ctx = Context.init(alloc); + defer ctx.deinit(); + + var cache = TemplateCache.init(alloc); + defer cache.deinit(); + + const renderer = Renderer.init(&ctx, &cache); + + const template = + \\{% firstof "nothing here" %} + ; + + const expected = + \\nothing here + ; + 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.expectEqualStrings(expected, buf.items); +} + +test "renderer - lorem" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("22 - lorem\n", .{}); + + const alloc = testing.allocator; + var ctx = Context.init(alloc); + defer ctx.deinit(); + + var cache = TemplateCache.init(alloc); + defer cache.deinit(); + + const renderer = Renderer.init(&ctx, &cache); + + const template = + \\{% lorem %} + ; + + 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, "Lorem ipsum") != null); +} + +test "renderer - lorem with count and method words" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("23 - lorem with count and method words\n", .{}); + + const alloc = testing.allocator; + var ctx = Context.init(alloc); + defer ctx.deinit(); + + var cache = TemplateCache.init(alloc); + defer cache.deinit(); + + const renderer = Renderer.init(&ctx, &cache); + + const template = + \\{% lorem 3 w %} + ; + + 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.expectEqualStrings("lorem ipsum dolor", buf.items); +} + +test "renderer - lorem with count and method paragraphs" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("24 - lorem with count and method paragraphs\n", .{}); + + const alloc = testing.allocator; + var ctx = Context.init(alloc); + defer ctx.deinit(); + + var cache = TemplateCache.init(alloc); + defer cache.deinit(); + + const renderer = Renderer.init(&ctx, &cache); + + const template = + \\{% lorem 5 p %} + ; + + 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}); + + const qty = std.mem.count(u8, buf.items, ""); + try testing.expect(std.mem.indexOf(u8, buf.items, "Lorem ipsum dolor") != null); + try testing.expect(qty == 5); +} + +test "renderer - lorem only random" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("25 - lorem only random\n", .{}); + + const alloc = testing.allocator; + var ctx = Context.init(alloc); + defer ctx.deinit(); + + var cache = TemplateCache.init(alloc); + defer cache.deinit(); + + const renderer = Renderer.init(&ctx, &cache); + + const template = + \\{% lorem true %} + ; + + 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(buf.items.len > 0); +} + +test "renderer - lorem words random" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("26 - lorem words random\n", .{}); + + const alloc = testing.allocator; + var ctx = Context.init(alloc); + defer ctx.deinit(); + + var cache = TemplateCache.init(alloc); + defer cache.deinit(); + + const renderer = Renderer.init(&ctx, &cache); + + const template = + \\{% lorem 6 w true %} + ; + + 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}); + + const spaces = std.mem.count(u8, buf.items, " "); + try testing.expect(spaces == 5); +} + +test "renderer - lorem paragraphs random" { + std.debug.print("____________________________________________________\n", .{}); + std.debug.print("26 - lorem paragraphs random\n", .{}); + + const alloc = testing.allocator; + var ctx = Context.init(alloc); + defer ctx.deinit(); + + var cache = TemplateCache.init(alloc); + defer cache.deinit(); + + const renderer = Renderer.init(&ctx, &cache); + + const template = + \\{% lorem 3 p true %} + ; + + 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}); + + const spaces = std.mem.count(u8, buf.items, "
"); + try testing.expect(spaces == 3); +}