Rust-Handlebars rust: Handlebars templating language implemented in Rust and for Rust.

handlebars-rust

Handlebars templating language implemented in Rust and for Rust.

Handlebars-rust is the template engine that renders the official Rust website rust-lang.org, its book.

Build Status MIT licensed Docs Donate Donate

Getting Started

Quick Start

extern crate handlebars;
#[macro_use]
extern crate serde_json;

use handlebars::Handlebars;

fn main() -> Result<(), Box<dyn Error>> {
    let mut reg = Handlebars::new();
    // render without register
    println!(
        "{}",
        reg.render_template("Hello {{name}}", &json!({"name": "foo"}))?
    );

    // register template using given name
    reg.register_template_string("tpl_1", "Good afternoon, {{name}}")?;
    println!("{}", reg.render("tpl_1", &json!({"name": "foo"}))?);
    Ok(())
}

Code Example

If you are not familiar with handlebars language syntax, it is recommended to walk through their introduction first.

Check the render example in the source tree. The example shows you how to:

  • Create a Handlebars registry and register the template from files;
  • Create a custom Helper with closure or struct implementing HelperDef, and register it;
  • Define and prepare some data;
  • Render it;

Run cargo run --example render to see results (or RUST_LOG=handlebars=info cargo run --example render for logging output).

Checkout examples/ for more concrete demos of the current API.

Minimum Rust Version Policy

Handlebars will track Rust nightly and stable channel. When dropping support for previous stable versions, I will bump minor version and clarify in CHANGELOG.

Rust compatibility table

Handlebars version range Minimum Rust version
~3.0.0 1.32
~2.0.0 1.32
~1.1.0 1.30
~1.0.0 1.23

Document

Rust doc.

Changelog

Changelog is available in the source tree named as CHANGELOG.md.

Contributor Guide

Any contribution to this library is welcomed. To get started into development, I have several Help Wanted issues, with the difficulty level labeled. When running into any problem, feel free to contact me on github.

I'm always looking for maintainers to work together on this library, let me know (via email or anywhere in the issue tracker) if you want to join.

Donations

I'm now accepting donations on liberapay and buymeacoffee if you find my work helpful and want to keep it going.

buymeacoffee

Why (this) Handlebars?

Handlebars is a real-world templating system that you can use to build your application without pain.

Features

Isolation of Rust and HTML

This library doesn't attempt to use some macro magic to allow you to write your template within your rust code. I admit that it's fun to do that but it doesn't fit real-world use cases.

Limited but essential control structures built-in

Only essential control directives if and each are built-in. This prevents you from putting too much application logic into your template.

Extensible helper system

You can write your own helper with Rust! It can be a block helper or inline helper. Put your logic into the helper and don't repeat yourself.

A helper can be as a simple as a Rust function like:

handlebars_helper!(hex: |v: i64| format!("0x{:x}", v));

/// register the helper
handlebars.register_helper("hex", Box::new(hex));

And using it in your template:

{{hex 16}}

With script_helper feature flag enabled, you can also create helpers using rhai script, just like JavaScript for handlebars-js. This feature was in early stage. Its API was limited at the moment, and can change in future.

Template inheritance

Every time I look into a templating system, I will investigate its support for template inheritance.

Template include is not sufficient for template reuse. In most cases you will need a skeleton of page as parent (header, footer, etc.), and embed your page into this parent.

You can find a real example of template inheritance in examples/partials.rs and templates used by this file.

WebAssembly compatible

Handlebars 3.0 can be used in WebAssembly projects.

Related Projects

Web frameworks

Adopters

The adopters page lists projects that uses handlebars for part of their functionalities.

Extensions

The extensions page has libraries that provide additional helpers, decorators and outputs to handlebars-rust, and you can use in your own projects.

License

This library (handlebars-rust) is open sourced under the MIT License.

