//! Small HTML template response values and rendering.
//! This is intentionally not a general-purpose template language.
const std = @import("std");
const http = @import("http.zig");
const HeaderField = http.HeaderField;
const Status = http.Status;
/// One named template value.
pub const TemplateValue = struct {
name: []const u8,
value: []const u8,
escape: bool = true,
};
/// Creates an HTML-escaped template value.
pub fn template(name: []const u8, value: []const u8) TemplateValue {
return .{ .name = name, .value = value };
}
/// Creates a trusted, unescaped HTML template value.
pub fn templateHtml(name: []const u8, value: []const u8) TemplateValue {
return .{ .name = name, .value = value, .escape = false };
}
/// A template response rendered before the handler arguments are released.
pub const Template = struct {
path: []const u8,
dir: std.Io.Dir = .cwd(),
/// Borrowed values that must outlive the handler call.
context: []const TemplateValue = &.{},
status: ?Status = null,
content_type: []const u8 = "text/html; charset=utf-8",
headers: []const HeaderField = &.{},
max_size: std.Io.Limit = .limited(16 * 1024 * 1024),
};
/// Renders a template source into a writer.
pub fn render(writer: *std.Io.Writer, source: []const u8, context: []const TemplateValue) !void {
var index: usize = 0;
while (index < source.len) {
const open = std.mem.indexOfPos(u8, source, index, "{{") orelse {
try writer.writeAll(source[index..]);
return;
};
try writer.writeAll(source[index..open]);
const raw = open + 2 < source.len and source[open + 2] == '{';
const name_start = open + if (raw) @as(usize, 3) else 2;
const close_marker = if (raw) "}}}" else "}}";
const close = std.mem.indexOfPos(u8, source, name_start, close_marker) orelse return error.InvalidTemplate;
const raw_name = source[name_start..close];
const name = std.mem.trim(u8, raw_name, " \t\r\n");
if (name.len == 0) return error.InvalidTemplate;
const value = valueFor(context, name) orelse return error.UnknownTemplateValue;
if (raw or !value.escape) {
try writer.writeAll(value.value);
} else {
try writeHtmlText(writer, value.value);
}
index = close + close_marker.len;
}
}
fn valueFor(context: []const TemplateValue, name: []const u8) ?TemplateValue {
for (context) |value| {
if (std.mem.eql(u8, value.name, name)) return value;
}
return null;
}
fn writeHtmlText(writer: *std.Io.Writer, value: []const u8) !void {
for (value) |ch| {
switch (ch) {
'&' => try writer.writeAll("&"),
'<' => try writer.writeAll("<"),
'>' => try writer.writeAll(">"),
else => try writer.writeByte(ch),
}
}
}