Skip to content

scc-tw/bustache

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

{{ bustache }}

CI codecov

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

CI Status Matrix

Platform Compiler Versions Build Types Sanitizers Status
Linux GCC 10, 11, 12, 13, 14, 15†, 16† Debug, Release ASan, UBSan, TSan CI
Linux Clang 15, 16, 17, 18, 19, 20†, 21†, 22† Debug, Release Basic CI
macOS Apple Clang Xcode 15.2, 15.4 Debug, Release ASan, UBSan CI
Windows MSVC 2022 VS 17 2022 Debug, Release ASan CI

Special Configurations

Configuration Platform Compiler Purpose Status
Header-only Ubuntu 22.04 GCC Test header-only mode CI
fmt Library Ubuntu 22.04 Clang 15 Test with external fmt CI
Coverage Ubuntu 22.04 GCC Code coverage analysis codecov
Documentation Ubuntu 22.04 GCC Docs & Examples CI

Legend:

  • † = Experimental versions (allowed to fail)
  • ASan = AddressSanitizer, UBSan = UndefinedBehaviorSanitizer, TSan = ThreadSanitizer
  • All builds test both library and header-only modes
  • Coverage includes source files only (excludes tests, dependencies, system headers)

CI Scale: ~80 total jobs across 4 platforms, 15+ compiler versions, multiple build types and sanitizers

Compiler Requirements

  • C++20 (required): GCC 10+, Clang 10+, MSVC 2019 16.10+ (19.29+)

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.

Known Issues

Recursion Protection

Currently, the library does not have recursion protection for dynamic partials. Self-referencing or mutually recursive dynamic partials will cause stack overflow. For example:

{{!-- Self-referencing partial --}}
{{>*partial_name}}

Where partial_name resolves to a partial that contains {{>*partial_name}}, or mutually recursive partials like:

  • Partial A contains {{>*next}}
  • Partial B contains {{>*prev}}
  • Data provides next: "B" and prev: "A"

Workaround: Ensure your dynamic partials do not create recursive references.

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)

About

C++20 implementation of {{ mustache }}

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C++ 79.8%
  • CMake 16.0%
  • Shell 4.1%
  • Mustache 0.1%