Git Product home page Git Product logo

mustache-zig's Introduction

MUSTACHE-ZIG

{{mustache}} templates for Zig.

made with Zig Matrix Build codecov license mit

logo

! Under development !

Features

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 {{&ampersant}}.

✓ 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}}.

Full spec compliant

✓ All implemented features passes the tests from mustache spec.

Examples

Render from strings, files and pre-loaded templates. See the source code for more details.

Runtime parser

const std = @import("std");
const mustache = @import("mustache");

pub fn main() !void {
    const template =
        \\Hello {{name}} from Zig
        \\Supported features:
        \\{{#features}}
        \\  - {{name}}
        \\{{/features}}
    ;

    const 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);
}

Comptime parser

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, .{}, .{});
    
    const Data = struct { value: []const u8 };
    const data: 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,
    );
}

JSON support

const std = @import("std");
const mustache = @import("mustache");

pub fn main() !void {
    const allocator = std.testing.allocator;

    // Parsing an arbitrary (dynamic) json string:
    const json_source =
        \\{
        \\   "name": "friends"
        \\}
    ;    
    var json = try std.json.parseFromSlice(
        std.json.Value,
        allocator,
        json_source,
        .{},
    );
    defer json.deinit();

    const template = "Hello {{name}} from Zig";
    const result = try mustache.allocRenderText(allocator, template, json);
    defer allocator.free(result);

    try std.testing.expectEqualStrings("Hello friends from Zig" , result);
}

FFI Interface

Mustache-zig exports a FFI interface to be consumed by other languages

For more details:

Customizable use

There is no "one size fits all", but the mustache-zig API is intended to provide great flexibility to cover many use cases.

diagram

Benchmarks.

There are some benchmark tests inspired by the excellent Ramhorns's benchmarks, comparing the performance of most popular Rust template engines.

  1. 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
  2. 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.

Mustache vs Zig's fmt

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.

  1. 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
  2. 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
  3. 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
  4. 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

Memory benchmarks

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);

Licensing

  • MIT

  • Mustache is Copyright (C) 2009 Chris Wanstrath Original CTemplate by Google

mustache-zig's People

Contributors

batiati avatar bobf avatar f-fr avatar jared-miller avatar mfashby avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

mustache-zig's Issues

example

Could you add an example how to use it from zig 0.11 as dynamic link library?

stage3: tuple does not work with funciton pointer

mustache-zig: stage3
zig: 0.11.0-dev.1636+47e14b7ff

> zig build run
error: _bug...
error: The following command terminated unexpectedly:
const std = @import("std");
const mustache = @import("mustache-zig/src/mustache.zig");

pub const Server = struct {
    handler_fns: std.StringHashMap(HandlerFn),

    pub const HandlerFn = *const fn(Request) void;

    fn handle(self: *Server) !void {
        var req = Request{ .path = "/test"};
        if (self.handler_fns.get(req.path)) |f| f(req);
    }
};

pub const Request = struct {
    path: []const u8 = undefined,
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer std.debug.assert(!gpa.deinit());
    const allocator = gpa.allocator();
    
    var fns = std.StringHashMap(Server.HandlerFn).init(allocator);
    try fns.put("/test", rqTest);
    defer fns.deinit();

    var srv = Server{.handler_fns = fns};
	
    try srv.handle();
}

fn rqTest(req: Request) void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer std.debug.assert(!gpa.deinit());
    const allocator = gpa.allocator();
	
    // const Ctx = struct {name: []const u8};
    // const ctx: Ctx = .{.name = req.path};
    const ctx = .{.name = req.path};
    
    const html = mustache.allocRenderText(allocator, "Hello {{name}} from Zig", ctx) catch return;
    defer allocator.free(html);
    
    std.debug.print("{s}\n", .{html});
}

Not sure since I'm new to zig, is this a problem of the zig compiler?

Review comptime options

There are a lot of comptime options, some of them still not implemented, poorly named, or not so obvious.
Some options could be visible internally only.

Example:

pub const Features = struct {
    allow_redefining_delimiters: bool = true,
    preseve_line_breaks_and_indentation: bool = true,
    lambdas: Lambdas = .{ .enabled = .{} },
};

Here we can keep track of all comptime options, and the current status:

