C++20 implementation of {{ mustache }}, compliant with spec v1.1.3.
- fmt (or C++20
<format>
)Note
This can be controlled byBUSTACHE_USE_FMT
- Google.Benchmark - for benchmark
- Catch2 - for test
- Variables
- Sections
- Inverted Sections
- Comments
- Partials
- Set Delimiter
- Lambdas
- HTML escaping (configurable)
- Inheritance (extension)
- Dynamic Names (extension)
- 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}}
.
{{ 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.
bustache::format format{"{{mustache}} templating"};
std::unordered_map<std::string, std::string> data{{"mustache", "bustache"}};
std::cout << format(data); // should print "bustache templating"
{{ 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!
#include <bustache/model.hpp>
- Boost.Describe
#include <bustache/adapted/boost_describe.hpp>
- Boost.JSON
#include <bustache/adapted/boost_json.hpp>
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.
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.
bustache::format
parses in-memory string into AST.
#include <bustache/format.hpp>
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
can be used for customized output.
#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
);
The context for partials can be any callable that meets the signature:
(std::string const& key) -> format const*;
The unresolved handler can be any callable that meets the signature:
(std::string const& key) -> value_ptr;
The sink can be any callable that meets the signature:
(char const* data, std::size_t count) -> void;
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.
Output directly to the std::basic_ostream
.
#include <bustache/render/ostream.hpp>
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)
// 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);
Generate a std::string
.
#include <bustache/render/string.hpp>
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);
bustache::format format(...);
std::string txt = to_string(format(data).context(context).escape(bustache::escape_html));
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.
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.
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.
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)