C++11 implementation of {{ mustache }}, compliant with spec v1.1.3.
- Boost - for
unordered_map
, etc
- Google.Benchmark - for benchmark
- Catch - for test
- Variables
- Sections
- Inverted Sections
- Comments
- Partials
- Set Delimiter
- Lambdas
- HTML escaping (configurable)
- Template inheritance (extension)
- Customizable behavior on unresolved key
- User-defined object and list
{{ 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 bustache::object
for Data, and anything that provides interface that is compatible with Map<std::string, bustache::format>
can be used for Partials.
The Format is orthogonal to the Data, so technically you can use your custom Data type with bustache::format
, but then you have to write the formatting logic yourself.
bustache::format format{"{{mustache}} templating"};
bustache::object data{{"mustache", "bustache"}};
std::cout << format(data); // should print "bustache templating"
It's basically the JSON Data Model represented in C++, with some extensions.
#include <bustache/model.hpp>
using array = std::vector<value>;
using object = boost::unordered_map<std::string, value>;
using lambda0v = std::function<value()>;
using lambda0f = std::function<format()>;
using lambda1v = std::function<value(ast::content_list const&)>;
using lambda1f = std::function<format(ast::content_list const&)>;
// Non-owning UDT views.
class object_view;
class list_view;
class value =
variant
<
std::nullptr_t
, bool
, int
, double
, std::string
, array
, lambda0v
, lambda0f
, lambda1v
, lambda1f
, object
, object_view
, list_view
>;
bustache::format
parses in-memory string into AST.
#include <bustache/format.hpp>
Constructors
format(char const* begin, char const* end); // [1]
template<std::size_t N>
explicit format(char const (&source)[N]); // [2]
template<class Source>
explicit format(Source const& source); // [3]
template <typename Source>
explicit format(Source const&& source); // [4]
explicit format(ast::content_list contents, bool copytext = true); // [5]
Source
is an object that represents continous memory, likestd::string
,std::vector<char>
orboost::iostreams::mapped_file_source
that provides access to raw memory throughsource.data()
andsource.size()
.- Version 2 allows implicit conversion from literal.
- Version 1~3 doesn't hold the text, you must ensure the memory referenced is valid and not modified at the use of the format object.
- Version 4 copies the necessary text into its internal buffer, so there's no lifetime issue.
- Version 5 takes a
ast::content_list
, ifcopytext == true
the text will be copied into the internal buffer.
Manipulator
template <typename T>
manipulator<T, no_context>
operator()(T const& data, option_type flag = normal) const;
template <typename T, typename Context>
manipulator<T, Context>
operator()(T const& data, Context const& context, option_type flag = normal) const;
Context
is the lookup-context used by Partials. You can implementbustache::context_trait<T>
for your custom context:
template<>
struct context_trait<T>
{
static format const* get(T const& self, std::string const& key);
};
context_trait
has default implementation for Map<std::string, bustache::format>
, where Map
has interface like map
or unordered_map
.
option_type
provides 2 options:normal
andescape_html
, ifnormal
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
.
// in <bustache/model.hpp>
template<class CharT, class Traits, class T, class Context,
std::enable_if_t<std::is_constructible<value_view, T>::value, bool> = true>
inline std::basic_ostream<CharT, Traits>&
operator<<(std::basic_ostream<CharT, Traits>& out, manipulator<T, Context> const& manip)
// open the template file
boost::iostreams::mapped_file_source file(...);
// create format from source
bustache::format format(file);
// create the data we want to output
bustache::object data{...};
// create the context for Partials
std::unordered_map<std::string, bustache::format> context{...};
// output the result
std::cout << format(data, context, bustache::escape_html);
Note that you can output anything that constitutes bustache::value
, not just bustache::object
.
Generate a std::string
from a manipulator
.
// in <bustache/model.hpp>
template<class T, class Context,
std::enable_if_t<std::is_constructible<value_view, T>::value, bool> = true>
inline std::string to_string(manipulator<T, Context> const& manip)
bustache::format format(...);
std::string txt = to_string(format(data, context, bustache::escape_html));
generate
can be used for customized output.
#include <bustache/generate.hpp>
template<class Sink, class UnresolvedHandler = default_unresolved_handler>
inline void generate
(
Sink& sink, format const& fmt, value_view const& data,
option_type flag = normal, UnresolvedHandler&& f = {}
)
{
generate(sink, fmt, data, no_context::dummy(), flag, std::forward<UnresolvedHandler>(f));
}
template<class Sink, class Context, class UnresolvedHandler = default_unresolved_handler>
void generate
(
Sink& sink, format const& fmt, value_view const& data,
Context const& context, option_type flag = normal, UnresolvedHandler&& f = {}
)
Sink
is a polymorphic functor that handles:
void operator()(char const* it, char const* end);
void operator()(bool data);
void operator()(int data);
void operator()(double data);
You don't have to deal with HTML-escaping yourself, it's handled within generate
depending on the option.
UnresolvedHandler
is a callable object that has the signature:
value(std::string const& key);
The key
parameter is the unresolved key. The default handler just returns a null value.
These are predefined output built on generate
.
#include <bustache/generate/ostream.hpp>
#include <bustache/generate/string.hpp>
template<class CharT, class Traits, class Context, class UnresolvedHandler = default_unresolved_handler>
void generate_ostream
(
std::basic_ostream<CharT, Traits>& out, format const& fmt,
value_view const& data, Context const& context,
option_type flag, UnresolvedHandler&& f = {}
);
template<class String, class Context, class UnresolvedHandler = default_unresolved_handler>
void generate_string
(
String& out, format const& fmt,
value_view const& data, Context const& context,
option_type flag, UnresolvedHandler&& f = {}
);
The stream-based output and string output are built on these functions,
but <bustache/model.hpp>
doesn't include these headers and only supports char
output,
if you need other char-type support for stream/string output, you have to include these headers as well.
The lambdas in {{ bustache }} have 4 variants - they're production of 2 param-set x 2 return-type.
One param-set accepts no params, the other accepts a bustache::ast::content_list const&
.
One return-type is bustache::value
, the other is bustache::format
.
Note that unlike other implementations, we pass a bustache::ast::content_list
instead of a raw string.
A content_list
is a parsed list of AST nodes, you can make a new content_list
out of the old one and give it to a bustache::format
.
Sometimes it's infeasible or inefficent to transform the whole user data to the JSON-like data model. Fortunately, {{ bustache }} allows users to use custom types in the data model, by implementing the required traits and using the corresponding views for the UDTs.
Implement bustache::object_trait
for your object type T
:
template<>
struct object_trait<T>
{
static value_ptr get(T const& self, std::string const& key, value_holder& hold);
};
Use bustache::object_view
to make a view to the object:
T data;
object_view(data);
Implement bustache::list_trait
for your list type T
:
template<>
struct list_trait<T>
{
static bool empty(T const& self);
static std::uintptr_t begin_cursor(T const& self);
static value_ptr next(T const& self, std::uintptr_t& state, value_holder& hold);
static void end_cursor(T const& self, std::uintptr_t) noexcept {}
};
Use bustache::list_view
to make a view to the list:
T data;
list_view(data);
Note that both object_trait<T>::get
and list_trait<T>::next
take a bustache::value_holder&
and return a bustache::value_ptr
.
The value that value_ptr
refers to must outlive the pointer, and that's what value_holder
offers - to hold the value.
To return a local value:
return hold(10);
To return an object_view
to some data member:
return hold(object_view(self.obj));
To return a local object:
return hold.object(obj); // obj is copied.
To return a local list:
return hold.list(lst); // lst is copied.
It's not always necessary to use value_holder
. If you know the data outlives the returned value_ptr
, you can do something like:
return value_view(self.data).get_pointer();
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);
error_type code() const;
};
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 (VS2017 15.7.4, boost 1.67.0, 64-bit release build):
06/25/18 16:14:52
Run on (8 X 3392 MHz CPU s)
CPU Caches:
L1 Data 32K (x4)
L1 Instruction 32K (x4)
L2 Unified 262K (x4)
L3 Unified 8388K (x1)
------------------------------------------------------
Benchmark Time CPU Iterations
------------------------------------------------------
bustache_usage 6605 ns 6627 ns 89600
mstch_usage 106620 ns 106027 ns 5600
kainjow_usage 22828 ns 22949 ns 32000
Lower is better.
Copyright (c) 2014-2018 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)