Option Meaning Status
ParseTextOptions.copy_strings Parsed templates should dupe all strings or just take slices from the template source Implemented
ParseFileOptions.read_buffer_size Buffer size of the streamed parser. Implemented, but it could have a better name
Features.allow_redefining_delimiters Allows redefining the delimiters through the tags '{{=' and '=}}' Not implemented
Features.preseve_line_breaks_and_indentation Follow strict mustache's rules about blank spaces and indentation Implemented, but it could have a better name
Lambdas.disabled Disable lambdas support, which means, there is no need to store the tag's content Implemented
Lambdas..enabled.max_recursion When enabled, prevents recursive lambda expansion Not implemented
ContextMisses.empty or fail Behaviour when interpolating an unknown variable Not implemented, defaults to "empty" always

Does this handle recursive partials?

Does this handle recursive partials?

Thanks!

Steve

const template_html_test =
    \\ <div>   
    \\   {{#menu_data.menu}}
    \\     {{>menu}}
    \\   {{/menu_data.menu}}   
    \\ </div>
;
const menu_partials_text = .{
    .{
        "menu",
        "<a href=\"{{link}}\">{{name}}</a>{{>menu}}",
    },
};

const result = try mustache.allocRenderTextPartials(testing.allocator, template_html_test, menu_partials_text, context);

Use lambda in a JSON context

Hi,

I tried to build this kind of struct to use lambdas with std.json.Value:

const std = @import("std");
const mustache = @import("mustache");

const Native = struct {
    values: std.json.Value,

    pub fn myAwesomeLambda(self: Native, context: mustache.LambdaContext) !void {
        // here I am doing awesome things with Native.values
    }
}

When I give my Native object as data to the renderAllocText function, it fails with this error:

src/rendering/context.zig:86:9: error: Unexpect context_source: json.dynamic.Value of type [json] and context_source == [native]

Which is undeniably correct.

However I am wondering if there are any tips to use lambdas in a JSON context ?
If not, do you plan something like this ?
If not, will you accept a PR implementing this feature ?
If yes, could you give me some tips to make it possible ? Something like how you see the implementation with more or less details. I will be happy to contribute to this awesome project.

Thank you.

Improve parser errors

Parser errors are very limited, and they have few test cases.
There are some missing features in parser errors:

  • Closing tags are not checked, you can open a tag with triple-mustaches and close with regular ones {{{invalid}}
  • Define the correct behavior when dealing with triple-mustaches and custom delimiters, for now, any delimiter + { is recognized as a triple-mustache
  • Improve error messages, maybe by printing parser errors on the output writer when rendering in the streamed mode and comptime mode.

Windows Support

Hi, I'm excited to use this library in my Zig project.
Is there an estimate for how long it would take to implement full Windows support?

UTF-16 support

Should be possible to read a template from a UTF-16 or "wide char" source, currently, only 8bits chars are supported.

  • Support UTF-16 for both parsing and rendering would save some work spent on conversions.
  • Interpret the BOM when reading from a file

Decimal Interpolation

We fail with this test from mustaches spec

- name: Basic Decimal Interpolation
    desc: Decimals should interpolate seamlessly with proper significance.
    data: { power: 1.210 }
    template: '"{{power}} jiggawatts!"'
    expected: '"1.21 jiggawatts!"'

Suggestions:

  • Implement a custom formatter for floats
  • Introduce a new configuration for max float precision, preventing from rendering things like 1.21000345698
  • Maybe waiting for Ryu floating point printing

Build fails

I am trying to build the library, in the git root I run zig build and get the following output

src/ffi/extern_types.zig:51:10: error: extern structs cannot contain fields of type
'?fn(?*const anyopaque, *const ffi.extern_types.Path, *ffi.extern_types.UserData) ca
llconv(.C) ffi.extern_types.PathResolution'
    get: ?fn (user_data_handle: UserDataHandle, path: *const Path, out_value: *UserD
ata) callconv(.C) PathResolution,
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/ffi/extern_types.zig:51:10: note: only pointer like optionals are extern compati
ble
referenced by:
    UserData: src/ffi/extern_types.zig:49:29
    UserData: src/ffi/extern_types.zig:51:80
    remaining reference traces hidden; use '-freference-trace' to see all reference
traces

src/parsing/parser.zig:140:68: error: expected type '*parsing.text_part.TextPart(.{.
source = .{ .string = .{.copy_strings = true} }, .output = .cache, .features = .{.al
low_redefining_delimiters = true, .preseve_line_breaks_and_indentation = true, .lamb
das = .{ .enabled = .{ ... } }}

Incidently I am trying to also get it working as a package, how can I statically link the library into my zig project ( can they be built a long side one another ? ). Sorry I am new to zig :).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.