Comments

  • Add a hint about deserializing helper params
    Add a hint about deserializing helper params

    Mar 9, 2018

    This addition adds a note about the easy possiblity to work with helper parameters in their original type representation by using Deserialize.

    Reply
  • stripping lines that are
    stripping lines that are "standalone" helpers

    Apr 3, 2019

    In the handlebars.js docs the following is described (https://handlebarsjs.com/expressions.html):

    This expands the default behavior of stripping lines that are "standalone" helpers (only a block helper, comment, or partial and whitespace).
    

    They give the example:

    {{#each nav}}
      <a href="{{url}}">
        {{#if test}}
          {{title}}
        {{^}}
          Empty
        {{/if}}
      </a>
    {{~/each}}
    

    That will render as

    <a href="foo">
        bar
    </a>
    <a href="bar">
        Empty
    </a>
    

    Thus rendering the line {{#if test}} as nothing. Currenly on handlebars.rs:

    ----------
    {{#if body}}
    {{ body }}
    {{/if}}
    

    renders as

    ----------
    
    body here
    

    (notice the newline between '---' and 'body here'.) Is it possible to ignore those lines?

    enhancement 
    Reply
  • Stripping newlines for helper tag
    Stripping newlines for helper tag

    Apr 6, 2019

    Fixes #258

                                                                                                                                                                                                           
    Reply
  • Index access support for String
    Index access support for String

    May 18, 2019

    At the moment, we only support index access for Array. String is also accessible via number index. Given s = "hello", the expression s.[0] should output h.

    enhancement 
    Reply
  • What to use instead of Context::navigate now?
    What to use instead of Context::navigate now?

    Jun 9, 2020

    I want to upgrade from v1 to v3 of this crate. Inside a datetime helper, I have the following code:

    let timezone = ctx
        .navigate(".", &VecDeque::new(), "timezone")?
        .ok_or_else(|| RenderError::new("timezone is missing in context"))?;
    

    Is this the recommended way to do this now, or is there a more convenient way (not having to match Value::Object)?

    let timezone = match ctx.data() {
        Value::Object(map) => map.get("timezone"),
        _ => None,
    }
    .ok_or_else(|| RenderError::new("timezone is missing in context"))?;
    

    2: Why does Handlebars need a lifetime now?

    question 
    Reply
  • Suggestion: Buffered file reading in `register_template_file`
    Suggestion: Buffered file reading in `register_template_file`

    Jun 15, 2020

    Currently it reads the file unbuffered: https://docs.rs/handlebars/3.1.0/src/handlebars/registry.rs.html#207

    For buffered reading, it could either wrap the File in a BufReader or just call std::fs::read_to_string followed by register_template_string.

    enhancement 
    Reply
  • update some debs for minimal-versions
    update some debs for minimal-versions

    Aug 26, 2018

    This bumps the minimal acceptable versions in Cargo.toml to versions that are compatible with -Z minimal-versions. This is part of the process of seeing how hard this is for crates to use in preparation for getting it stabilized for use in CI, specifically upstreaming the changes required to get criterion working with it. It is easy to use if all of your dependencies support it, but much harder if trying to impose it on them.

    Also cargo defaults to interpreting version requirements as ^. So this switched to the shorter form.

    Reply
  • Consider using rustc_serialize::json::encode to obtain json
    Consider using rustc_serialize::json::encode to obtain json

    Apr 12, 2015

    Rather than force every structure to implement the ToJson trait, it might be preferable to allow users to just add a #[derive(RustcEncodable)] on their structs, and handlebars can obtain a JSON value via the rustc_serialize::json::encode function.

    Reply
  • Feature/serde feature
    Feature/serde feature

    Dec 16, 2015

    Fixes #50

    Added a feature flag to use serde_json as type system, while keeping rustc_serialize as default implementation. Backward compatible.

    Reply
  • Segfault in Handlebars
    Segfault in Handlebars

    Aug 28, 2017

    I just experienced a segfault which seems to be originated in the handlebars code.

    As there are ~6000 stack levels, it seems that I ended up crashing into the heap by recursing too deep. The recursion seems to happen in handlebars, as only a few (as in below 10) stack levels are from my code.

    The corrosponding code is here, here is the causing call.

    The templates can be found here, the registration of the templates and the helpers is here. If you have any questions regarding the code, feel free to ask me, I know the codebase is pretty big and maybe confusing for some people.


    To reproduce:

    • Clone the repository, checkout the branch from the PR
    • cargo build --manifest-path bin/core/imag-store/Cargo.toml # takes some time
    • mkdir /tmp/store/
    • ./target/debug/imag-store --rtp /tmp/ --config imagrc.toml create -p test entry -c foo
    • enjoy.

    The last few lines of the stacktrace follow


    #6274 0x00005555556808c2 in core::result::Result<&handlebars::template::Template, handlebars::error::RenderError>::and_then<&handlebars::template::Template,handlebars::error::RenderError,(),closure> (self=Ok = {...}, op=closure = {...})
        at /build/rustc-1.17.0-src/src/libcore/result.rs:601
    #6275 0x00005555555f8b18 in handlebars::registry::Registry::renderw<collections::btree::map::BTreeMap<&str, collections::string::String>> (self=0x7ffff6ab9038, name="DEBUG", data=0x7fffffff5578, writer=&mut Write)
        at /home/m/.cargo/registry/src/github.com-1ecc6299db9ec823/handlebars-0.29.0/src/registry.rs:247
    #6276 0x00005555555f8654 in handlebars::registry::Registry::render<collections::btree::map::BTreeMap<&str, collections::string::String>> (self=0x7ffff6ab9038, name="DEBUG", data=0x7fffffff5578)
        at /home/m/.cargo/registry/src/github.com-1ecc6299db9ec823/handlebars-0.29.0/src/registry.rs:236
    #6277 0x00005555556f75c4 in libimagrt::logger::{{impl}}::log (self=0x7ffff6ab9000, record=0x7fffffff5a50) at /home/m/archive/development/rust/imag/lib/core/libimagrt/src/logger.rs:149
    #6278 0x0000555555e16a17 in log::__log (level=log::LogLevel::Debug, target="handlebars::render", loc=0x5555562f06a8 <<handlebars::template::TemplateElement as handlebars::render::Renderable>::render::_LOC>, args=Arguments = {...})
        at /home/m/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.3.8/src/lib.rs:903
    #6279 0x0000555555ae6784 in handlebars::render::{{impl}}::render (self=0x7ffff6a35400, registry=0x7ffff6ab9038, rc=0x7fffffff7038) at /home/m/.cargo/registry/src/github.com-1ecc6299db9ec823/handlebars-0.29.0/src/render.rs:641
    #6280 0x0000555555ae5e41 in handlebars::render::{{impl}}::render (self=0x7ffff6a37718, registry=0x7ffff6ab9038, rc=0x7fffffff7038) at /home/m/.cargo/registry/src/github.com-1ecc6299db9ec823/handlebars-0.29.0/src/render.rs:590
    #6281 0x00005555555f8f6e in handlebars::registry::{{impl}}::renderw::{{closure}}<collections::btree::map::BTreeMap<&str, collections::string::String>> (t=0x7ffff6a37718) at /home/m/.cargo/registry/src/github.com-1ecc6299db9ec823/handlebars-0.29.0/src/registry.rs:254
    #6282 0x00005555556808c2 in core::result::Result<&handlebars::template::Template, handlebars::error::RenderError>::and_then<&handlebars::template::Template,handlebars::error::RenderError,(),closure> (self=Ok = {...}, op=closure = {...})
        at /build/rustc-1.17.0-src/src/libcore/result.rs:601
    #6283 0x00005555555f8b18 in handlebars::registry::Registry::renderw<collections::btree::map::BTreeMap<&str, collections::string::String>> (self=0x7ffff6ab9038, name="DEBUG", data=0x7fffffff79d8, writer=&mut Write)
        at /home/m/.cargo/registry/src/github.com-1ecc6299db9ec823/handlebars-0.29.0/src/registry.rs:247
    #6284 0x00005555555f8654 in handlebars::registry::Registry::render<collections::btree::map::BTreeMap<&str, collections::string::String>> (self=0x7ffff6ab9038, name="DEBUG", data=0x7fffffff79d8)
        at /home/m/.cargo/registry/src/github.com-1ecc6299db9ec823/handlebars-0.29.0/src/registry.rs:236
    #6285 0x00005555556f75c4 in libimagrt::logger::{{impl}}::log (self=0x7ffff6ab9000, record=0x7fffffff7eb0) at /home/m/archive/development/rust/imag/lib/core/libimagrt/src/logger.rs:149
    #6286 0x0000555555e16a17 in log::__log (level=log::LogLevel::Debug, target="handlebars::render", loc=0x5555562f06a8 <<handlebars::template::TemplateElement as handlebars::render::Renderable>::render::_LOC>, args=Arguments = {...})
        at /home/m/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.3.8/src/lib.rs:903
    #6287 0x0000555555ae6784 in handlebars::render::{{impl}}::render (self=0x7ffff6a35400, registry=0x7ffff6ab9038, rc=0x7fffffff9498) at /home/m/.cargo/registry/src/github.com-1ecc6299db9ec823/handlebars-0.29.0/src/render.rs:641
    #6288 0x0000555555ae5e41 in handlebars::render::{{impl}}::render (self=0x7ffff6a37718, registry=0x7ffff6ab9038, rc=0x7fffffff9498) at /home/m/.cargo/registry/src/github.com-1ecc6299db9ec823/handlebars-0.29.0/src/render.rs:590
    #6289 0x00005555555f8f6e in handlebars::registry::{{impl}}::renderw::{{closure}}<collections::btree::map::BTreeMap<&str, collections::string::String>> (t=0x7ffff6a37718) at /home/m/.cargo/registry/src/github.com-1ecc6299db9ec823/handlebars-0.29.0/src/registry.rs:254
    #6290 0x00005555556808c2 in core::result::Result<&handlebars::template::Template, handlebars::error::RenderError>::and_then<&handlebars::template::Template,handlebars::error::RenderError,(),closure> (self=Ok = {...}, op=closure = {...})
        at /build/rustc-1.17.0-src/src/libcore/result.rs:601
    #6291 0x00005555555f8b18 in handlebars::registry::Registry::renderw<collections::btree::map::BTreeMap<&str, collections::string::String>> (self=0x7ffff6ab9038, name="DEBUG", data=0x7fffffff9e38, writer=&mut Write)
        at /home/m/.cargo/registry/src/github.com-1ecc6299db9ec823/handlebars-0.29.0/src/registry.rs:247
    ---Type <return> to continue, or q <return> to quit---
    #6292 0x00005555555f8654 in handlebars::registry::Registry::render<collections::btree::map::BTreeMap<&str, collections::string::String>> (self=0x7ffff6ab9038, name="DEBUG", data=0x7fffffff9e38)
        at /home/m/.cargo/registry/src/github.com-1ecc6299db9ec823/handlebars-0.29.0/src/registry.rs:236
    #6293 0x00005555556f75c4 in libimagrt::logger::{{impl}}::log (self=0x7ffff6ab9000, record=0x7fffffffa310) at /home/m/archive/development/rust/imag/lib/core/libimagrt/src/logger.rs:149
    
    #6294 0x0000555555e16a17 in log::__log (level=log::LogLevel::Debug, target="libimagrt::runtime", loc=0x5555562e92f0 <libimagrt::runtime::Runtime::_new::_LOC>, args=Arguments = {...}) at /home/m/.cargo/registry/src/github.com-1ecc6299db9ec823/log-0.3.8/src/lib.rs:903
    #6295 0x00005555555d4d34 in libimagrt::runtime::Runtime::_new<clap::app::App> (cli_app=App = {...}, matches=ArgMatches = {...}, Python Exception <type 'exceptions.ValueError'> invalid literal for int() with base 0: '0x1 <error: Cannot access memory at address 0x1>': 
    config=...) at <log macros>:7
    #6296 0x00005555555d4319 in libimagrt::runtime::Runtime::new<clap::app::App> (cli_app=App = {...}) at /home/m/archive/development/rust/imag/lib/core/libimagrt/src/runtime.rs:97
    #6297 0x00005555555d360d in libimagrt::setup::generate_runtime_setup<fn(clap::app::App) -> clap::app::App> (name="imag-store", version="0.4.0", about="Direct interface to the store. Use with great care!", builder=0x0)
        at /home/m/archive/development/rust/imag/lib/core/libimagrt/src/setup.rs:39
    #6298 0x00005555555e642c in imag_store::main () at /home/m/archive/development/rust/imag/bin/core/imag-store/src/main.rs:71
    
    Reply
  • added a markdown helper
    added a markdown helper

    Nov 8, 2015

    I know this is probably just the beginning but I think I added a working markdown helper so that if you have a variable x that is a string containing markdown, then you can parse it to html by passing

    {{markdown x}}
    

    in the template..

    I want to improve this by adding some kind of environment, but I couldn't quite figure it out right away (and the use case above is the one that i want to use anyway). As a second commit perhaps something like below could be implemented

    {{#markdown}}
    #wow
    {{> sub_template}}
    {{~markdown}}
    

    edit: also sorry that this changed Cargo.toml so much. I used cargo add pulldown-cmark and it must have mangled the Cargo.toml I will go back and edit by hand

    Reply
  • Pest parser
    Pest parser

    Jun 11, 2016

    Fixes #81

    I decided to use pest to replace current custom parser for maintainability and correctness.

    Tasks:

    • [x] Grammar
    • [x] Rebuild parser with pest Rdp.queue() results
    • [x] Keeping error tracking: report template error with line/column number
    • [x] Keeping template element maps, line/column number for each item
    • [x] Retain leading whitespaces in text
    Reply