├── .appveyor.yml
├── .gitignore
├── .gitmodules
├── .travis.yml
├── CMakeLists.txt
├── LICENSE
├── README.md
├── benchmark
├── CMakeLists.txt
└── benchmark_main.cpp
├── cmake
└── mstch-config.cmake
├── include
└── mstch
│ └── mstch.hpp
├── src
├── CMakeLists.txt
├── mstch.cpp
├── render_context.cpp
├── render_context.hpp
├── state
│ ├── in_section.cpp
│ ├── in_section.hpp
│ ├── outside_section.cpp
│ ├── outside_section.hpp
│ └── render_state.hpp
├── template_type.cpp
├── template_type.hpp
├── token.cpp
├── token.hpp
├── utils.cpp
├── utils.hpp
└── visitor
│ ├── get_token.hpp
│ ├── has_token.hpp
│ ├── is_node_empty.hpp
│ ├── render_node.hpp
│ └── render_section.hpp
└── test
├── CMakeLists.txt
├── data
├── ampersand_escape.hpp
├── ampersand_escape.mustache
├── ampersand_escape.txt
├── apostrophe.hpp
├── apostrophe.mustache
├── apostrophe.txt
├── array_of_strings.hpp
├── array_of_strings.mustache
├── array_of_strings.txt
├── backslashes.hpp
├── backslashes.mustache
├── backslashes.txt
├── bug_11_eating_whitespace.hpp
├── bug_11_eating_whitespace.mustache
├── bug_11_eating_whitespace.txt
├── bug_length_property.hpp
├── bug_length_property.mustache
├── bug_length_property.txt
├── changing_delimiters.hpp
├── changing_delimiters.mustache
├── changing_delimiters.txt
├── comments.hpp
├── comments.mustache
├── comments.txt
├── complex.hpp
├── complex.mustache
├── complex.txt
├── context_lookup.hpp
├── context_lookup.mustache
├── context_lookup.txt
├── delimiters.hpp
├── delimiters.mustache
├── delimiters.txt
├── disappearing_whitespace.hpp
├── disappearing_whitespace.mustache
├── disappearing_whitespace.txt
├── dot_notation.hpp
├── dot_notation.mustache
├── dot_notation.txt
├── double_render.hpp
├── double_render.mustache
├── double_render.txt
├── empty_list.hpp
├── empty_list.mustache
├── empty_list.txt
├── empty_sections.hpp
├── empty_sections.mustache
├── empty_sections.txt
├── empty_string.hpp
├── empty_string.mustache
├── empty_string.txt
├── empty_template.hpp
├── empty_template.mustache
├── empty_template.txt
├── error_eof_in_section.hpp
├── error_eof_in_section.mustache
├── error_eof_in_section.txt
├── error_eof_in_tag.hpp
├── error_eof_in_tag.mustache
├── error_eof_in_tag.txt
├── error_not_found.hpp
├── error_not_found.mustache
├── error_not_found.txt
├── escaped.hpp
├── escaped.mustache
├── escaped.txt
├── falsy.hpp
├── falsy.mustache
├── falsy.txt
├── falsy_array.hpp
├── falsy_array.mustache
├── falsy_array.txt
├── grandparent_context.hpp
├── grandparent_context.mustache
├── grandparent_context.txt
├── higher_order_sections.hpp
├── higher_order_sections.mustache
├── higher_order_sections.txt
├── implicit_iterator.hpp
├── implicit_iterator.mustache
├── implicit_iterator.txt
├── included_tag.hpp
├── included_tag.mustache
├── included_tag.txt
├── inverted_section.hpp
├── inverted_section.mustache
├── inverted_section.txt
├── keys_with_questionmarks.hpp
├── keys_with_questionmarks.mustache
├── keys_with_questionmarks.txt
├── multiline_comment.hpp
├── multiline_comment.mustache
├── multiline_comment.txt
├── nested_dot.hpp
├── nested_dot.mustache
├── nested_dot.txt
├── nested_higher_order_sections.hpp
├── nested_higher_order_sections.mustache
├── nested_higher_order_sections.txt
├── nested_iterating.hpp
├── nested_iterating.mustache
├── nested_iterating.txt
├── nesting.hpp
├── nesting.mustache
├── nesting.txt
├── nesting_same_name.hpp
├── nesting_same_name.mustache
├── nesting_same_name.txt
├── null_lookup_array.hpp
├── null_lookup_array.mustache
├── null_lookup_array.txt
├── null_lookup_object.hpp
├── null_lookup_object.mustache
├── null_lookup_object.txt
├── null_string.hpp
├── null_string.mustache
├── null_string.txt
├── null_view.hpp
├── null_view.mustache
├── null_view.txt
├── partial_array.hpp
├── partial_array.mustache
├── partial_array.partial
├── partial_array.txt
├── partial_array_of_partials.hpp
├── partial_array_of_partials.mustache
├── partial_array_of_partials.partial
├── partial_array_of_partials.txt
├── partial_array_of_partials_implicit.hpp
├── partial_array_of_partials_implicit.mustache
├── partial_array_of_partials_implicit.partial
├── partial_array_of_partials_implicit.txt
├── partial_empty.hpp
├── partial_empty.mustache
├── partial_empty.partial
├── partial_empty.txt
├── partial_template.hpp
├── partial_template.mustache
├── partial_template.partial
├── partial_template.txt
├── partial_view.hpp
├── partial_view.mustache
├── partial_view.partial
├── partial_view.txt
├── partial_whitespace.hpp
├── partial_whitespace.mustache
├── partial_whitespace.partial
├── partial_whitespace.txt
├── recursion_with_same_names.hpp
├── recursion_with_same_names.mustache
├── recursion_with_same_names.txt
├── reuse_of_enumerables.hpp
├── reuse_of_enumerables.mustache
├── reuse_of_enumerables.txt
├── section_as_context.hpp
├── section_as_context.mustache
├── section_as_context.txt
├── section_functions_in_partials.hpp
├── section_functions_in_partials.mustache
├── section_functions_in_partials.partial
├── section_functions_in_partials.txt
├── simple.hpp
├── simple.mustache
├── simple.txt
├── string_as_context.hpp
├── string_as_context.mustache
├── string_as_context.txt
├── two_in_a_row.hpp
├── two_in_a_row.mustache
├── two_in_a_row.txt
├── two_sections.hpp
├── two_sections.mustache
├── two_sections.txt
├── unescaped.hpp
├── unescaped.mustache
├── unescaped.txt
├── whitespace.hpp
├── whitespace.mustache
├── whitespace.txt
├── zero_view.hpp
├── zero_view.mustache
└── zero_view.txt
├── specs_lambdas.hpp
├── test_context.hpp
└── test_main.cpp
/.appveyor.yml:
--------------------------------------------------------------------------------
1 | environment:
2 | matrix:
3 | - GENERATOR: "Visual Studio 12 2013"
4 | CONFIG: Release
5 | BOOST_ROOT: "C:\\Libraries\\boost"
6 | BOOST_LIBRARYDIR: "C:\\Libraries\\boost\\lib32-msvc-12.0"
7 |
8 | - GENERATOR: "Visual Studio 14 2015"
9 | CONFIG: Release
10 | BOOST_ROOT: "C:\\Libraries\\boost_1_59_0"
11 | BOOST_LIBRARYDIR: "C:\\Libraries\\boost_1_59_0\\lib32-msvc-14.0"
12 |
13 | - GENERATOR: "Visual Studio 14 2015 Win64"
14 | CONFIG: Release
15 | BOOST_ROOT: "C:\\Libraries\\boost_1_59_0"
16 | BOOST_LIBRARYDIR: "C:\\Libraries\\boost_1_59_0\\lib64-msvc-14.0"
17 |
18 | - GENERATOR: "Visual Studio 12 2013"
19 | CONFIG: Debug
20 | BOOST_ROOT: "C:\\Libraries\\boost"
21 | BOOST_LIBRARYDIR: "C:\\Libraries\\boost\\lib32-msvc-12.0"
22 |
23 | - GENERATOR: "Visual Studio 14 2015"
24 | CONFIG: Debug
25 | BOOST_ROOT: "C:\\Libraries\\boost_1_59_0"
26 | BOOST_LIBRARYDIR: "C:\\Libraries\\boost_1_59_0\\lib32-msvc-14.0"
27 |
28 | - GENERATOR: "Visual Studio 14 2015 Win64"
29 | CONFIG: Debug
30 | BOOST_ROOT: "C:\\Libraries\\boost_1_59_0"
31 | BOOST_LIBRARYDIR: "C:\\Libraries\\boost_1_59_0\\lib64-msvc-14.0"
32 |
33 | install:
34 | - git submodule init
35 | - git submodule update
36 |
37 | build_script:
38 | - mkdir build
39 | - cd build
40 | - cmake "-G%GENERATOR%" -DBOOST_ROOT="%BOOST_ROOT%" -DBOOST_LIBRARYDIR="%BOOST_LIBRARYDIR%" -DBoost_USE_STATIC_LIBS=ON -DWITH_UNIT_TESTS=ON ..
41 | - cmake --build . --config "%CONFIG%"
42 |
43 | test_script:
44 | - ctest --build-config "%CONFIG%"
45 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | build*
3 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "vendor/Catch"]
2 | path = vendor/Catch
3 | url = https://github.com/philsquared/Catch.git
4 | [submodule "vendor/spec"]
5 | path = vendor/spec
6 | url = https://github.com/mustache/spec.git
7 | [submodule "vendor/benchmark"]
8 | path = vendor/benchmark
9 | url = https://github.com/google/benchmark.git
10 | [submodule "vendor/headerize"]
11 | path = vendor/headerize
12 | url = https://github.com/no1msd/headerize.git
13 | [submodule "vendor/rapidjson"]
14 | path = vendor/rapidjson
15 | url = https://github.com/miloyip/rapidjson.git
16 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: cpp
2 | sudo: false
3 |
4 | matrix:
5 | include:
6 | - env:
7 | - COMPILER=g++-5
8 | - WITH_UNIT_TESTS=ON
9 | addons:
10 | apt:
11 | sources:
12 | - ubuntu-toolchain-r-test
13 | - boost-latest
14 | - george-edison55-precise-backports
15 | packages:
16 | - g++-5
17 | - cmake
18 | - cmake-data
19 | - libboost1.55-dev
20 | - libboost-regex1.55-dev
21 | - libboost-program-options1.55-dev
22 | os: linux
23 | - env:
24 | - COMPILER=g++-4.9
25 | - WITH_UNIT_TESTS=ON
26 | addons:
27 | apt:
28 | sources:
29 | - ubuntu-toolchain-r-test
30 | - boost-latest
31 | - george-edison55-precise-backports
32 | packages:
33 | - g++-4.9
34 | - cmake
35 | - cmake-data
36 | - libboost1.55-dev
37 | - libboost-regex1.55-dev
38 | - libboost-program-options1.55-dev
39 | os: linux
40 | - env:
41 | - COMPILER=g++-4.8
42 | - WITH_UNIT_TESTS=ON
43 | addons:
44 | apt:
45 | sources:
46 | - ubuntu-toolchain-r-test
47 | - boost-latest
48 | - george-edison55-precise-backports
49 | packages:
50 | - g++-4.8
51 | - cmake
52 | - cmake-data
53 | - libboost1.55-dev
54 | - libboost-regex1.55-dev
55 | - libboost-program-options1.55-dev
56 | os: linux
57 | - env:
58 | - COMPILER=g++-4.7
59 | - WITH_UNIT_TESTS=ON
60 | addons:
61 | apt:
62 | sources:
63 | - ubuntu-toolchain-r-test
64 | - boost-latest
65 | - george-edison55-precise-backports
66 | packages:
67 | - g++-4.7
68 | - cmake
69 | - cmake-data
70 | - libboost1.55-dev
71 | - libboost-regex1.55-dev
72 | - libboost-program-options1.55-dev
73 | os: linux
74 | - env:
75 | - COMPILER=clang++-3.5
76 | - WITH_UNIT_TESTS=ON
77 | addons:
78 | apt:
79 | sources:
80 | - llvm-toolchain-precise-3.5
81 | - ubuntu-toolchain-r-test
82 | - boost-latest
83 | - george-edison55-precise-backports
84 | packages:
85 | - clang-3.5
86 | - cmake
87 | - cmake-data
88 | - libboost1.55-dev
89 | - libboost-regex1.55-dev
90 | - libboost-program-options1.55-dev
91 | os: linux
92 | - env:
93 | - COMPILER=clang++-3.6
94 | - WITH_UNIT_TESTS=ON
95 | addons:
96 | apt:
97 | sources:
98 | - llvm-toolchain-precise-3.6
99 | - ubuntu-toolchain-r-test
100 | - boost-latest
101 | - george-edison55-precise-backports
102 | packages:
103 | - clang-3.6
104 | - cmake
105 | - cmake-data
106 | - libboost1.55-dev
107 | - libboost-regex1.55-dev
108 | - libboost-program-options1.55-dev
109 | os: linux
110 | - env:
111 | - COMPILER=clang++-3.7
112 | - WITH_UNIT_TESTS=ON
113 | addons:
114 | apt:
115 | sources:
116 | - llvm-toolchain-precise-3.7
117 | - ubuntu-toolchain-r-test
118 | - boost-latest
119 | - george-edison55-precise-backports
120 | packages:
121 | - clang-3.7
122 | - cmake
123 | - cmake-data
124 | - libboost1.55-dev
125 | - libboost-regex1.55-dev
126 | - libboost-program-options1.55-dev
127 | os: linux
128 | - env:
129 | - COMPILER=clang++
130 | - WITH_UNIT_TESTS=ON
131 | os: osx
132 |
133 | before_script:
134 | - export CXX=${COMPILER}
135 | - ${COMPILER} --version
136 | - mkdir build
137 | - cd build
138 | - cmake -DWITH_UNIT_TESTS=${WITH_UNIT_TESTS} ..
139 |
140 | script: make
141 |
142 | after_script: make test
143 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.0.2)
2 | project(mstch)
3 |
4 | option(WITH_UNIT_TESTS "enable building unit test executable" OFF)
5 | option(WITH_BENCHMARK "enable building benchmark executable" OFF)
6 |
7 | set(CMAKE_INCLUDE_CURRENT_DIR ON)
8 | set(CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE ON)
9 | set(CMAKE_BUILD_TYPE Release)
10 |
11 | set(mstch_VERSION 1.0.1)
12 |
13 | if(NOT MSVC)
14 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -O3")
15 | endif()
16 |
17 | add_subdirectory(src)
18 |
19 | if(WITH_UNIT_TESTS)
20 | enable_testing()
21 | add_subdirectory(vendor/headerize)
22 | add_subdirectory(test)
23 | endif()
24 |
25 | if(WITH_BENCHMARK)
26 | add_subdirectory(vendor/benchmark)
27 | add_subdirectory(benchmark)
28 | endif()
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Daniel Sipka
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mstch - {{mustache}} templates in C++11
2 |
3 | 
4 |
5 | mstch is a complete implementation of [{{mustache}}](http://mustache.github.io/)
6 | templates using modern C++. It's compliant with [specifications](https://github.com/mustache/spec)
7 | v1.1.3, including the lambda module.
8 |
9 | [](http://melpon.org/wandbox/permlink/EqyOe7IBRYPGVk5f)
10 | [](http://badge.fury.io/gh/no1msd%2Fmstch)
11 | [](https://travis-ci.org/no1msd/mstch)
12 | [](https://ci.appveyor.com/project/no1msd/mstch)
13 |
14 | ## Supported features
15 |
16 | mstch supports the complete feature set described in the `mustache(5)` [manpage](http://mustache.github.com/mustache.5.html):
17 |
18 | - JSON-like data structure using [Boost.Variant](http://www.boost.org/libs/variant)
19 | - variables, sections, inverted sections
20 | - partials
21 | - changing the delimiter
22 | - C++11 lambdas
23 | - C++ objects as view models
24 |
25 | ## Basic usage
26 |
27 | ```c++
28 | #include
29 | #include
30 |
31 | int main() {
32 | std::string view{"{{#names}}Hi {{name}}!\n{{/names}}"};
33 | mstch::map context{
34 | {"names", mstch::array{
35 | mstch::map{{"name", std::string{"Chris"}}},
36 | mstch::map{{"name", std::string{"Mark"}}},
37 | mstch::map{{"name", std::string{"Scott"}}},
38 | }}
39 | };
40 |
41 | std::cout << mstch::render(view, context) << std::endl;
42 |
43 | return 0;
44 | }
45 |
46 | ```
47 |
48 | The output of this example will be:
49 |
50 | ```html
51 | Hi Chris!
52 | Hi Mark!
53 | Hi Scott!
54 | ```
55 |
56 | ### Data structure
57 |
58 | The types in the example above, `mstch::array` and `mstch::map` are actually
59 | aliases for standard types:
60 |
61 | ```c++
62 | using map = std::map;
63 | using array = std::vector;
64 | ```
65 |
66 | `mstch::node` is a `boost::variant` that can hold a `std::string`, `int`,
67 | `double`, `bool`, `mstch::lambda` or a `std::shared_ptr`
68 | (see below), also a map or an array recursively. Essentially it works just like
69 | a JSON object.
70 |
71 | Note that when using a `std::string` as value you must explicitly specify the
72 | type, since a `const char*` literal like `"foobar"` would be implicitly
73 | converted to `bool`. Alternatively you can use [C++14 string_literals](http://en.cppreference.com/w/cpp/string/basic_string/operator%22%22s)
74 | if your compiler supports it.
75 |
76 | ## Advanced usage
77 |
78 | ### Partials
79 |
80 | Partials can be passed in a `std::map` as the third parameter of the
81 | `mstch::render` function:
82 |
83 | ```c++
84 | std::string view{"{{#names}}{{> user}}{{/names}}"};
85 | std::string user_view{"{{name}}\n "};
86 | mstch::map context{
87 | {"names", mstch::array{
88 | mstch::map{{"name", std::string{"Chris"}}},
89 | mstch::map{{"name", std::string{"Mark"}}},
90 | mstch::map{{"name", std::string{"Scott"}}},
91 | }}
92 | };
93 |
94 | std::cout << mstch::render(view, context, {{"user", user_view}}) << std::endl;
95 | ```
96 |
97 | Output:
98 |
99 | ```html
100 | Chris
101 | Mark
102 | Scott
103 | ```
104 |
105 | ### Lambdas
106 |
107 | C++11 lambda expressions can be used to add logic to your templates. Like a
108 | `const char*` literal, lambdas can be implicitly converted to `bool`, so they
109 | must be wrapped in a `mstch::lambda` object when used in a `mstch::node`. The
110 | lambda expression passed to `mstch::lambda` must itself return a `mstch::node`.
111 | The returned node will be rendered to a string, then it will be parsed as a
112 | template.
113 |
114 | The lambda expression accepts either no parameters:
115 |
116 | ```c++
117 | std::string view{"Hello {{lambda}}!"};
118 | mstch::map context{
119 | {"lambda", mstch::lambda{[]() -> mstch::node {
120 | return std::string{"World"};
121 | }}}
122 | };
123 |
124 | std::cout << mstch::render(view, context) << std::endl;
125 | ```
126 |
127 | Output:
128 |
129 | ```html
130 | Hello World!
131 | ```
132 |
133 | Or it accepts a `const std::string&` that gets the unrendered literal block:
134 |
135 | ```c++
136 | std::string view{"{{#bold}}{{yay}} :){{/bold}}"};
137 | mstch::map context{
138 | {"yay", std::string{"Yay!"}},
139 | {"bold", mstch::lambda{[](const std::string& text) -> mstch::node {
140 | return "" + text + " ";
141 | }}}
142 | };
143 |
144 | std::cout << mstch::render(view, context) << std::endl;
145 | ```
146 |
147 | Output:
148 |
149 | ```html
150 | Yay! :)
151 | ```
152 |
153 | ### Objects
154 |
155 | Custom objects can also be used as context for rendering templates. The class
156 | must inherit from `mstch::object`, and register it's exported methods with
157 | `register_methods`. Exported methods must have the return type of `mstch::node`.
158 | Objects must be created as a `std::shared_ptr`.
159 |
160 | ```c++
161 | class example: public mstch::object {
162 | public:
163 | example(): m_value(1) {
164 | register_methods(this, {
165 | {"count", &example::count},
166 | {"names", &example::names}
167 | });
168 | }
169 |
170 | mstch::node count() {
171 | return m_value++;
172 | }
173 |
174 | mstch::node names() {
175 | return mstch::array{
176 | std::string{"Chris"}, std::string{"Mark"}, std::string{"Scott"}};
177 | }
178 |
179 | private:
180 | int m_value;
181 | };
182 |
183 | std::string view{"{{#names}}{{count}} : {{.}}\n{{/names}}"};
184 | const auto context = std::make_shared();
185 |
186 | std::cout << mstch::render(view, context) << std::endl;
187 | ```
188 |
189 | Output:
190 |
191 | ```html
192 | 1 : Chris
193 | 2 : Mark
194 | 3 : Scott
195 | ```
196 |
197 | ### Custom escape function
198 |
199 | By default, mstch uses HTML escaping on the output, as per specification. This
200 | is not useful if your output is not HTML, so mstch provides a way to supply
201 | your own escape implementation. Just assign any callable object to the static
202 | `mstch::config::escape`, which is an initially empty
203 | `std::function`.
204 |
205 | For example you can turn off escaping entirely with a lambda:
206 |
207 | ```c++
208 | mstch::config::escape = [](const std::string& str) -> std::string {
209 | return str;
210 | };
211 | ```
212 |
213 | ## Requirements
214 |
215 | - A C++ compiler with decent C++11 support. Currently tested with:
216 | - GCC 4.7, 4.8, 4.9, 5.1
217 | - clang 3.5, 3.6, 3.7 (both libstdc++ and libc++ are supported)
218 | - MSVC 2013, 2015
219 | - Boost 1.54+ for [Boost.Variant](http://www.boost.org/libs/variant)
220 | - CMake 3.0+ for building
221 |
222 | ## Using mstch in your project
223 |
224 | If you are using CMake, the easiest way to include mstch in your project is to
225 | copy the whole directory to your source tree, and use `add_subdirectory` in your
226 | CMakeLists.txt. This will set a variable named `mstch_INCLUDE_DIR` that contains
227 | its include path, and add a static library target named `mstch`. For example:
228 |
229 | ```cmake
230 | add_subdirectory(external/mstch)
231 | include_directories(${mstch_INCLUDE_DIR})
232 | target_link_libraries(your_project mstch)
233 | ```
234 |
235 | If you prefer to install the library globally, you can simply do the following
236 | from the root of the source tree:
237 |
238 | ```bash
239 | $ mkdir build
240 | $ cd build
241 | $ cmake ..
242 | $ make
243 | $ make install
244 | ```
245 |
246 | The install command may require root privileges. This will also install CMake
247 | config files, so you can use use `find_package` in your CMakeLists.txt:
248 |
249 | ```cmake
250 | find_package(mstch)
251 | target_link_libraries(your_project mstch::mstch)
252 | ```
253 |
254 | ## Running the unit tests
255 |
256 | Unit tests are using the [Catch](https://github.com/philsquared/Catch) framework
257 | and [rapidjson](http://rapidjson.org/) to parse the
258 | [Mustache specifications](https://github.com/mustache/spec), all of which are
259 | included in the repository as git submodules. Various
260 | [Boost](http://www.boost.org/) libraries are also required to build them.
261 |
262 | Don't forget to initialize submodules:
263 |
264 | ```bash
265 | $ git submodule init
266 | $ git submodule update
267 | ```
268 |
269 | To build and run the unit tests:
270 |
271 | ```bash
272 | $ mkdir build
273 | $ cd build
274 | $ cmake -DWITH_UNIT_TESTS=ON ..
275 | $ make
276 | $ make test
277 | ```
278 |
279 | ## License
280 |
281 | mstch is licensed under the [MIT license](https://github.com/no1msd/mstch/blob/master/LICENSE).
282 |
--------------------------------------------------------------------------------
/benchmark/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | include_directories(
2 | ${CMAKE_SOURCE_DIR}/include
3 | ${CMAKE_SOURCE_DIR}/vendor/benchmark/include)
4 |
5 | add_executable(mstch_benchmark benchmark_main.cpp)
6 | target_link_libraries(mstch_benchmark mstch benchmark)
7 |
--------------------------------------------------------------------------------
/benchmark/benchmark_main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "mstch/mstch.hpp"
4 |
5 | static void basic_usage(benchmark::State& state) {
6 | std::string comment_tmp{
7 | ""};
10 |
11 | auto comment_view = mstch::map{
12 | {"header", std::string{"My Post Comments"}},
13 | {"comments", mstch::array{
14 | mstch::map{{"name", std::string{"Joe"}}, {"body", std::string{"Thanks for this post!"}}},
15 | mstch::map{{"name", std::string{"Sam"}}, {"body", std::string{"Thanks for this post!"}}},
16 | mstch::map{{"name", std::string{"Heather"}}, {"body", std::string{"Thanks for this post!"}}},
17 | mstch::map{{"name", std::string{"Kathy"}}, {"body", std::string{"Thanks for this post!"}}},
18 | mstch::map{{"name", std::string{"George"}}, {"body", std::string{"Thanks for this post!"}}}}}};
19 |
20 | while (state.KeepRunning())
21 | mstch::render(comment_tmp, comment_view);
22 | }
23 |
24 | BENCHMARK(basic_usage);
25 |
26 | BENCHMARK_MAIN();
27 |
--------------------------------------------------------------------------------
/cmake/mstch-config.cmake:
--------------------------------------------------------------------------------
1 | include("${CMAKE_CURRENT_LIST_DIR}/mstch-targets.cmake")
--------------------------------------------------------------------------------
/include/mstch/mstch.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include
10 |
11 | namespace mstch {
12 |
13 | struct config {
14 | static std::function escape;
15 | };
16 |
17 | namespace internal {
18 |
19 | template
20 | class object_t {
21 | public:
22 | const N& at(const std::string& name) const {
23 | cache[name] = (methods.at(name))();
24 | return cache[name];
25 | }
26 |
27 | bool has(const std::string name) const {
28 | return methods.count(name) != 0;
29 | }
30 |
31 | protected:
32 | template
33 | void register_methods(S* s, std::map methods) {
34 | for(auto& item: methods)
35 | this->methods.insert({item.first, std::bind(item.second, s)});
36 | }
37 |
38 | private:
39 | std::map> methods;
40 | mutable std::map cache;
41 | };
42 |
43 | template
44 | class is_fun {
45 | private:
46 | using not_fun = char;
47 | using fun_without_args = char[2];
48 | using fun_with_args = char[3];
49 | template struct really_has;
50 | template static fun_without_args& test(
51 | really_has*);
52 | template static fun_with_args& test(
53 | really_has*);
55 | template static not_fun& test(...);
56 |
57 | public:
58 | static bool const no_args = sizeof(test(0)) == sizeof(fun_without_args);
59 | static bool const has_args = sizeof(test(0)) == sizeof(fun_with_args);
60 | };
61 |
62 | template
63 | using node_renderer = std::function;
64 |
65 | template
66 | class lambda_t {
67 | public:
68 | template
69 | lambda_t(F f, typename std::enable_if::no_args>::type* = 0):
70 | fun([f](node_renderer renderer, const std::string&) {
71 | return renderer(f());
72 | })
73 | {
74 | }
75 |
76 | template
77 | lambda_t(F f, typename std::enable_if::has_args>::type* = 0):
78 | fun([f](node_renderer renderer, const std::string& text) {
79 | return renderer(f(text));
80 | })
81 | {
82 | }
83 |
84 | std::string operator()(node_renderer renderer,
85 | const std::string& text = "") const
86 | {
87 | return fun(renderer, text);
88 | }
89 |
90 | private:
91 | std::function renderer, const std::string&)> fun;
92 | };
93 |
94 | }
95 |
96 | using node = boost::make_recursive_variant<
97 | std::nullptr_t, std::string, int, double, bool,
98 | internal::lambda_t,
99 | std::shared_ptr>,
100 | std::map,
101 | std::vector>::type;
102 | using object = internal::object_t;
103 | using lambda = internal::lambda_t;
104 | using map = std::map;
105 | using array = std::vector;
106 |
107 | std::string render(
108 | const std::string& tmplt,
109 | const node& root,
110 | const std::map& partials =
111 | std::map());
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/src/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | find_package(Boost 1.54 REQUIRED)
2 |
3 | set(mstch_INCLUDE_DIR
4 | ${PROJECT_SOURCE_DIR}/include CACHE STRING "mstch include directory")
5 |
6 | include_directories(
7 | ${mstch_INCLUDE_DIR}
8 | ${Boost_INCLUDE_DIR})
9 |
10 | set(SRC
11 | state/in_section.cpp
12 | state/outside_section.cpp
13 | state/render_state.hpp
14 | visitor/get_token.hpp
15 | visitor/has_token.hpp
16 | visitor/is_node_empty.hpp
17 | visitor/render_node.hpp
18 | visitor/render_section.hpp
19 | mstch.cpp
20 | render_context.cpp
21 | template_type.cpp
22 | token.cpp
23 | utils.cpp)
24 |
25 | add_library(mstch STATIC ${SRC})
26 |
27 | set_property(TARGET mstch PROPERTY VERSION ${mstch_VERSION})
28 |
29 | install(
30 | TARGETS mstch EXPORT mstchTargets
31 | LIBRARY DESTINATION lib
32 | ARCHIVE DESTINATION lib)
33 |
34 | install(
35 | FILES "${PROJECT_SOURCE_DIR}/include/mstch/mstch.hpp"
36 | DESTINATION include/mstch
37 | COMPONENT Devel)
38 |
39 | include(CMakePackageConfigHelpers)
40 | write_basic_package_version_file(
41 | "${CMAKE_CURRENT_BINARY_DIR}/mstch/mstch-config-version.cmake"
42 | VERSION ${mstch_VERSION}
43 | COMPATIBILITY AnyNewerVersion)
44 |
45 | export(
46 | EXPORT mstchTargets
47 | FILE "${CMAKE_CURRENT_BINARY_DIR}/mstch/mstch-targets.cmake"
48 | NAMESPACE mstch::)
49 |
50 | configure_file(
51 | "${PROJECT_SOURCE_DIR}/cmake/mstch-config.cmake"
52 | "${CMAKE_CURRENT_BINARY_DIR}/mstch/mstch-config.cmake")
53 |
54 | install(
55 | EXPORT mstchTargets
56 | FILE mstch-targets.cmake
57 | NAMESPACE mstch::
58 | DESTINATION lib/cmake/mstch)
59 |
60 | install(FILES
61 | "${PROJECT_SOURCE_DIR}/cmake/mstch-config.cmake"
62 | "${CMAKE_CURRENT_BINARY_DIR}/mstch/mstch-config-version.cmake"
63 | DESTINATION lib/cmake/mstch
64 | COMPONENT Devel)
65 |
--------------------------------------------------------------------------------
/src/mstch.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "mstch/mstch.hpp"
4 | #include "render_context.hpp"
5 |
6 | using namespace mstch;
7 |
8 | std::function mstch::config::escape;
9 |
10 | std::string mstch::render(
11 | const std::string& tmplt,
12 | const node& root,
13 | const std::map& partials)
14 | {
15 | std::map partial_templates;
16 | for (auto& partial: partials)
17 | partial_templates.insert({partial.first, {partial.second}});
18 |
19 | return render_context(root, partial_templates).render(tmplt);
20 | }
21 |
--------------------------------------------------------------------------------
/src/render_context.cpp:
--------------------------------------------------------------------------------
1 | #include "render_context.hpp"
2 | #include "state/outside_section.hpp"
3 | #include "visitor/get_token.hpp"
4 |
5 | using namespace mstch;
6 |
7 | const mstch::node render_context::null_node;
8 |
9 | render_context::push::push(render_context& context, const mstch::node& node):
10 | m_context(context)
11 | {
12 | context.m_nodes.emplace_front(node);
13 | context.m_node_ptrs.emplace_front(&node);
14 | context.m_state.push(std::unique_ptr(new outside_section));
15 | }
16 |
17 | render_context::push::~push() {
18 | m_context.m_nodes.pop_front();
19 | m_context.m_node_ptrs.pop_front();
20 | m_context.m_state.pop();
21 | }
22 |
23 | std::string render_context::push::render(const template_type& templt) {
24 | return m_context.render(templt);
25 | }
26 |
27 | render_context::render_context(
28 | const mstch::node& node,
29 | const std::map& partials):
30 | m_partials(partials), m_nodes(1, node), m_node_ptrs(1, &node)
31 | {
32 | m_state.push(std::unique_ptr(new outside_section));
33 | }
34 |
35 | const mstch::node& render_context::find_node(
36 | const std::string& token,
37 | std::list current_nodes)
38 | {
39 | if (token != "." && token.find('.') != std::string::npos)
40 | return find_node(token.substr(token.rfind('.') + 1),
41 | {&find_node(token.substr(0, token.rfind('.')), current_nodes)});
42 | else
43 | for (auto& node: current_nodes)
44 | if (visit(has_token(token), *node))
45 | return visit(get_token(token, *node), *node);
46 | return null_node;
47 | }
48 |
49 | const mstch::node& render_context::get_node(const std::string& token) {
50 | return find_node(token, m_node_ptrs);
51 | }
52 |
53 | std::string render_context::render(
54 | const template_type& templt, const std::string& prefix)
55 | {
56 | std::string output;
57 | bool prev_eol = true;
58 | for (auto& token: templt) {
59 | if (prev_eol && prefix.length() != 0)
60 | output += m_state.top()->render(*this, {prefix});
61 | output += m_state.top()->render(*this, token);
62 | prev_eol = token.eol();
63 | }
64 | return output;
65 | }
66 |
67 | std::string render_context::render_partial(
68 | const std::string& partial_name, const std::string& prefix)
69 | {
70 | return m_partials.count(partial_name) ?
71 | render(m_partials.at(partial_name), prefix) : "";
72 | }
73 |
--------------------------------------------------------------------------------
/src/render_context.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "mstch/mstch.hpp"
10 | #include "state/render_state.hpp"
11 | #include "template_type.hpp"
12 |
13 | namespace mstch {
14 |
15 | class render_context {
16 | public:
17 | class push {
18 | public:
19 | push(render_context& context, const mstch::node& node = {});
20 | ~push();
21 | std::string render(const template_type& templt);
22 | private:
23 | render_context& m_context;
24 | };
25 |
26 | render_context(
27 | const mstch::node& node,
28 | const std::map& partials);
29 | const mstch::node& get_node(const std::string& token);
30 | std::string render(
31 | const template_type& templt, const std::string& prefix = "");
32 | std::string render_partial(
33 | const std::string& partial_name, const std::string& prefix);
34 | template
35 | void set_state(Args&& ... args) {
36 | m_state.top() = std::unique_ptr(
37 | new T(std::forward(args)...));
38 | }
39 |
40 | private:
41 | static const mstch::node null_node;
42 | const mstch::node& find_node(
43 | const std::string& token,
44 | std::list current_nodes);
45 | std::map m_partials;
46 | std::deque m_nodes;
47 | std::list m_node_ptrs;
48 | std::stack> m_state;
49 | };
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/state/in_section.cpp:
--------------------------------------------------------------------------------
1 | #include "in_section.hpp"
2 | #include "outside_section.hpp"
3 | #include "visitor/is_node_empty.hpp"
4 | #include "visitor/render_section.hpp"
5 |
6 | using namespace mstch;
7 |
8 | in_section::in_section(type type, const token& start_token):
9 | m_type(type), m_start_token(start_token), m_skipped_openings(0)
10 | {
11 | }
12 |
13 | std::string in_section::render(render_context& ctx, const token& token) {
14 | if (token.token_type() == token::type::section_close)
15 | if (token.name() == m_start_token.name() && m_skipped_openings == 0) {
16 | auto& node = ctx.get_node(m_start_token.name());
17 | std::string out;
18 |
19 | if (m_type == type::normal && !visit(is_node_empty(), node))
20 | out = visit(render_section(ctx, m_section, m_start_token.delims()), node);
21 | else if (m_type == type::inverted && visit(is_node_empty(), node))
22 | out = render_context::push(ctx).render(m_section);
23 |
24 | ctx.set_state();
25 | return out;
26 | } else
27 | m_skipped_openings--;
28 | else if (token.token_type() == token::type::inverted_section_open ||
29 | token.token_type() == token::type::section_open)
30 | m_skipped_openings++;
31 |
32 | m_section << token;
33 | return "";
34 | }
35 |
--------------------------------------------------------------------------------
/src/state/in_section.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | #include "render_state.hpp"
7 | #include "template_type.hpp"
8 |
9 | namespace mstch {
10 |
11 | class in_section: public render_state {
12 | public:
13 | enum class type { inverted, normal };
14 | in_section(type type, const token& start_token);
15 | std::string render(render_context& context, const token& token) override;
16 |
17 | private:
18 | const type m_type;
19 | const token& m_start_token;
20 | template_type m_section;
21 | int m_skipped_openings;
22 | };
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/state/outside_section.cpp:
--------------------------------------------------------------------------------
1 | #include "outside_section.hpp"
2 |
3 | #include "visitor/render_node.hpp"
4 | #include "in_section.hpp"
5 | #include "render_context.hpp"
6 |
7 | using namespace mstch;
8 |
9 | std::string outside_section::render(
10 | render_context& ctx, const token& token)
11 | {
12 | using flag = render_node::flag;
13 | switch (token.token_type()) {
14 | case token::type::section_open:
15 | ctx.set_state(in_section::type::normal, token);
16 | break;
17 | case token::type::inverted_section_open:
18 | ctx.set_state(in_section::type::inverted, token);
19 | break;
20 | case token::type::variable:
21 | return visit(render_node(ctx, flag::escape_html), ctx.get_node(token.name()));
22 | case token::type::unescaped_variable:
23 | return visit(render_node(ctx, flag::none), ctx.get_node(token.name()));
24 | case token::type::text:
25 | return token.raw();
26 | case token::type::partial:
27 | return ctx.render_partial(token.name(), token.partial_prefix());
28 | default:
29 | break;
30 | }
31 | return "";
32 | }
33 |
--------------------------------------------------------------------------------
/src/state/outside_section.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "render_state.hpp"
4 |
5 | namespace mstch {
6 |
7 | class outside_section: public render_state {
8 | public:
9 | std::string render(render_context& context, const token& token) override;
10 | };
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/state/render_state.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include "token.hpp"
6 |
7 | namespace mstch {
8 |
9 | class render_context;
10 |
11 | class render_state {
12 | public:
13 | virtual ~render_state() {}
14 | virtual std::string render(render_context& context, const token& token) = 0;
15 | };
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/template_type.cpp:
--------------------------------------------------------------------------------
1 | #include "template_type.hpp"
2 |
3 | using namespace mstch;
4 |
5 | template_type::template_type(const std::string& str, const delim_type& delims):
6 | m_open(delims.first), m_close(delims.second)
7 | {
8 | tokenize(str);
9 | strip_whitespace();
10 | }
11 |
12 | template_type::template_type(const std::string& str):
13 | m_open("{{"), m_close("}}")
14 | {
15 | tokenize(str);
16 | strip_whitespace();
17 | }
18 |
19 | void template_type::process_text(citer begin, citer end) {
20 | if (begin == end)
21 | return;
22 | auto start = begin;
23 | for (auto it = begin; it != end; ++it)
24 | if (*it == '\n' || it == end - 1) {
25 | m_tokens.push_back({{start, it + 1}});
26 | start = it + 1;
27 | }
28 | }
29 |
30 | void template_type::tokenize(const std::string& tmp) {
31 | citer beg = tmp.begin();
32 | auto npos = std::string::npos;
33 |
34 | for (std::size_t cur_pos = 0; cur_pos < tmp.size();) {
35 | auto open_pos = tmp.find(m_open, cur_pos);
36 | auto close_pos = tmp.find(
37 | m_close, open_pos == npos ? open_pos : open_pos + 1);
38 |
39 | if (close_pos != npos && open_pos != npos) {
40 | if (*(beg + open_pos + m_open.size()) == '{' &&
41 | *(beg + close_pos + m_close.size()) == '}')
42 | ++close_pos;
43 |
44 | process_text(beg + cur_pos, beg + open_pos);
45 | cur_pos = close_pos + m_close.size();
46 | m_tokens.push_back({{beg + open_pos, beg + close_pos + m_close.size()},
47 | m_open.size(), m_close.size()});
48 |
49 | if (cur_pos == tmp.size()) {
50 | m_tokens.push_back({{""}});
51 | m_tokens.back().eol(true);
52 | }
53 |
54 | if (*(beg + open_pos + m_open.size()) == '=' &&
55 | *(beg + close_pos - 1) == '=')
56 | {
57 | auto tok_beg = beg + open_pos + m_open.size() + 1;
58 | auto tok_end = beg + close_pos - 1;
59 | auto front_skip = first_not_ws(tok_beg, tok_end);
60 | auto back_skip = first_not_ws(reverse(tok_end), reverse(tok_beg));
61 | m_open = {front_skip, beg + tmp.find(' ', front_skip - beg)};
62 | m_close = {beg + tmp.rfind(' ', back_skip - beg) + 1, back_skip + 1};
63 | }
64 | } else {
65 | process_text(beg + cur_pos, tmp.end());
66 | cur_pos = close_pos;
67 | }
68 | }
69 | }
70 |
71 | void template_type::strip_whitespace() {
72 | auto line_begin = m_tokens.begin();
73 | bool has_tag = false, non_space = false;
74 |
75 | for (auto it = m_tokens.begin(); it != m_tokens.end(); ++it) {
76 | auto type = (*it).token_type();
77 | if (type != token::type::text && type != token::type::variable &&
78 | type != token::type::unescaped_variable)
79 | has_tag = true;
80 | else if (!(*it).ws_only())
81 | non_space = true;
82 |
83 | if ((*it).eol()) {
84 | if (has_tag && !non_space) {
85 | store_prefixes(line_begin);
86 |
87 | auto c = line_begin;
88 | for (bool end = false; !end; (*c).ws_only() ? c = m_tokens.erase(c) : ++c)
89 | if ((end = (*c).eol()))
90 | it = c - 1;
91 | }
92 |
93 | non_space = has_tag = false;
94 | line_begin = it + 1;
95 | }
96 | }
97 | }
98 |
99 | void template_type::store_prefixes(std::vector::iterator beg) {
100 | for (auto cur = beg; !(*cur).eol(); ++cur)
101 | if ((*cur).token_type() == token::type::partial &&
102 | cur != beg && (*(cur - 1)).ws_only())
103 | (*cur).partial_prefix((*(cur - 1)).raw());
104 | }
105 |
--------------------------------------------------------------------------------
/src/template_type.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | #include "token.hpp"
7 | #include "utils.hpp"
8 |
9 | namespace mstch {
10 |
11 | class template_type {
12 | public:
13 | template_type() = default;
14 | template_type(const std::string& str);
15 | template_type(const std::string& str, const delim_type& delims);
16 | std::vector::const_iterator begin() const { return m_tokens.begin(); }
17 | std::vector::const_iterator end() const { return m_tokens.end(); }
18 | void operator<<(const token& token) { m_tokens.push_back(token); }
19 |
20 | private:
21 | std::vector m_tokens;
22 | std::string m_open;
23 | std::string m_close;
24 | void strip_whitespace();
25 | void process_text(citer beg, citer end);
26 | void tokenize(const std::string& tmp);
27 | void store_prefixes(std::vector::iterator beg);
28 | };
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/token.cpp:
--------------------------------------------------------------------------------
1 | #include "token.hpp"
2 | #include "utils.hpp"
3 |
4 | using namespace mstch;
5 |
6 | token::type token::token_info(char c) {
7 | switch (c) {
8 | case '>': return type::partial;
9 | case '^': return type::inverted_section_open;
10 | case '/': return type::section_close;
11 | case '&': return type::unescaped_variable;
12 | case '#': return type::section_open;
13 | case '!': return type::comment;
14 | default: return type::variable;
15 | }
16 | }
17 |
18 | token::token(const std::string& str, std::size_t left, std::size_t right):
19 | m_raw(str), m_eol(false), m_ws_only(false)
20 | {
21 | if (left != 0 && right != 0) {
22 | if (str[left] == '=' && str[str.size() - right - 1] == '=') {
23 | m_type = type::delimiter_change;
24 | } else if (str[left] == '{' && str[str.size() - right - 1] == '}') {
25 | m_type = type::unescaped_variable;
26 | m_name = {first_not_ws(str.begin() + left + 1, str.end() - right),
27 | first_not_ws(str.rbegin() + 1 + right, str.rend() - left) + 1};
28 | } else {
29 | auto c = first_not_ws(str.begin() + left, str.end() - right);
30 | m_type = token_info(*c);
31 | if (m_type != type::variable)
32 | c = first_not_ws(c + 1, str.end() - right);
33 | m_name = {c, first_not_ws(str.rbegin() + right, str.rend() - left) + 1};
34 | m_delims = {{str.begin(), str.begin() + left},
35 | {str.end() - right, str.end()}};
36 | }
37 | } else {
38 | m_type = type::text;
39 | m_eol = (str.size() > 0 && str[str.size() - 1] == '\n');
40 | m_ws_only = (str.find_first_not_of(" \r\n\t") == std::string::npos);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/token.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace mstch {
6 |
7 | using delim_type = std::pair;
8 |
9 | class token {
10 | public:
11 | enum class type {
12 | text, variable, section_open, section_close, inverted_section_open,
13 | unescaped_variable, comment, partial, delimiter_change
14 | };
15 | token(const std::string& str, std::size_t left = 0, std::size_t right = 0);
16 | type token_type() const { return m_type; };
17 | const std::string& raw() const { return m_raw; };
18 | const std::string& name() const { return m_name; };
19 | const std::string& partial_prefix() const { return m_partial_prefix; };
20 | const delim_type& delims() const { return m_delims; };
21 | void partial_prefix(const std::string& p_partial_prefix) {
22 | m_partial_prefix = p_partial_prefix;
23 | };
24 | bool eol() const { return m_eol; }
25 | void eol(bool eol) { m_eol = eol; }
26 | bool ws_only() const { return m_ws_only; }
27 |
28 | private:
29 | type m_type;
30 | std::string m_name;
31 | std::string m_raw;
32 | std::string m_partial_prefix;
33 | delim_type m_delims;
34 | bool m_eol;
35 | bool m_ws_only;
36 | type token_info(char c);
37 | };
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/utils.cpp:
--------------------------------------------------------------------------------
1 | #include "utils.hpp"
2 | #include "mstch/mstch.hpp"
3 |
4 | mstch::citer mstch::first_not_ws(mstch::citer begin, mstch::citer end) {
5 | for (auto it = begin; it != end; ++it)
6 | if (*it != ' ') return it;
7 | return end;
8 | }
9 |
10 | mstch::citer mstch::first_not_ws(mstch::criter begin, mstch::criter end) {
11 | for (auto rit = begin; rit != end; ++rit)
12 | if (*rit != ' ') return --(rit.base());
13 | return --(end.base());
14 | }
15 |
16 | mstch::criter mstch::reverse(mstch::citer it) {
17 | return std::reverse_iterator(it);
18 | }
19 |
20 | std::string mstch::html_escape(const std::string& str) {
21 | if (mstch::config::escape)
22 | return mstch::config::escape(str);
23 |
24 | std::string out;
25 | citer start = str.begin();
26 |
27 | auto add_escape = [&out, &start](const std::string& escaped, citer& it) {
28 | out += std::string{start, it} + escaped;
29 | start = it + 1;
30 | };
31 |
32 | for (auto it = str.begin(); it != str.end(); ++it)
33 | switch (*it) {
34 | case '&': add_escape("&", it); break;
35 | case '\'': add_escape("'", it); break;
36 | case '"': add_escape(""", it); break;
37 | case '<': add_escape("<", it); break;
38 | case '>': add_escape(">", it); break;
39 | case '/': add_escape("/", it); break;
40 | default: break;
41 | }
42 |
43 | return out + std::string{start, str.end()};
44 | }
45 |
--------------------------------------------------------------------------------
/src/utils.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | namespace mstch {
7 |
8 | using citer = std::string::const_iterator;
9 | using criter = std::string::const_reverse_iterator;
10 |
11 | citer first_not_ws(citer begin, citer end);
12 | citer first_not_ws(criter begin, criter end);
13 | std::string html_escape(const std::string& str);
14 | criter reverse(citer it);
15 |
16 | template
17 | auto visit(Args&&... args) -> decltype(boost::apply_visitor(
18 | std::forward(args)...))
19 | {
20 | return boost::apply_visitor(std::forward(args)...);
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/visitor/get_token.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include "mstch/mstch.hpp"
6 | #include "has_token.hpp"
7 |
8 | namespace mstch {
9 |
10 | class get_token: public boost::static_visitor {
11 | public:
12 | get_token(const std::string& token, const mstch::node& node):
13 | m_token(token), m_node(node)
14 | {
15 | }
16 |
17 | template
18 | const mstch::node& operator()(const T&) const {
19 | return m_node;
20 | }
21 |
22 | const mstch::node& operator()(const map& map) const {
23 | return map.at(m_token);
24 | }
25 |
26 | const mstch::node& operator()(const std::shared_ptr& object) const {
27 | return object->at(m_token);
28 | }
29 |
30 | private:
31 | const std::string& m_token;
32 | const mstch::node& m_node;
33 | };
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/visitor/has_token.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include "mstch/mstch.hpp"
6 |
7 | namespace mstch {
8 |
9 | class has_token: public boost::static_visitor {
10 | public:
11 | has_token(const std::string& token): m_token(token) {
12 | }
13 |
14 | template
15 | bool operator()(const T&) const {
16 | return m_token == ".";
17 | }
18 |
19 | bool operator()(const map& map) const {
20 | return map.count(m_token) == 1;
21 | }
22 |
23 | bool operator()(const std::shared_ptr& object) const {
24 | return object->has(m_token);
25 | }
26 |
27 | private:
28 | const std::string& m_token;
29 | };
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/visitor/is_node_empty.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include "mstch/mstch.hpp"
6 |
7 | namespace mstch {
8 |
9 | class is_node_empty: public boost::static_visitor {
10 | public:
11 | template
12 | bool operator()(const T&) const {
13 | return false;
14 | }
15 |
16 | bool operator()(const std::nullptr_t&) const {
17 | return true;
18 | }
19 |
20 | bool operator()(const int& value) const {
21 | return value == 0;
22 | }
23 |
24 | bool operator()(const double& value) const {
25 | return value == 0;
26 | }
27 |
28 | bool operator()(const bool& value) const {
29 | return !value;
30 | }
31 |
32 | bool operator()(const std::string& value) const {
33 | return value == "";
34 | }
35 |
36 | bool operator()(const array& array) const {
37 | return array.size() == 0;
38 | }
39 | };
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/visitor/render_node.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | #include "render_context.hpp"
7 | #include "mstch/mstch.hpp"
8 | #include "utils.hpp"
9 |
10 | namespace mstch {
11 |
12 | class render_node: public boost::static_visitor {
13 | public:
14 | enum class flag { none, escape_html };
15 | render_node(render_context& ctx, flag p_flag = flag::none):
16 | m_ctx(ctx), m_flag(p_flag)
17 | {
18 | }
19 |
20 | template
21 | std::string operator()(const T&) const {
22 | return "";
23 | }
24 |
25 | std::string operator()(const int& value) const {
26 | return std::to_string(value);
27 | }
28 |
29 | std::string operator()(const double& value) const {
30 | std::stringstream ss;
31 | ss << value;
32 | return ss.str();
33 | }
34 |
35 | std::string operator()(const bool& value) const {
36 | return value ? "true" : "false";
37 | }
38 |
39 | std::string operator()(const lambda& value) const {
40 | template_type interpreted{value([this](const mstch::node& n) {
41 | return visit(render_node(m_ctx), n);
42 | })};
43 | auto rendered = render_context::push(m_ctx).render(interpreted);
44 | return (m_flag == flag::escape_html) ? html_escape(rendered) : rendered;
45 | }
46 |
47 | std::string operator()(const std::string& value) const {
48 | return (m_flag == flag::escape_html) ? html_escape(value) : value;
49 | }
50 |
51 | private:
52 | render_context& m_ctx;
53 | flag m_flag;
54 | };
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/visitor/render_section.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #include "render_context.hpp"
6 | #include "mstch/mstch.hpp"
7 | #include "utils.hpp"
8 | #include "render_node.hpp"
9 |
10 | namespace mstch {
11 |
12 | class render_section: public boost::static_visitor {
13 | public:
14 | enum class flag { none, keep_array };
15 | render_section(
16 | render_context& ctx,
17 | const template_type& section,
18 | const delim_type& delims,
19 | flag p_flag = flag::none):
20 | m_ctx(ctx), m_section(section), m_delims(delims), m_flag(p_flag)
21 | {
22 | }
23 |
24 | template
25 | std::string operator()(const T& t) const {
26 | return render_context::push(m_ctx, t).render(m_section);
27 | }
28 |
29 | std::string operator()(const lambda& fun) const {
30 | std::string section_str;
31 | for (auto& token: m_section)
32 | section_str += token.raw();
33 | template_type interpreted{fun([this](const mstch::node& n) {
34 | return visit(render_node(m_ctx), n);
35 | }, section_str), m_delims};
36 | return render_context::push(m_ctx).render(interpreted);
37 | }
38 |
39 | std::string operator()(const array& array) const {
40 | std::string out;
41 | if (m_flag == flag::keep_array)
42 | return render_context::push(m_ctx, array).render(m_section);
43 | else
44 | for (auto& item: array)
45 | out += visit(render_section(
46 | m_ctx, m_section, m_delims, flag::keep_array), item);
47 | return out;
48 | }
49 |
50 | private:
51 | render_context& m_ctx;
52 | const template_type& m_section;
53 | const delim_type& m_delims;
54 | flag m_flag;
55 | };
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/test/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | find_package(Boost 1.54 REQUIRED)
2 |
3 | include_directories(
4 | ${CMAKE_SOURCE_DIR}/include
5 | ${CMAKE_SOURCE_DIR}/vendor/Catch/single_include
6 | ${CMAKE_SOURCE_DIR}/vendor/rapidjson/include
7 | ${Boost_INCLUDE_DIR})
8 |
9 | file(GLOB data_files RELATIVE
10 | "${CMAKE_SOURCE_DIR}/test/data"
11 | "${CMAKE_SOURCE_DIR}/test/data/*.hpp")
12 |
13 | foreach(data_file ${data_files})
14 | string(REGEX REPLACE "\\.hpp" "" test_name "${data_file}")
15 | list(APPEND tests "${test_name}")
16 | endforeach(data_file)
17 |
18 | file(GLOB string_files RELATIVE
19 | "${CMAKE_SOURCE_DIR}/test/data"
20 | "${CMAKE_SOURCE_DIR}/test/data/*.mustache"
21 | "${CMAKE_SOURCE_DIR}/test/data/*.txt"
22 | "${CMAKE_SOURCE_DIR}/test/data/*.partial")
23 |
24 | foreach(string_file ${string_files})
25 | list(APPEND genargs "-i${string_file}")
26 | endforeach(string_file)
27 |
28 | add_custom_command(
29 | OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/test_data.hpp
30 | COMMAND headerize --output ${CMAKE_CURRENT_BINARY_DIR}/test_data.hpp --namespace mstchtest ${genargs}
31 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/test/data/)
32 | set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/test_data.hpp PROPERTIES GENERATED TRUE)
33 | add_custom_target(test_data_hpp DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/test_data.hpp)
34 |
35 | file(GLOB specs_files RELATIVE
36 | "${CMAKE_SOURCE_DIR}/vendor/spec/specs"
37 | "${CMAKE_SOURCE_DIR}/vendor/spec/specs/*.json")
38 |
39 | foreach(specs_file ${specs_files})
40 | list(APPEND specsargs "-i${specs_file}")
41 | string(REGEX REPLACE "\\.json" "" test_name "${specs_file}")
42 | string(REGEX REPLACE "~" "" test_name "${test_name}")
43 | list(APPEND tests "specs_${test_name}")
44 | endforeach(specs_file)
45 |
46 | add_custom_command(
47 | OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/specs_data.hpp
48 | COMMAND headerize --output ${CMAKE_CURRENT_BINARY_DIR}/specs_data.hpp --namespace mstchtest ${specsargs}
49 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/vendor/spec/specs/)
50 | set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/specs_data.hpp PROPERTIES GENERATED TRUE)
51 | add_custom_target(specs_data_hpp DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/specs_data.hpp)
52 |
53 | add_executable(mstch_test test_main.cpp)
54 | target_link_libraries(mstch_test mstch)
55 | add_dependencies(mstch_test test_data_hpp specs_data_hpp)
56 |
57 | foreach(test ${tests})
58 | add_test(NAME ${test} COMMAND mstch_test ${test})
59 | endforeach(test)
60 |
--------------------------------------------------------------------------------
/test/data/ampersand_escape.hpp:
--------------------------------------------------------------------------------
1 | const auto ampersand_escape_data = mstch::map{
2 | {"message", std::string{"Some "}}
3 | };
--------------------------------------------------------------------------------
/test/data/ampersand_escape.mustache:
--------------------------------------------------------------------------------
1 | {{&message}}
2 |
--------------------------------------------------------------------------------
/test/data/ampersand_escape.txt:
--------------------------------------------------------------------------------
1 | Some
2 |
--------------------------------------------------------------------------------
/test/data/apostrophe.hpp:
--------------------------------------------------------------------------------
1 | const auto apostrophe_data = mstch::map{
2 | {"apos", std::string{"'"}},
3 | {"control", std::string{"X"}}
4 | };
--------------------------------------------------------------------------------
/test/data/apostrophe.mustache:
--------------------------------------------------------------------------------
1 | {{apos}}{{control}}
2 |
--------------------------------------------------------------------------------
/test/data/apostrophe.txt:
--------------------------------------------------------------------------------
1 | 'X
2 |
--------------------------------------------------------------------------------
/test/data/array_of_strings.hpp:
--------------------------------------------------------------------------------
1 | const auto array_of_strings_data = mstch::map{
2 | {"array_of_strings", mstch::array{std::string{"hello"}, std::string{"world"}}}
3 | };
--------------------------------------------------------------------------------
/test/data/array_of_strings.mustache:
--------------------------------------------------------------------------------
1 | {{#array_of_strings}}{{.}} {{/array_of_strings}}
2 |
--------------------------------------------------------------------------------
/test/data/array_of_strings.txt:
--------------------------------------------------------------------------------
1 | hello world
2 |
--------------------------------------------------------------------------------
/test/data/backslashes.hpp:
--------------------------------------------------------------------------------
1 | const auto backslashes_data = mstch::map{
2 | {"value", std::string{"\\abc"}}
3 | };
--------------------------------------------------------------------------------
/test/data/backslashes.mustache:
--------------------------------------------------------------------------------
1 | * {{value}}
2 | * {{{value}}}
3 | * {{&value}}
4 |
8 |
--------------------------------------------------------------------------------
/test/data/backslashes.txt:
--------------------------------------------------------------------------------
1 | * \abc
2 | * \abc
3 | * \abc
4 |
8 |
--------------------------------------------------------------------------------
/test/data/bug_11_eating_whitespace.hpp:
--------------------------------------------------------------------------------
1 | const auto bug_11_eating_whitespace_data = mstch::map{
2 | {"tag", std::string{"yo"}}
3 | };
--------------------------------------------------------------------------------
/test/data/bug_11_eating_whitespace.mustache:
--------------------------------------------------------------------------------
1 | {{tag}} foo
2 |
--------------------------------------------------------------------------------
/test/data/bug_11_eating_whitespace.txt:
--------------------------------------------------------------------------------
1 | yo foo
2 |
--------------------------------------------------------------------------------
/test/data/bug_length_property.hpp:
--------------------------------------------------------------------------------
1 | const auto bug_length_property_data = mstch::map{
2 | {"length", std::string{"hello"}}
3 | };
--------------------------------------------------------------------------------
/test/data/bug_length_property.mustache:
--------------------------------------------------------------------------------
1 | {{#length}}The length variable is: {{length}}{{/length}}
2 |
--------------------------------------------------------------------------------
/test/data/bug_length_property.txt:
--------------------------------------------------------------------------------
1 | The length variable is: hello
2 |
--------------------------------------------------------------------------------
/test/data/changing_delimiters.hpp:
--------------------------------------------------------------------------------
1 | const mstch::node changing_delimiters_data = mstch::map{
2 | {"foo", std::string{"foooooooooooooo"}},
3 | {"bar", std::string{"bar! "}}
4 | };
5 |
--------------------------------------------------------------------------------
/test/data/changing_delimiters.mustache:
--------------------------------------------------------------------------------
1 | {{=<% %>=}}<% foo %> {{foo}} <%{bar}%> {{{bar}}}
2 |
--------------------------------------------------------------------------------
/test/data/changing_delimiters.txt:
--------------------------------------------------------------------------------
1 | foooooooooooooo {{foo}} bar! {{{bar}}}
2 |
--------------------------------------------------------------------------------
/test/data/comments.hpp:
--------------------------------------------------------------------------------
1 | const mstch::node comments_data = mstch::map{
2 | {"title", mstch::lambda{[]()->mstch::node{return std::string{"A Comedy of Errors"};}}}
3 | };
--------------------------------------------------------------------------------
/test/data/comments.mustache:
--------------------------------------------------------------------------------
1 | {{title}}{{! just something interesting... or not... }}
2 |
--------------------------------------------------------------------------------
/test/data/comments.txt:
--------------------------------------------------------------------------------
1 | A Comedy of Errors
2 |
--------------------------------------------------------------------------------
/test/data/complex.hpp:
--------------------------------------------------------------------------------
1 | class complex_item: public mstch::object {
2 | private:
3 | std::string m_name;
4 | bool m_current;
5 | std::string m_url;
6 | public:
7 | complex_item(const std::string& name, bool current, const std::string& url):
8 | m_name(name), m_current(current), m_url(url)
9 | {
10 | register_methods(this, std::map{
11 | {"name", &complex_item::name}, {"current", &complex_item::current},
12 | {"url", &complex_item::url}, {"link", &complex_item::link}
13 | });
14 | }
15 |
16 | mstch::node current() {
17 | return m_current;
18 | }
19 |
20 | mstch::node url() {
21 | return m_url;
22 | }
23 |
24 | mstch::node name() {
25 | return m_name;
26 | }
27 |
28 | mstch::node link() {
29 | return !m_current;
30 | }
31 | };
32 |
33 | class complex: public mstch::object {
34 | private:
35 | std::string m_header;
36 | mstch::array m_item;
37 | public:
38 | complex():
39 | m_header("Colors"),
40 | m_item(mstch::array{
41 | std::make_shared("red", true, "#Red"),
42 | std::make_shared("green", false, "#Green"),
43 | std::make_shared("blue", false, "#Blue")
44 | })
45 | {
46 | register_methods(this, std::map{
47 | {"header", &complex::header}, {"item", &complex::item},
48 | {"list", &complex::list}, {"empty", &complex::empty}
49 | });
50 | }
51 |
52 | mstch::node header() {
53 | return m_header;
54 | }
55 |
56 | mstch::node item() {
57 | return m_item;
58 | }
59 |
60 | mstch::node list() {
61 | return m_item.size() != 0;
62 | }
63 |
64 | mstch::node empty() {
65 | return m_item.size() == 0;
66 | }
67 | };
68 |
69 | const auto complex_data = std::make_shared();
--------------------------------------------------------------------------------
/test/data/complex.mustache:
--------------------------------------------------------------------------------
1 | {{header}}
2 | {{#list}}
3 |
4 | {{#item}}
5 | {{#current}}
6 | {{name}}
7 | {{/current}}
8 | {{#link}}
9 | {{name}}
10 | {{/link}}
11 | {{/item}}
12 |
13 | {{/list}}
14 | {{#empty}}
15 | The list is empty.
16 | {{/empty}}
17 |
--------------------------------------------------------------------------------
/test/data/complex.txt:
--------------------------------------------------------------------------------
1 | Colors
2 |
7 |
--------------------------------------------------------------------------------
/test/data/context_lookup.hpp:
--------------------------------------------------------------------------------
1 | const auto context_lookup_data = mstch::map{
2 | {"outer", mstch::map{
3 | {"id", 1},
4 | {"second", mstch::map{
5 | {"nothing", 2}
6 | }}
7 | }}
8 | };
--------------------------------------------------------------------------------
/test/data/context_lookup.mustache:
--------------------------------------------------------------------------------
1 | {{#outer}}{{#second}}{{id}}{{/second}}{{/outer}}
2 |
--------------------------------------------------------------------------------
/test/data/context_lookup.txt:
--------------------------------------------------------------------------------
1 | 1
2 |
--------------------------------------------------------------------------------
/test/data/delimiters.hpp:
--------------------------------------------------------------------------------
1 | const mstch::node delimiters_data = mstch::map{
2 | {"first", std::string{"It worked the first time."}},
3 | {"second", std::string{"And it worked the second time."}},
4 | {"third", std::string{"Then, surprisingly, it worked the third time."}},
5 | {"fourth", std::string{"Fourth time also fine!."}}
6 | };
7 |
--------------------------------------------------------------------------------
/test/data/delimiters.mustache:
--------------------------------------------------------------------------------
1 | {{=<% %>=}}*
2 | <% first %>
3 | * <% second %>
4 | <%=| |=%>
5 | * | third |
6 | |={{ }}=|
7 | * {{ fourth }}
8 |
--------------------------------------------------------------------------------
/test/data/delimiters.txt:
--------------------------------------------------------------------------------
1 | *
2 | It worked the first time.
3 | * And it worked the second time.
4 | * Then, surprisingly, it worked the third time.
5 | * Fourth time also fine!.
6 |
--------------------------------------------------------------------------------
/test/data/disappearing_whitespace.hpp:
--------------------------------------------------------------------------------
1 | const auto disappearing_whitespace_data = mstch::map{
2 | {"bedrooms", true},
3 | {"total", 1}
4 | };
--------------------------------------------------------------------------------
/test/data/disappearing_whitespace.mustache:
--------------------------------------------------------------------------------
1 | {{#bedrooms}}{{total}}{{/bedrooms}} BED
2 |
--------------------------------------------------------------------------------
/test/data/disappearing_whitespace.txt:
--------------------------------------------------------------------------------
1 | 1 BED
2 |
--------------------------------------------------------------------------------
/test/data/dot_notation.hpp:
--------------------------------------------------------------------------------
1 | class dot_notation_price: public mstch::object {
2 | private:
3 | int m_value;
4 | mstch::map m_currency;
5 | public:
6 | dot_notation_price():
7 | m_value(200), m_currency(mstch::map{{"symbol", std::string{"$"}}, {"name", std::string{"USD"}}})
8 | {
9 | register_methods(this, std::map{
10 | {"value", &dot_notation_price::value},
11 | {"vat", &dot_notation_price::vat},
12 | {"currency", &dot_notation_price::currency}});
13 | }
14 |
15 | mstch::node value() {
16 | return m_value;
17 | }
18 |
19 | mstch::node vat() {
20 | return m_value * 0.2;
21 | }
22 |
23 | mstch::node currency() {
24 | return m_currency;
25 | }
26 | };
27 |
28 | const auto dot_notation_data = mstch::map{
29 | {"name", std::string{"A Book"}},
30 | {"authors", mstch::array{std::string{"John Power"}, std::string{"Jamie Walsh"}}},
31 | {"price", std::make_shared()},
32 | {"availability", mstch::map{{"status", true}, {"text", std::string{"In Stock"}}}},
33 | {"truthy", mstch::map{{"zero", 0}, {"notTrue", false}}}
34 | };
--------------------------------------------------------------------------------
/test/data/dot_notation.mustache:
--------------------------------------------------------------------------------
1 |
2 | {{name}}
3 | Authors:
{{#authors}}{{.}} {{/authors}}
4 | Price: {{{price.currency.symbol}}}{{price.value}} {{#price.currency}}{{name}} {{availability.text}} {{/price.currency}}
5 | VAT: {{{price.currency.symbol}}}{{#price}}{{vat}}{{/price}}
6 |
7 | Test truthy false values:
8 | Zero: {{truthy.zero}}
9 | False: {{truthy.notTrue}}
10 |
--------------------------------------------------------------------------------
/test/data/dot_notation.txt:
--------------------------------------------------------------------------------
1 |
2 | A Book
3 | Authors:
4 | Price: $200 USD In Stock
5 | VAT: $40
6 |
7 | Test truthy false values:
8 | Zero: 0
9 | False: false
10 |
--------------------------------------------------------------------------------
/test/data/double_render.hpp:
--------------------------------------------------------------------------------
1 | const auto double_render_data = mstch::map{
2 | {"foo", true},
3 | {"bar", std::string{"{{win}}"}},
4 | {"win", std::string{"FAIL"}}
5 | };
--------------------------------------------------------------------------------
/test/data/double_render.mustache:
--------------------------------------------------------------------------------
1 | {{#foo}}{{bar}}{{/foo}}
2 |
--------------------------------------------------------------------------------
/test/data/double_render.txt:
--------------------------------------------------------------------------------
1 | {{win}}
2 |
--------------------------------------------------------------------------------
/test/data/empty_list.hpp:
--------------------------------------------------------------------------------
1 | const auto empty_list_data = mstch::map{
2 | {"jobs", mstch::array{}}
3 | };
--------------------------------------------------------------------------------
/test/data/empty_list.mustache:
--------------------------------------------------------------------------------
1 | These are the jobs:
2 | {{#jobs}}
3 | {{.}}
4 | {{/jobs}}
5 |
--------------------------------------------------------------------------------
/test/data/empty_list.txt:
--------------------------------------------------------------------------------
1 | These are the jobs:
2 |
--------------------------------------------------------------------------------
/test/data/empty_sections.hpp:
--------------------------------------------------------------------------------
1 | const auto empty_sections_data = mstch::map{};
--------------------------------------------------------------------------------
/test/data/empty_sections.mustache:
--------------------------------------------------------------------------------
1 | {{#foo}}{{/foo}}foo{{#bar}}{{/bar}}
2 |
--------------------------------------------------------------------------------
/test/data/empty_sections.txt:
--------------------------------------------------------------------------------
1 | foo
2 |
--------------------------------------------------------------------------------
/test/data/empty_string.hpp:
--------------------------------------------------------------------------------
1 | const auto empty_string_data = mstch::map{
2 | {"description", std::string{"That is all!"}},
3 | {"child", mstch::map{
4 | {"description", std::string{""}}
5 | }}
6 | };
--------------------------------------------------------------------------------
/test/data/empty_string.mustache:
--------------------------------------------------------------------------------
1 | {{description}}{{#child}}{{description}}{{/child}}
2 |
--------------------------------------------------------------------------------
/test/data/empty_string.txt:
--------------------------------------------------------------------------------
1 | That is all!
2 |
--------------------------------------------------------------------------------
/test/data/empty_template.hpp:
--------------------------------------------------------------------------------
1 | const auto empty_template_data = mstch::map{};
--------------------------------------------------------------------------------
/test/data/empty_template.mustache:
--------------------------------------------------------------------------------
1 | Test
--------------------------------------------------------------------------------
/test/data/empty_template.txt:
--------------------------------------------------------------------------------
1 | Test
--------------------------------------------------------------------------------
/test/data/error_eof_in_section.hpp:
--------------------------------------------------------------------------------
1 | const auto error_eof_in_section_data = mstch::map{
2 | {"hello", mstch::array{std::string{"a"}, std::string{"b"}}}
3 | };
--------------------------------------------------------------------------------
/test/data/error_eof_in_section.mustache:
--------------------------------------------------------------------------------
1 | yay{{#hello}}{{.}}
--------------------------------------------------------------------------------
/test/data/error_eof_in_section.txt:
--------------------------------------------------------------------------------
1 | yay
--------------------------------------------------------------------------------
/test/data/error_eof_in_tag.hpp:
--------------------------------------------------------------------------------
1 | const auto error_eof_in_tag_data = mstch::map{{"hello", std::string{"world"}}};
--------------------------------------------------------------------------------
/test/data/error_eof_in_tag.mustache:
--------------------------------------------------------------------------------
1 | {{hello{{hello}}{{hello
--------------------------------------------------------------------------------
/test/data/error_eof_in_tag.txt:
--------------------------------------------------------------------------------
1 | {{hello
--------------------------------------------------------------------------------
/test/data/error_not_found.hpp:
--------------------------------------------------------------------------------
1 | const auto error_not_found_data = mstch::map{
2 | {"bar", 2}
3 | };
--------------------------------------------------------------------------------
/test/data/error_not_found.mustache:
--------------------------------------------------------------------------------
1 | {{foo}}
--------------------------------------------------------------------------------
/test/data/error_not_found.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/no1msd/mstch/0fde1cf94c26ede7fa267f4b64c0efe5da81a77a/test/data/error_not_found.txt
--------------------------------------------------------------------------------
/test/data/escaped.hpp:
--------------------------------------------------------------------------------
1 | const mstch::node escaped_data = mstch::map{
2 | {"title", mstch::lambda{[]()->mstch::node{ return std::string{"Bear > Shark"}; }}},
3 | {"entities", mstch::lambda{[]()->mstch::node{ return std::string{"" \"'<>/"}; }}}
4 | };
--------------------------------------------------------------------------------
/test/data/escaped.mustache:
--------------------------------------------------------------------------------
1 | {{title}}
2 | And even {{entities}}, but not {{{entities}}}.
3 |
--------------------------------------------------------------------------------
/test/data/escaped.txt:
--------------------------------------------------------------------------------
1 | Bear > Shark
2 | And even " "'<>/, but not " "'<>/.
3 |
--------------------------------------------------------------------------------
/test/data/falsy.hpp:
--------------------------------------------------------------------------------
1 | const auto falsy_data = mstch::map{
2 | {"emptyString", std::string{""}},
3 | {"emptyArray", mstch::array{}},
4 | {"zero", 0},
5 | {"null", mstch::node{}}
6 | };
--------------------------------------------------------------------------------
/test/data/falsy.mustache:
--------------------------------------------------------------------------------
1 | {{#emptyString}}empty string{{/emptyString}}
2 | {{^emptyString}}inverted empty string{{/emptyString}}
3 | {{#emptyArray}}empty array{{/emptyArray}}
4 | {{^emptyArray}}inverted empty array{{/emptyArray}}
5 | {{#zero}}zero{{/zero}}
6 | {{^zero}}inverted zero{{/zero}}
7 | {{#null}}null{{/null}}
8 | {{^null}}inverted null{{/null}}
9 |
--------------------------------------------------------------------------------
/test/data/falsy.txt:
--------------------------------------------------------------------------------
1 |
2 | inverted empty string
3 |
4 | inverted empty array
5 |
6 | inverted zero
7 |
8 | inverted null
9 |
--------------------------------------------------------------------------------
/test/data/falsy_array.hpp:
--------------------------------------------------------------------------------
1 | const auto falsy_array_data = mstch::map{
2 | {"list", mstch::array{
3 | mstch::array{std::string{""}, std::string{"emptyString"}},
4 | mstch::array{mstch::array{}, std::string{"emptyArray"}},
5 | mstch::array{0, std::string{"zero"}},
6 | mstch::array{mstch::node{}, std::string{"null"}}}
7 | }
8 | };
--------------------------------------------------------------------------------
/test/data/falsy_array.mustache:
--------------------------------------------------------------------------------
1 | {{#list}}
2 | {{#.}}{{#.}}{{.}}{{/.}}{{^.}}inverted {{/.}}{{/.}}
3 | {{/list}}
--------------------------------------------------------------------------------
/test/data/falsy_array.txt:
--------------------------------------------------------------------------------
1 | inverted emptyString
2 | inverted emptyArray
3 | inverted zero
4 | inverted null
5 |
--------------------------------------------------------------------------------
/test/data/grandparent_context.hpp:
--------------------------------------------------------------------------------
1 | const auto grandparent_context_data = mstch::map{
2 | {"grand_parent_id", std::string{"grand_parent1"}},
3 | {"parent_contexts", mstch::array{
4 | mstch::map{
5 | {"parent_id", std::string{"parent1"}},
6 | {"child_contexts", mstch::array{
7 | mstch::map{{"child_id", std::string{"parent1-child1"}}},
8 | mstch::map{{"child_id", std::string{"parent1-child2"}}}
9 | }}
10 | },
11 | mstch::map{
12 | {"parent_id", std::string{"parent2"}},
13 | {"child_contexts", mstch::array{
14 | mstch::map{{"child_id", std::string{"parent2-child1"}}},
15 | mstch::map{{"child_id", std::string{"parent2-child2"}}}
16 | }}
17 | }
18 | }}
19 | };
--------------------------------------------------------------------------------
/test/data/grandparent_context.mustache:
--------------------------------------------------------------------------------
1 | {{grand_parent_id}}
2 | {{#parent_contexts}}
3 | {{grand_parent_id}}
4 | {{parent_id}}
5 | {{#child_contexts}}
6 | {{grand_parent_id}}
7 | {{parent_id}}
8 | {{child_id}}
9 | {{/child_contexts}}
10 | {{/parent_contexts}}
11 |
--------------------------------------------------------------------------------
/test/data/grandparent_context.txt:
--------------------------------------------------------------------------------
1 | grand_parent1
2 | grand_parent1
3 | parent1
4 | grand_parent1
5 | parent1
6 | parent1-child1
7 | grand_parent1
8 | parent1
9 | parent1-child2
10 | grand_parent1
11 | parent2
12 | grand_parent1
13 | parent2
14 | parent2-child1
15 | grand_parent1
16 | parent2
17 | parent2-child2
18 |
--------------------------------------------------------------------------------
/test/data/higher_order_sections.hpp:
--------------------------------------------------------------------------------
1 | class higher_order_sections: public mstch::object {
2 | private:
3 | std::string m_helper;
4 | public:
5 | higher_order_sections(): m_helper("To tinker?") {
6 | register_methods(this, std::map{
7 | {"name", &higher_order_sections::name},
8 | {"helper", &higher_order_sections::helper},
9 | {"bolder", &higher_order_sections::bolder}
10 | });
11 | }
12 |
13 | mstch::node name() {
14 | return std::string{"Tater"};
15 | }
16 |
17 | mstch::node helper() {
18 | return m_helper;
19 | }
20 |
21 | mstch::node bolder() {
22 | return mstch::lambda{[this](const std::string& text) -> mstch::node {
23 | return "" + text + " " + m_helper;
24 | }};
25 | }
26 | };
27 |
28 | const mstch::node higher_order_sections_data = std::make_shared();
--------------------------------------------------------------------------------
/test/data/higher_order_sections.mustache:
--------------------------------------------------------------------------------
1 | {{#bolder}}Hi {{name}}.{{/bolder}}
2 |
--------------------------------------------------------------------------------
/test/data/higher_order_sections.txt:
--------------------------------------------------------------------------------
1 | Hi Tater. To tinker?
2 |
--------------------------------------------------------------------------------
/test/data/implicit_iterator.hpp:
--------------------------------------------------------------------------------
1 | const auto implicit_iterator_data = mstch::map{
2 | {"data", mstch::map{
3 | {"author", mstch::map{
4 | {"twitter_id", 819606},
5 | {"name", std::string{"janl"}}
6 | }}
7 | }}
8 | };
9 |
--------------------------------------------------------------------------------
/test/data/implicit_iterator.mustache:
--------------------------------------------------------------------------------
1 | {{# data.author.twitter_id }}
2 |
3 | {{/ data.author.twitter_id }}
4 |
5 | {{# data.author.name }}
6 |
7 | {{/ data.author.name }}
8 |
--------------------------------------------------------------------------------
/test/data/implicit_iterator.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/test/data/included_tag.hpp:
--------------------------------------------------------------------------------
1 | const auto included_tag_data = mstch::map{
2 | {"html", std::string{"I like {{mustache}}"}}
3 | };
--------------------------------------------------------------------------------
/test/data/included_tag.mustache:
--------------------------------------------------------------------------------
1 | You said "{{{html}}}" today
2 |
--------------------------------------------------------------------------------
/test/data/included_tag.txt:
--------------------------------------------------------------------------------
1 | You said "I like {{mustache}}" today
2 |
--------------------------------------------------------------------------------
/test/data/inverted_section.hpp:
--------------------------------------------------------------------------------
1 | const auto inverted_section_data = mstch::map{
2 | {"repos", mstch::array{}}
3 | };
--------------------------------------------------------------------------------
/test/data/inverted_section.mustache:
--------------------------------------------------------------------------------
1 | {{#repos}}{{name}} {{/repos}}
2 | {{^repos}}No repos :({{/repos}}
3 | {{^nothin}}Hello!{{/nothin}}
4 |
--------------------------------------------------------------------------------
/test/data/inverted_section.txt:
--------------------------------------------------------------------------------
1 |
2 | No repos :(
3 | Hello!
4 |
--------------------------------------------------------------------------------
/test/data/keys_with_questionmarks.hpp:
--------------------------------------------------------------------------------
1 | const auto keys_with_questionmarks_data = mstch::map{
2 | {"person?", mstch::map{
3 | {"name", std::string{"Jon"}}
4 | }}
5 | };
--------------------------------------------------------------------------------
/test/data/keys_with_questionmarks.mustache:
--------------------------------------------------------------------------------
1 | {{#person?}}
2 | Hi {{name}}!
3 | {{/person?}}
4 |
--------------------------------------------------------------------------------
/test/data/keys_with_questionmarks.txt:
--------------------------------------------------------------------------------
1 | Hi Jon!
2 |
--------------------------------------------------------------------------------
/test/data/multiline_comment.hpp:
--------------------------------------------------------------------------------
1 | const auto multiline_comment_data = mstch::map{};
--------------------------------------------------------------------------------
/test/data/multiline_comment.mustache:
--------------------------------------------------------------------------------
1 | {{!
2 |
3 | This is a multi-line comment.
4 |
5 | }}
6 | Hello world!
7 |
--------------------------------------------------------------------------------
/test/data/multiline_comment.txt:
--------------------------------------------------------------------------------
1 | Hello world!
2 |
--------------------------------------------------------------------------------
/test/data/nested_dot.hpp:
--------------------------------------------------------------------------------
1 | const auto nested_dot_data = mstch::map{{"name", std::string{"Bruno"}}};
--------------------------------------------------------------------------------
/test/data/nested_dot.mustache:
--------------------------------------------------------------------------------
1 | {{#name}}Hello {{.}}{{/name}}
--------------------------------------------------------------------------------
/test/data/nested_dot.txt:
--------------------------------------------------------------------------------
1 | Hello Bruno
--------------------------------------------------------------------------------
/test/data/nested_higher_order_sections.hpp:
--------------------------------------------------------------------------------
1 | const mstch::node nested_higher_order_sections_data = mstch::map{
2 | {"bold", mstch::lambda{[](const std::string& text) -> mstch::node {
3 | return std::string{""} + text + std::string{" "};
4 | }}},
5 | {"person", mstch::map{{"name", std::string{"Jonas"}}}}
6 | };
--------------------------------------------------------------------------------
/test/data/nested_higher_order_sections.mustache:
--------------------------------------------------------------------------------
1 | {{#bold}}{{#person}}My name is {{name}}!{{/person}}{{/bold}}
2 |
--------------------------------------------------------------------------------
/test/data/nested_higher_order_sections.txt:
--------------------------------------------------------------------------------
1 | My name is Jonas!
2 |
--------------------------------------------------------------------------------
/test/data/nested_iterating.hpp:
--------------------------------------------------------------------------------
1 | const auto nested_iterating_data = mstch::map{
2 | {"inner", mstch::array{mstch::map{
3 | {"foo", std::string{"foo"}},
4 | {"inner", mstch::array{mstch::map{
5 | {"bar", std::string{"bar"}}
6 | }}}
7 | }}}
8 | };
--------------------------------------------------------------------------------
/test/data/nested_iterating.mustache:
--------------------------------------------------------------------------------
1 | {{#inner}}{{foo}}{{#inner}}{{bar}}{{/inner}}{{/inner}}
2 |
--------------------------------------------------------------------------------
/test/data/nested_iterating.txt:
--------------------------------------------------------------------------------
1 | foobar
2 |
--------------------------------------------------------------------------------
/test/data/nesting.hpp:
--------------------------------------------------------------------------------
1 | const auto nesting_data = mstch::map{
2 | {"foo", mstch::array{
3 | mstch::map{{"a", mstch::map{{"b", 1}}}},
4 | mstch::map{{"a", mstch::map{{"b", 2}}}},
5 | mstch::map{{"a", mstch::map{{"b", 3}}}}
6 | }}
7 | };
--------------------------------------------------------------------------------
/test/data/nesting.mustache:
--------------------------------------------------------------------------------
1 | {{#foo}}
2 | {{#a}}
3 | {{b}}
4 | {{/a}}
5 | {{/foo}}
6 |
--------------------------------------------------------------------------------
/test/data/nesting.txt:
--------------------------------------------------------------------------------
1 | 1
2 | 2
3 | 3
4 |
--------------------------------------------------------------------------------
/test/data/nesting_same_name.hpp:
--------------------------------------------------------------------------------
1 | const auto nesting_same_name_data = mstch::map{
2 | {"items", mstch::array{
3 | mstch::map{
4 | {"name", std::string{"name"}},
5 | {"items", mstch::array{1, 2, 3, 4}}
6 | }
7 | }}
8 | };
--------------------------------------------------------------------------------
/test/data/nesting_same_name.mustache:
--------------------------------------------------------------------------------
1 | {{#items}}{{name}}{{#items}}{{.}}{{/items}}{{/items}}
2 |
--------------------------------------------------------------------------------
/test/data/nesting_same_name.txt:
--------------------------------------------------------------------------------
1 | name1234
2 |
--------------------------------------------------------------------------------
/test/data/null_lookup_array.hpp:
--------------------------------------------------------------------------------
1 | const auto null_lookup_array_data = mstch::map{
2 | {"name", std::string{"David"}},
3 | {"twitter", std::string{"@dasilvacontin"}},
4 | {"farray", mstch::array{
5 | mstch::array{std::string{"Flor"}, std::string{"@florrts"}},
6 | mstch::array{std::string{"Miquel"}, mstch::node{}},
7 | }}
8 | };
--------------------------------------------------------------------------------
/test/data/null_lookup_array.mustache:
--------------------------------------------------------------------------------
1 | {{#farray}}
2 | {{#.}}{{#.}}{{.}} {{/.}}{{^.}}no twitter{{/.}}{{/.}}
3 | {{/farray}}
4 |
--------------------------------------------------------------------------------
/test/data/null_lookup_array.txt:
--------------------------------------------------------------------------------
1 | Flor @florrts
2 | Miquel no twitter
3 |
--------------------------------------------------------------------------------
/test/data/null_lookup_object.hpp:
--------------------------------------------------------------------------------
1 | const auto null_lookup_object_data = mstch::map{
2 | {"name", std::string{"David"}},
3 | {"twitter", std::string{"@dasilvacontin"}},
4 | {"fobject", mstch::array{
5 | mstch::map{
6 | {"name", std::string{"Flor"}},
7 | {"twitter", std::string{"@florrts"}}
8 | },
9 | mstch::map{
10 | {"name", std::string{"Miquel"}},
11 | {"twitter", mstch::node{}}
12 | }
13 | }}
14 | };
--------------------------------------------------------------------------------
/test/data/null_lookup_object.mustache:
--------------------------------------------------------------------------------
1 | {{#fobject}}
2 | {{name}}'s twitter: {{#twitter}}{{.}}{{/twitter}}{{^twitter}}unknown{{/twitter}}.
3 | {{/fobject}}
4 |
--------------------------------------------------------------------------------
/test/data/null_lookup_object.txt:
--------------------------------------------------------------------------------
1 | Flor's twitter: @florrts.
2 | Miquel's twitter: unknown.
3 |
--------------------------------------------------------------------------------
/test/data/null_string.hpp:
--------------------------------------------------------------------------------
1 | const auto null_string_data = mstch::map{
2 | {"name", std::string{"Elise"}},
3 | {"glytch", true},
4 | {"binary", false},
5 | {"value", mstch::node{}}
6 | };
7 |
--------------------------------------------------------------------------------
/test/data/null_string.mustache:
--------------------------------------------------------------------------------
1 | Hello {{name}}
2 | glytch {{glytch}}
3 | binary {{binary}}
4 | value {{value}}
5 |
--------------------------------------------------------------------------------
/test/data/null_string.txt:
--------------------------------------------------------------------------------
1 | Hello Elise
2 | glytch true
3 | binary false
4 | value
5 |
--------------------------------------------------------------------------------
/test/data/null_view.hpp:
--------------------------------------------------------------------------------
1 | const auto null_view_data = mstch::map{
2 | {"name", std::string{"Joe"}},
3 | {"friends", mstch::node{}}
4 | };
--------------------------------------------------------------------------------
/test/data/null_view.mustache:
--------------------------------------------------------------------------------
1 | {{name}}'s friends: {{#friends}}{{name}}, {{/friends}}
--------------------------------------------------------------------------------
/test/data/null_view.txt:
--------------------------------------------------------------------------------
1 | Joe's friends:
--------------------------------------------------------------------------------
/test/data/partial_array.hpp:
--------------------------------------------------------------------------------
1 | const auto partial_array_data = mstch::map{
2 | {"array", mstch::array{std::string{"1"}, std::string{"2"}, std::string{"3"}, std::string{"4"}}}
3 | };
--------------------------------------------------------------------------------
/test/data/partial_array.mustache:
--------------------------------------------------------------------------------
1 | {{>partial}}
--------------------------------------------------------------------------------
/test/data/partial_array.partial:
--------------------------------------------------------------------------------
1 | Here's a non-sense array of values
2 | {{#array}}
3 | {{.}}
4 | {{/array}}
5 |
--------------------------------------------------------------------------------
/test/data/partial_array.txt:
--------------------------------------------------------------------------------
1 | Here's a non-sense array of values
2 | 1
3 | 2
4 | 3
5 | 4
6 |
--------------------------------------------------------------------------------
/test/data/partial_array_of_partials.hpp:
--------------------------------------------------------------------------------
1 | const auto partial_array_of_partials_data = mstch::map{
2 | {"numbers", mstch::array{
3 | mstch::map{{"i", std::string{"1"}}},
4 | mstch::map{{"i", std::string{"2"}}},
5 | mstch::map{{"i", std::string{"3"}}},
6 | mstch::map{{"i", std::string{"4"}}}
7 | }}
8 | };
--------------------------------------------------------------------------------
/test/data/partial_array_of_partials.mustache:
--------------------------------------------------------------------------------
1 | Here is some stuff!
2 | {{#numbers}}
3 | {{>partial}}
4 | {{/numbers}}
5 |
--------------------------------------------------------------------------------
/test/data/partial_array_of_partials.partial:
--------------------------------------------------------------------------------
1 | {{i}}
2 |
--------------------------------------------------------------------------------
/test/data/partial_array_of_partials.txt:
--------------------------------------------------------------------------------
1 | Here is some stuff!
2 | 1
3 | 2
4 | 3
5 | 4
6 |
--------------------------------------------------------------------------------
/test/data/partial_array_of_partials_implicit.hpp:
--------------------------------------------------------------------------------
1 | const auto partial_array_of_partials_implicit_data = mstch::map{
2 | {"numbers", mstch::array{std::string{"1"}, std::string{"2"}, std::string{"3"}, std::string{"4"}}}
3 | };
--------------------------------------------------------------------------------
/test/data/partial_array_of_partials_implicit.mustache:
--------------------------------------------------------------------------------
1 | Here is some stuff!
2 | {{#numbers}}
3 | {{>partial}}
4 | {{/numbers}}
5 |
--------------------------------------------------------------------------------
/test/data/partial_array_of_partials_implicit.partial:
--------------------------------------------------------------------------------
1 | {{.}}
2 |
--------------------------------------------------------------------------------
/test/data/partial_array_of_partials_implicit.txt:
--------------------------------------------------------------------------------
1 | Here is some stuff!
2 | 1
3 | 2
4 | 3
5 | 4
6 |
--------------------------------------------------------------------------------
/test/data/partial_empty.hpp:
--------------------------------------------------------------------------------
1 | const auto partial_empty_data = mstch::map{
2 | {"foo", 1}
3 | };
--------------------------------------------------------------------------------
/test/data/partial_empty.mustache:
--------------------------------------------------------------------------------
1 | hey {{foo}}
2 | {{>partial}}
3 |
--------------------------------------------------------------------------------
/test/data/partial_empty.partial:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/no1msd/mstch/0fde1cf94c26ede7fa267f4b64c0efe5da81a77a/test/data/partial_empty.partial
--------------------------------------------------------------------------------
/test/data/partial_empty.txt:
--------------------------------------------------------------------------------
1 | hey 1
2 |
--------------------------------------------------------------------------------
/test/data/partial_template.hpp:
--------------------------------------------------------------------------------
1 | const mstch::node partial_template_data = mstch::map{
2 | {"title", mstch::lambda{[]()->mstch::node{ return std::string{"Welcome"}; }}},
3 | {"again", mstch::lambda{[]()->mstch::node{ return std::string{"Goodbye"}; }}},
4 | };
--------------------------------------------------------------------------------
/test/data/partial_template.mustache:
--------------------------------------------------------------------------------
1 | {{title}}
2 | {{>partial}}
3 |
--------------------------------------------------------------------------------
/test/data/partial_template.partial:
--------------------------------------------------------------------------------
1 | Again, {{again}}!
2 |
--------------------------------------------------------------------------------
/test/data/partial_template.txt:
--------------------------------------------------------------------------------
1 | Welcome
2 | Again, Goodbye!
3 |
--------------------------------------------------------------------------------
/test/data/partial_view.hpp:
--------------------------------------------------------------------------------
1 | class partial_view: public mstch::object {
2 | private:
3 | int m_value;
4 |
5 | public:
6 | partial_view(): m_value(10000) {
7 | register_methods(this, std::map{
8 | {"greeting", &partial_view::greeting},
9 | {"farewell", &partial_view::farewell},
10 | {"name", &partial_view::name},
11 | {"value", &partial_view::value},
12 | {"taxed_value", &partial_view::taxed_value},
13 | {"in_ca", &partial_view::in_ca}
14 | });
15 | }
16 |
17 | mstch::node greeting() {
18 | return std::string{"Welcome"};
19 | }
20 |
21 | mstch::node farewell() {
22 | return std::string{"Fair enough, right?"};
23 | }
24 |
25 | mstch::node name() {
26 | return std::string{"Chris"};
27 | }
28 |
29 | mstch::node value() {
30 | return m_value;
31 | }
32 |
33 | mstch::node taxed_value() {
34 | return m_value - (m_value * 0.4);
35 | }
36 |
37 | mstch::node in_ca() {
38 | return true;
39 | }
40 | };
41 |
42 | const auto partial_view_data = std::make_shared();
--------------------------------------------------------------------------------
/test/data/partial_view.mustache:
--------------------------------------------------------------------------------
1 | {{greeting}}
2 | {{>partial}}
3 | {{farewell}}
4 |
--------------------------------------------------------------------------------
/test/data/partial_view.partial:
--------------------------------------------------------------------------------
1 | Hello {{name}}
2 | You have just won ${{value}}!
3 | {{#in_ca}}
4 | Well, ${{ taxed_value }}, after taxes.
5 | {{/in_ca}}
--------------------------------------------------------------------------------
/test/data/partial_view.txt:
--------------------------------------------------------------------------------
1 | Welcome
2 | Hello Chris
3 | You have just won $10000!
4 | Well, $6000, after taxes.
5 | Fair enough, right?
6 |
--------------------------------------------------------------------------------
/test/data/partial_whitespace.hpp:
--------------------------------------------------------------------------------
1 | class partial_whitespace: public mstch::object {
2 | private:
3 | int m_value;
4 | public:
5 | partial_whitespace(): m_value(10000) {
6 | register_methods(this, std::map{
7 | {"greeting", &partial_whitespace::greeting},
8 | {"farewell", &partial_whitespace::farewell},
9 | {"name", &partial_whitespace::name},
10 | {"value", &partial_whitespace::value},
11 | {"taxed_value", &partial_whitespace::taxed_value},
12 | {"in_ca", &partial_whitespace::in_ca}
13 | });
14 | }
15 |
16 | mstch::node greeting() {
17 | return std::string{"Welcome"};
18 | }
19 |
20 | mstch::node farewell() {
21 | return std::string{"Fair enough, right?"};
22 | }
23 |
24 | mstch::node name() {
25 | return std::string{"Chris"};
26 | }
27 |
28 | mstch::node value() {
29 | return m_value;
30 | }
31 |
32 | mstch::node taxed_value() {
33 | return static_cast(m_value - (m_value * 0.4));
34 | }
35 |
36 | mstch::node in_ca() {
37 | return true;
38 | }
39 | };
40 |
41 | const auto partial_whitespace_data = std::make_shared();
--------------------------------------------------------------------------------
/test/data/partial_whitespace.mustache:
--------------------------------------------------------------------------------
1 | {{ greeting }}
2 | {{> partial }}
3 | {{ farewell }}
4 |
--------------------------------------------------------------------------------
/test/data/partial_whitespace.partial:
--------------------------------------------------------------------------------
1 | Hello {{ name}}
2 | You have just won ${{value }}!
3 | {{# in_ca }}
4 | Well, ${{ taxed_value }}, after taxes.
5 | {{/ in_ca }}
--------------------------------------------------------------------------------
/test/data/partial_whitespace.txt:
--------------------------------------------------------------------------------
1 | Welcome
2 | Hello Chris
3 | You have just won $10000!
4 | Well, $6000, after taxes.
5 | Fair enough, right?
6 |
--------------------------------------------------------------------------------
/test/data/recursion_with_same_names.hpp:
--------------------------------------------------------------------------------
1 | const auto recursion_with_same_names_data = mstch::map{
2 | {"name", std::string{"name"}},
3 | {"description", std::string{"desc"}},
4 | {"terms", mstch::array{
5 | mstch::map{{"name", std::string{"t1"}}, {"index", 0}},
6 | mstch::map{{"name", std::string{"t2"}}, {"index", 1}}
7 | }}
8 | };
--------------------------------------------------------------------------------
/test/data/recursion_with_same_names.mustache:
--------------------------------------------------------------------------------
1 | {{ name }}
2 | {{ description }}
3 |
4 | {{#terms}}
5 | {{name}}
6 | {{index}}
7 | {{/terms}}
8 |
--------------------------------------------------------------------------------
/test/data/recursion_with_same_names.txt:
--------------------------------------------------------------------------------
1 | name
2 | desc
3 |
4 | t1
5 | 0
6 | t2
7 | 1
8 |
--------------------------------------------------------------------------------
/test/data/reuse_of_enumerables.hpp:
--------------------------------------------------------------------------------
1 | const auto reuse_of_enumerables_data = mstch::map{
2 | {"terms", mstch::array{
3 | mstch::map{{"name", std::string{"t1"}}, {"index", 0}},
4 | mstch::map{{"name", std::string{"t2"}}, {"index", 1}}
5 | }}
6 | };
--------------------------------------------------------------------------------
/test/data/reuse_of_enumerables.mustache:
--------------------------------------------------------------------------------
1 | {{#terms}}
2 | {{name}}
3 | {{index}}
4 | {{/terms}}
5 | {{#terms}}
6 | {{name}}
7 | {{index}}
8 | {{/terms}}
9 |
--------------------------------------------------------------------------------
/test/data/reuse_of_enumerables.txt:
--------------------------------------------------------------------------------
1 | t1
2 | 0
3 | t2
4 | 1
5 | t1
6 | 0
7 | t2
8 | 1
9 |
--------------------------------------------------------------------------------
/test/data/section_as_context.hpp:
--------------------------------------------------------------------------------
1 | const auto section_as_context_data = mstch::map{
2 | {"a_object", mstch::map{
3 | {"title", std::string{"this is an object"}},
4 | {"description", std::string{"one of its attributes is a list"}},
5 | {"a_list", mstch::array{
6 | mstch::map{{"label", std::string{"listitem1"}}},
7 | mstch::map{{"label", std::string{"listitem2"}}}
8 | }}
9 | }}
10 | };
--------------------------------------------------------------------------------
/test/data/section_as_context.mustache:
--------------------------------------------------------------------------------
1 | {{#a_object}}
2 | {{title}}
3 | {{description}}
4 |
5 | {{#a_list}}
6 | {{label}}
7 | {{/a_list}}
8 |
9 | {{/a_object}}
10 |
--------------------------------------------------------------------------------
/test/data/section_as_context.txt:
--------------------------------------------------------------------------------
1 | this is an object
2 | one of its attributes is a list
3 |
4 | listitem1
5 | listitem2
6 |
7 |
--------------------------------------------------------------------------------
/test/data/section_functions_in_partials.hpp:
--------------------------------------------------------------------------------
1 | const mstch::node section_functions_in_partials_data = mstch::map{
2 | {"bold", mstch::lambda{[](const std::string& text) -> mstch::node {
3 | return std::string{""} + text + std::string{" "};
4 | }}}
5 | };
--------------------------------------------------------------------------------
/test/data/section_functions_in_partials.mustache:
--------------------------------------------------------------------------------
1 | {{> partial}}
2 |
3 | some more text
4 |
--------------------------------------------------------------------------------
/test/data/section_functions_in_partials.partial:
--------------------------------------------------------------------------------
1 | {{#bold}}Hello There{{/bold}}
2 |
--------------------------------------------------------------------------------
/test/data/section_functions_in_partials.txt:
--------------------------------------------------------------------------------
1 | Hello There
2 |
3 | some more text
4 |
--------------------------------------------------------------------------------
/test/data/simple.hpp:
--------------------------------------------------------------------------------
1 | class simple: public mstch::object {
2 | private:
3 | int m_value;
4 | public:
5 | simple():
6 | m_value(10000)
7 | {
8 | register_methods(this, std::map{
9 | {"name", &simple::name},
10 | {"value", &simple::value},
11 | {"taxed_value", &simple::taxed_value},
12 | {"in_ca", &simple::in_ca}});
13 | }
14 |
15 | mstch::node name() {
16 | return std::string{"Chris"};
17 | }
18 |
19 | mstch::node value() {
20 | return m_value;
21 | }
22 |
23 | mstch::node taxed_value() {
24 | return m_value - (m_value * 0.4);
25 | }
26 |
27 | mstch::node in_ca() {
28 | return true;
29 | }
30 | };
31 |
32 | const auto simple_data = std::make_shared();
--------------------------------------------------------------------------------
/test/data/simple.mustache:
--------------------------------------------------------------------------------
1 | Hello {{name}}
2 | You have just won ${{value}}!
3 | {{#in_ca}}
4 | Well, ${{ taxed_value }}, after taxes.
5 | {{/in_ca}}
6 |
--------------------------------------------------------------------------------
/test/data/simple.txt:
--------------------------------------------------------------------------------
1 | Hello Chris
2 | You have just won $10000!
3 | Well, $6000, after taxes.
4 |
--------------------------------------------------------------------------------
/test/data/string_as_context.hpp:
--------------------------------------------------------------------------------
1 | const auto string_as_context_data = mstch::map{
2 | {"a_string", std::string{"aa"}},
3 | {"a_list", mstch::array{std::string{"a"},std::string{"b"},std::string{"c"}}}
4 | };
--------------------------------------------------------------------------------
/test/data/string_as_context.mustache:
--------------------------------------------------------------------------------
1 |
2 | {{#a_list}}
3 | {{a_string}}/{{.}}
4 | {{/a_list}}
5 |
--------------------------------------------------------------------------------
/test/data/string_as_context.txt:
--------------------------------------------------------------------------------
1 |
2 | aa/a
3 | aa/b
4 | aa/c
5 |
--------------------------------------------------------------------------------
/test/data/two_in_a_row.hpp:
--------------------------------------------------------------------------------
1 | const auto two_in_a_row_data = mstch::map{
2 | {"name", std::string{"Joe"}},
3 | {"greeting", std::string{"Welcome"}}
4 | };
--------------------------------------------------------------------------------
/test/data/two_in_a_row.mustache:
--------------------------------------------------------------------------------
1 | {{greeting}}, {{name}}!
2 |
--------------------------------------------------------------------------------
/test/data/two_in_a_row.txt:
--------------------------------------------------------------------------------
1 | Welcome, Joe!
2 |
--------------------------------------------------------------------------------
/test/data/two_sections.hpp:
--------------------------------------------------------------------------------
1 | const auto two_sections_data = mstch::map{};
--------------------------------------------------------------------------------
/test/data/two_sections.mustache:
--------------------------------------------------------------------------------
1 | {{#foo}}
2 | {{/foo}}
3 | {{#bar}}
4 | {{/bar}}
5 |
--------------------------------------------------------------------------------
/test/data/two_sections.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/no1msd/mstch/0fde1cf94c26ede7fa267f4b64c0efe5da81a77a/test/data/two_sections.txt
--------------------------------------------------------------------------------
/test/data/unescaped.hpp:
--------------------------------------------------------------------------------
1 | const mstch::node unescaped_data = mstch::map{
2 | {"title", mstch::lambda{[]()->mstch::node{ return std::string{"Bear > Shark"}; }}}
3 | };
--------------------------------------------------------------------------------
/test/data/unescaped.mustache:
--------------------------------------------------------------------------------
1 | {{{title}}}
2 |
--------------------------------------------------------------------------------
/test/data/unescaped.txt:
--------------------------------------------------------------------------------
1 | Bear > Shark
2 |
--------------------------------------------------------------------------------
/test/data/whitespace.hpp:
--------------------------------------------------------------------------------
1 | const auto whitespace_data = mstch::map{
2 | {"tag1", std::string{"Hello"}},
3 | {"tag2", std::string{"World"}}
4 | };
--------------------------------------------------------------------------------
/test/data/whitespace.mustache:
--------------------------------------------------------------------------------
1 | {{tag1}}
2 |
3 |
4 | {{tag2}}.
5 |
--------------------------------------------------------------------------------
/test/data/whitespace.txt:
--------------------------------------------------------------------------------
1 | Hello
2 |
3 |
4 | World.
5 |
--------------------------------------------------------------------------------
/test/data/zero_view.hpp:
--------------------------------------------------------------------------------
1 | const auto zero_view_data = mstch::map{{"nums", mstch::array{0, 1, 2}}};
--------------------------------------------------------------------------------
/test/data/zero_view.mustache:
--------------------------------------------------------------------------------
1 | {{#nums}}{{.}},{{/nums}}
--------------------------------------------------------------------------------
/test/data/zero_view.txt:
--------------------------------------------------------------------------------
1 | 0,1,2,
--------------------------------------------------------------------------------
/test/specs_lambdas.hpp:
--------------------------------------------------------------------------------
1 | std::map specs_lambdas {
2 | {"Interpolation", mstch::lambda{[](const std::string&) -> mstch::node {
3 | return std::string{"world"};
4 | }}},
5 | {"Interpolation - Expansion", mstch::lambda{[](const std::string&) -> mstch::node {
6 | return std::string{"{{planet}}"};
7 | }}},
8 | {"Interpolation - Alternate Delimiters", mstch::lambda{[](const std::string&) -> mstch::node {
9 | return std::string{"|planet| => {{planet}}"};
10 | }}},
11 | {"Interpolation - Multiple Calls", mstch::lambda{[](const std::string&) -> mstch::node {
12 | static int calls = 0; return ++calls;
13 | }}},
14 | {"Escaping", mstch::lambda{[](const std::string&) -> mstch::node {
15 | return std::string{">"};
16 | }}},
17 | {"Section", mstch::lambda{[](const std::string& txt) -> mstch::node {
18 | return std::string{(txt == "{{x}}") ? "yes" : "no"};
19 | }}},
20 | {"Section - Expansion", mstch::lambda{[](const std::string& txt) -> mstch::node {
21 | return txt + std::string{"{{planet}}"} + txt;
22 | }}},
23 | {"Section - Alternate Delimiters", mstch::lambda{[](const std::string& txt) -> mstch::node {
24 | return txt + std::string{"{{planet}} => |planet|"} + txt;
25 | }}},
26 | {"Section - Multiple Calls", mstch::lambda{[](const std::string& txt) -> mstch::node {
27 | return "__" + txt + "__";
28 | }}},
29 | {"Inverted Section", mstch::lambda{[](const std::string&) -> mstch::node {
30 | return false;
31 | }}}
32 | };
--------------------------------------------------------------------------------
/test/test_context.hpp:
--------------------------------------------------------------------------------
1 | #include "data/simple.hpp"
2 | #include "data/empty_string.hpp"
3 | #include "data/multiline_comment.hpp"
4 | #include "data/included_tag.hpp"
5 | #include "data/string_as_context.hpp"
6 | #include "data/falsy_array.hpp"
7 | #include "data/nested_dot.hpp"
8 | #include "data/escaped.hpp"
9 | #include "data/partial_view.hpp"
10 | #include "data/nested_higher_order_sections.hpp"
11 | #include "data/zero_view.hpp"
12 | #include "data/disappearing_whitespace.hpp"
13 | #include "data/ampersand_escape.hpp"
14 | #include "data/falsy.hpp"
15 | #include "data/reuse_of_enumerables.hpp"
16 | #include "data/apostrophe.hpp"
17 | #include "data/grandparent_context.hpp"
18 | #include "data/higher_order_sections.hpp"
19 | #include "data/empty_list.hpp"
20 | #include "data/two_in_a_row.hpp"
21 | #include "data/partial_array_of_partials.hpp"
22 | #include "data/keys_with_questionmarks.hpp"
23 | #include "data/error_not_found.hpp"
24 | #include "data/complex.hpp"
25 | #include "data/double_render.hpp"
26 | #include "data/null_lookup_object.hpp"
27 | #include "data/error_eof_in_tag.hpp"
28 | #include "data/delimiters.hpp"
29 | #include "data/null_view.hpp"
30 | #include "data/null_string.hpp"
31 | #include "data/comments.hpp"
32 | #include "data/null_lookup_array.hpp"
33 | #include "data/section_as_context.hpp"
34 | #include "data/unescaped.hpp"
35 | #include "data/dot_notation.hpp"
36 | #include "data/recursion_with_same_names.hpp"
37 | #include "data/two_sections.hpp"
38 | #include "data/partial_array_of_partials_implicit.hpp"
39 | #include "data/changing_delimiters.hpp"
40 | #include "data/nesting_same_name.hpp"
41 | #include "data/partial_empty.hpp"
42 | #include "data/inverted_section.hpp"
43 | #include "data/nested_iterating.hpp"
44 | #include "data/partial_template.hpp"
45 | #include "data/nesting.hpp"
46 | #include "data/bug_11_eating_whitespace.hpp"
47 | #include "data/implicit_iterator.hpp"
48 | #include "data/whitespace.hpp"
49 | #include "data/array_of_strings.hpp"
50 | #include "data/empty_sections.hpp"
51 | #include "data/context_lookup.hpp"
52 | #include "data/section_functions_in_partials.hpp"
53 | #include "data/partial_whitespace.hpp"
54 | #include "data/backslashes.hpp"
55 | #include "data/error_eof_in_section.hpp"
56 | #include "data/empty_template.hpp"
57 | #include "data/partial_array.hpp"
58 | #include "data/bug_length_property.hpp"
59 |
--------------------------------------------------------------------------------
/test/test_main.cpp:
--------------------------------------------------------------------------------
1 | #define CATCH_CONFIG_MAIN
2 |
3 | #include "catch.hpp"
4 | #include "rapidjson/document.h"
5 | #include "mstch/mstch.hpp"
6 | #include "test_context.hpp"
7 | #include "test_data.hpp"
8 | #include "specs_data.hpp"
9 | #include "specs_lambdas.hpp"
10 |
11 | using namespace mstchtest;
12 |
13 | mstch::node to_value(const rapidjson::Value& val) {
14 | if (val.IsString())
15 | return std::string{val.GetString()};
16 | if (val.IsBool())
17 | return val.GetBool();
18 | if (val.IsDouble())
19 | return val.GetDouble();
20 | if (val.IsInt())
21 | return val.GetInt();
22 | return mstch::node{};
23 | }
24 |
25 | mstch::array to_array(const rapidjson::Value& val);
26 |
27 | mstch::map to_object(const rapidjson::Value& val) {
28 | mstch::map ret;
29 | for (auto i = val.MemberBegin(); i != val.MemberEnd(); ++i) {
30 | if (i->value.IsArray())
31 | ret.insert(std::make_pair(i->name.GetString(), to_array(i->value)));
32 | else if (i->value.IsObject())
33 | ret.insert(std::make_pair(i->name.GetString(), to_object(i->value)));
34 | else
35 | ret.insert(std::make_pair(i->name.GetString(), to_value(i->value)));
36 | }
37 | return ret;
38 | }
39 |
40 | mstch::array to_array(const rapidjson::Value& val) {
41 | mstch::array ret;
42 | for (auto i = val.Begin(); i != val.End(); ++i) {
43 | if (i->IsArray())
44 | ret.push_back(to_array(*i));
45 | else if (i->IsObject())
46 | ret.push_back(to_object(*i));
47 | else
48 | ret.push_back(to_value(*i));
49 | }
50 | return ret;
51 | }
52 |
53 | mstch::node parse_with_rapidjson(const std::string& str) {
54 | rapidjson::Document doc;
55 | doc.Parse(str.c_str());
56 | return to_object(doc);
57 | }
58 |
59 | #define MSTCH_PARTIAL_TEST(x) TEST_CASE(#x) { \
60 | REQUIRE(x ## _txt == mstch::render(x ## _mustache, x ## _data, {{"partial", x ## _partial}})); \
61 | }
62 |
63 | #define MSTCH_TEST(x) TEST_CASE(#x) { \
64 | REQUIRE(x ## _txt == mstch::render(x ## _mustache, x ## _data)); \
65 | }
66 |
67 | #define SPECS_TEST(x) TEST_CASE("specs_" #x) { \
68 | using boost::get; \
69 | auto data = parse_with_rapidjson(x ## _json); \
70 | for (auto& test_item: get(get(data)["tests"])) {\
71 | auto test = get(test_item); \
72 | std::map partials; \
73 | if (test.count("partials")) \
74 | for (auto& partial_item: get(test["partials"])) \
75 | partials.insert(std::make_pair(partial_item.first, get(partial_item.second))); \
76 | mstch::map context; \
77 | for (auto& data_item: get(test["data"])) \
78 | if (data_item.first == "lambda") \
79 | context.insert(std::make_pair(data_item.first, specs_lambdas[get(test["name"])])); \
80 | else \
81 | context.insert(data_item); \
82 | SECTION(get(test["name"])) \
83 | REQUIRE(mstch::render( \
84 | get(test["template"]), \
85 | context, partials) == \
86 | get(test["expected"])); \
87 | } \
88 | }
89 |
90 | MSTCH_TEST(ampersand_escape)
91 | MSTCH_TEST(apostrophe)
92 | MSTCH_TEST(array_of_strings)
93 | MSTCH_TEST(backslashes)
94 | MSTCH_TEST(bug_11_eating_whitespace)
95 | MSTCH_TEST(bug_length_property)
96 | MSTCH_TEST(changing_delimiters)
97 | MSTCH_TEST(comments)
98 | MSTCH_TEST(complex)
99 | MSTCH_TEST(context_lookup)
100 | MSTCH_TEST(delimiters)
101 | MSTCH_TEST(disappearing_whitespace)
102 | MSTCH_TEST(dot_notation)
103 | MSTCH_TEST(double_render)
104 | MSTCH_TEST(empty_list)
105 | MSTCH_TEST(empty_sections)
106 | MSTCH_TEST(empty_string)
107 | MSTCH_TEST(empty_template)
108 | MSTCH_TEST(error_eof_in_section)
109 | MSTCH_TEST(error_eof_in_tag)
110 | MSTCH_TEST(error_not_found)
111 | MSTCH_TEST(escaped)
112 | MSTCH_TEST(falsy)
113 | MSTCH_TEST(falsy_array)
114 | MSTCH_TEST(grandparent_context)
115 | MSTCH_TEST(higher_order_sections)
116 | MSTCH_TEST(implicit_iterator)
117 | MSTCH_TEST(included_tag)
118 | MSTCH_TEST(inverted_section)
119 | MSTCH_TEST(keys_with_questionmarks)
120 | MSTCH_TEST(multiline_comment)
121 | MSTCH_TEST(nested_dot)
122 | MSTCH_TEST(nested_higher_order_sections)
123 | MSTCH_TEST(nested_iterating)
124 | MSTCH_TEST(nesting)
125 | MSTCH_TEST(nesting_same_name)
126 | MSTCH_TEST(null_lookup_array)
127 | MSTCH_TEST(null_lookup_object)
128 | MSTCH_TEST(null_string)
129 | MSTCH_TEST(null_view)
130 | MSTCH_PARTIAL_TEST(partial_array)
131 | MSTCH_PARTIAL_TEST(partial_array_of_partials)
132 | MSTCH_PARTIAL_TEST(partial_array_of_partials_implicit)
133 | MSTCH_PARTIAL_TEST(partial_empty)
134 | MSTCH_PARTIAL_TEST(partial_template)
135 | MSTCH_PARTIAL_TEST(partial_view)
136 | MSTCH_PARTIAL_TEST(partial_whitespace)
137 | MSTCH_TEST(recursion_with_same_names)
138 | MSTCH_TEST(reuse_of_enumerables)
139 | MSTCH_TEST(section_as_context)
140 | MSTCH_PARTIAL_TEST(section_functions_in_partials)
141 | MSTCH_TEST(simple)
142 | MSTCH_TEST(string_as_context)
143 | MSTCH_TEST(two_in_a_row)
144 | MSTCH_TEST(two_sections)
145 | MSTCH_TEST(unescaped)
146 | MSTCH_TEST(whitespace)
147 | MSTCH_TEST(zero_view)
148 |
149 | SPECS_TEST(comments)
150 | SPECS_TEST(delimiters)
151 | SPECS_TEST(interpolation)
152 | SPECS_TEST(inverted)
153 | SPECS_TEST(partials)
154 | SPECS_TEST(sections)
155 | SPECS_TEST(lambdas)
156 |
--------------------------------------------------------------------------------
{{header}}
" 8 | "{{#comments}} {{/comments}}
{{name}}
" 9 | "{{body}}