Skip to content

jamboree/bustache

master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
doc
 
 
 
 
src
 
 
 
 
 
 

{{ bustache }}

C++20 implementation of {{ mustache }}, compliant with spec v1.1.3.

Dependencies

  • fmt (or C++20 <format>)

Optional Dependencies

Supported Features

  • Variables
  • Sections
  • Inverted Sections
  • Comments
  • Partials
  • Set Delimiter
  • Lambdas
  • HTML escaping (configurable)
  • Template inheritance (extension)

Other Features

  • Customizable behavior on unresolved variable
  • Trait-based user-defined model
  • Variable format string, e.g. {{var:*^10}}.
  • List expansion section, e.g. {{*map}}({{key}} -> {{value}}){{/map}}.
  • Filter section, e.g. {{?filter}}...{{/filter}}.

Basics

{{ mustache }} is a template language for text-replacing. When it comes to formatting, there are 2 essential things -- Format and Data. {{ mustache }} also allows an extra lookup-context for Partials. In {{ bustache }}, we represent the Format as a bustache::format object, and Data and Partials can be anything that implements the required traits.

Quick Example

bustache::format format{"{{mustache}} templating"};
std::unordered_map<std::string, std::string> data{{"mustache", "bustache"}};
std::cout << format(data); // should print "bustache templating"

Manual

Data Model

{{ bustache }} doesn't required a fixed set of predefined data types to be used as data model. Instead, any type can be used as data model. Most STL-compatible containers will work out-of-the-box, including the ones that you defined yourself!

Header

#include <bustache/model.hpp>

Model Traits

To meet the Model concept, you have to implement the traits:

template<>
struct bustache::impl_model<T>
{
    static constexpr model kind;
};

where model can be one of the following:

  • model::atom
  • model::object
  • model::list
// Required by model::atom.
template<>
struct bustache::impl_test<T>
{
    static bool test(T const& self);
};

// Required by model::atom.
template<>
struct bustache::impl_print<T>
{
    static void print(T const& self, output_handler os, char const* fmt);
};

// Required by model::object.
template<>
struct bustache::impl_object<T>
{
    static void get(T const& self, std::string const& key, value_handler visit);
};

// Required by model::list.
template<>
struct bustache::impl_list<T>
{
    static bool empty(T const& self);
    static void iterate(T const& self, value_handler visit);
};

See udt.cpp for more examples.

Compatible Trait

Some types cannot be categorized into a single model (e.g. variant), to make it compatible, you can implement the trait:

template<>
struct bustache::impl_compatible<T>
{
    static value_ptr get_value_ptr(T const& self);
};

See model.hpp for example.

Format Object

bustache::format parses in-memory string into AST.

Header

#include <bustache/format.hpp>

Synopsis

Constructors

explicit format(std::string_view source); // [1]
format(std::string_view source, bool copytext); // [2]
format(ast::document doc, bool copytext); // [3]
  • Version 1 doesn't hold the text, you must ensure the source is valid and not modified during its use.
  • Version 2~3, if copytext == true the text will be copied into the internal buffer.

Manipulator

A manipulator combines the format & data and allows you to specify some options.

template<class T>
manipulator</*unspecified*/> format::operator()(T const& data) const;

// Specify the context for partials.
template<class T>
manipulator</*unspecified*/> manipulator::context(T const&) const noexcept;

// Specify the escape action.
template<class T>
manipulator</*unspecified*/> manipulator::escape(T const&) const noexcept;

Render API

render can be used for customized output.

Header

#include <bustache/render.hpp>

template<class Sink, class Escape = no_escape_t>
void render
(
    Sink const& os, format const& fmt, value_ref data,
    context_handler context = no_context_t{}, Escape escape = {},
    unresolved_handler f = nullptr
);

Context Handler

The context for partials can be any callable that meets the signature:

(std::string const& key) -> format const*;

Unresolved Handler

The unresolved handler can be any callable that meets the signature:

(std::string const& key) -> value_ptr;

Sink (Output Handler)

The sink can be any callable that meets the signature:

(char const* data, std::size_t count) -> void;

Escape Action

The escape action can be any callable that meets the signature:

template<class OldSink>
(OldSink const& sink) -> NewSink;

There're 2 predefined actions: no_escape (default) and escape_html, if no_escape is chosen, there's no difference between {{Tag}} and {{{Tag}}}, the text won't be escaped in both cases.

Stream-based Output

Output directly to the std::basic_ostream.

Header

#include <bustache/render/ostream.hpp>

Synopsis

template<class CharT, class Traits, class Escape = no_escape_t>
void render_ostream
(
    std::basic_ostream<CharT, Traits>& out, format const& fmt,
    value_ref data, context_handler context = no_context_t{},
    Escape escape = {}, unresolved_handler f = nullptr
);

template<class CharT, class Traits, class... Opts>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& out, manipulator<Opts...> const& manip)

Example

// Create format from source.
bustache::format format(...);
// Create the data we want to output.
my_data data{...};
// Create the context for Partials.
my_context context{...};
// Output the result.
std::cout << format(data).context(context).escape(bustache::escape_html);

String Output

Generate a std::string.

Header

#include <bustache/render/string.hpp>

Synopsis

template<class String, class Escape = no_escape_t>
void render_string
(
    String& out, format const& fmt,
    value_ref data, context_handler context = no_context_t{},
    Escape escape = {}, unresolved_handler f = nullptr
);

template<class... Opts>
std::string to_string(manipulator<Opts...> const& manip);

Example

bustache::format format(...);
std::string txt = to_string(format(data).context(context).escape(bustache::escape_html));

Advanced Topics

Lambdas

The lambdas in {{ bustache }} accept signatures below:

  • (ast::view const* view) -> format
  • (ast::view const* view) -> Value

A ast::view is a parsed list of AST nodes, you can make a new ast::view out of the old one and give it to a format. Note that view will be null if the lambda is used as variable.

Error Handling

The constructor of bustache::format may throw bustache::format_error if the parsing fails.

class format_error : public std::runtime_error
{
public:
    explicit format_error(error_type err, std::ptrdiff_t position);

    error_type code() const noexcept;
    std::ptrdiff_t position() const noexcept;
};

error_type has these values:

  • error_set_delim
  • error_baddelim
  • error_delim
  • error_section
  • error_badkey

You can also use what() for a descriptive text.

Performance

Compare with 2 other libs - mstch and Kainjow.Mustache. See benchmark.cpp.

Sample run (VS2019 16.7.6, boost 1.73.0, 64-bit release build):

2020-10-27T16:10:49+08:00
Running F:\code\bus\x64\Release\bus.exe
Run on (8 X 3600 MHz CPU s)
CPU Caches:
  L1 Data 32 KiB (x8)
  L1 Instruction 32 KiB (x8)
  L2 Unified 256 KiB (x8)
  L3 Unified 12288 KiB (x1)
---------------------------------------------------------
Benchmark               Time             CPU   Iterations
---------------------------------------------------------
bustache_usage       4675 ns         4708 ns       149333
mstch_usage         80919 ns        81961 ns         8960
kainjow_usage       23993 ns        24065 ns        29867

Lower is better.

benchmark

License

Copyright (c) 2014-2021 Jamboree

Distributed under the Boost Software License, Version 1.0. (See accompanying
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)