/bustache

C++20 implementation of {{ mustache }}

Primary LanguageC++

{{ bustache }}

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

Dependencies

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

    Note
    This can be controlled by BUSTACHE_USE_FMT

Optional Dependencies

Supported Features

  • Variables
  • Sections
  • Inverted Sections
  • Comments
  • Partials
  • Set Delimiter
  • Lambdas
  • HTML escaping (configurable)
  • Inheritance (extension)
  • Dynamic Names (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>

Adapted Model

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