{{mustache}} templates for Zig.
✓ Comments {{! Mustache is awesome }}
.
✓ Custom delimiters {{=[ ]=}}
.
✓ Interpolation of common types, such as strings, enums, bools, optionals, pointers, integers, floats and JSON objects into {{variables}
.
✓ Unescaped interpolation with {{{tripple-mustache}}}
or {{&ersant}}
.
✓ Rendering sections {{#foo}} ... {{/foo}}
.
✓ Section iterator over slices, arrays and tuples {{slice}} ... {{/slice}}
.
✓ Rendering inverted sections {{^foo}} ... {{/foo}}
.
✓ Lambdas expansion.
✓ Rendering partials {{>file.html}}
.
☐ Rendering parents and blocks {{<file.html}}
and {{$block}}
.
✓ All implemented features passes the tests from mustache spec.
Render from strings, files and pre-loaded templates. See the source code for more details.
const std = @import("std");
const mustache = @import("mustache");
pub fn main() !void {
const template =
\\Hello {{name}} from Zig
\\Supported features:
\\{{#features}}
\\ - {{name}}
\\{{/features}}
;
var data = .{
.name = "friends",
.features = .{
.{ .name = "interpolation" },
.{ .name = "sections" },
.{ .name = "delimiters" },
.{ .name = "partials" },
},
};
const allocator = std.testing.allocator;
const result = try mustache.allocRenderText(allocator, template, data);
defer allocator.free(result);
try std.testing.expectEqualStrings(
\\Hello friends from Zig
\\Supported features:
\\ - interpolation
\\ - sections
\\ - delimiters
\\ - partials
\\
, result);
}
const std = @import("std");
const mustache = @import("mustache");
pub fn main() !void {
const template_text = "It's a comptime loaded template, with a {{value}}";
const comptime_template = comptime mustache.parseComptime(template_text, .{}, .{});
var data = .{
.value = "runtime value"
};
const allocator = std.testing.allocator;
const result = try mustache.allocRender(comptime_template, data);
defer allocator.free(result);
try std.testing.expectEqualStrings(
"It's a comptime loaded template, with a runtime value",
result,
);
}
const std = @import("std");
const mustache = @import("mustache");
pub fn main() !void {
const template = "Hello {{name}} from Zig";
const allocator = std.testing.allocator;
var parser = std.json.Parser.init(allocator, false);
defer parser.deinit();
// Parsing an arbitrary json string
var json = try parser.parse(
\\{
\\ "name": "friends"
\\}
);
defer json.deinit();
const result = try mustache.allocRenderText(allocator, template, json);
defer allocator.free(result);
try std.testing.expectEqualStrings("Hello friends from Zig" , result);
}
Mustache-zig exports a FFI interface to be consumed by other languages
For more details:
There is no "one size fits all", but the mustache-zig API is intended to provide great flexibility to cover many use cases.
There are some benchmark tests inspired by the excellent Ramhorns's benchmarks, comparing the performance of most popular Rust template engines.
-
Rendering to a new allocated string 1 million times
Total time ns/iter MB/s Ramhorns 0.14.0 0,040s 40 ns 2425 MB/s Askama 0.9 0,136s 136 ns 713 MB/s Tera 1.2.0 0,308s 308 ns 314 MB/s Mustache 0.9 0,363s 363 ns 267 MB/s Handlebars 3.1.0-beta.2 1,833s 1,833 ns 52 MB/s -
Parsing a template 1 million times
Total time ns/iter MB/s Ramhorns 0.14.0 0,317s 317 ns 492 MB/s Mustache 0.9 5,863s 5,863 ns 26 MB/s Handlebars 3.1.0-beta.2 11,797s 11,797 ns 13 MB/s
*All benchmarks were executed using cargo bench
on a Intel i7-1185G7 @ 3.00GHz, Linux kernel 5.17
For comparision with mustache-zig, refer to "Rendering to a new allocated string 1 million times" and "Parsing a template 1 million times" sections bellow.
The same benchmark was implemented in Zig for both mustache-zig and Zig's std.fmt
.
We can assume that Zig's std.fmt
is the fastest possible way to render a simple string using Zig. This benchmark shows how much slower a mustache template is rendered when compared with the same template rendered by Zig's std.fmt
.
-
Rendering to a pre-allocated buffer 1 million times
Total time ns/iter MB/s Penality Zig fmt 0.042s 42 ns 2596 MB/s -- mustache-zig 0.094s 94 ns 1149 MB/s 2.260x slower -
Rendering to a new allocated string 1 million times
Total time ns/iter MB/s Penality Zig fmt 0.058s 58 ns 1869 MB/s -- mustache-zig 0.167s 167 ns 645 MB/s 2.897x slower -
Rendering to a local file 1 million times
Total time ns/iter MB/s Penality Zig fmt 0.079s 79 ns 1367 MB/s -- mustache-zig 0.125s 125 ns 862 MB/s 1.586x slower -
Parsing a template 1 million times
Total time ns/iter MB/s mustache-zig 1.380s 1,380 ns 182 MB/s
*All benchmarks were compiled as ReleaseSafe, and executed on a Intel i7-1185G7 @ 3.00GHz, Linux kernel 5.17
Mustache templates are well known for HTML templating, but it's useful to render any kind of dynamic document, and potentially load templates from untrusted or user-defined sources.
So, it's also important to be able to deal with multi-megabyte inputs without eating all your RAM.
// 32KB should be enough memory for this job
// 16KB if we don't need to support lambdas 😅
var plenty_of_memory = std.heap.GeneralPurposeAllocator(.{ .enable_memory_limit = true }){
.requested_memory_limit = 32 * 1024,
};
defer _ = plenty_of_memory.deinit();
try mustache.renderFile(plenty_of_memory.allocator(), "10MB_file.mustache", ctx, out_writer);
-
MIT
-
Mustache is Copyright (C) 2009 Chris Wanstrath Original CTemplate by Google