├── .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 | ![mstch logo](http://i.imgur.com/MRyStO5.png) 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 | [![Try it online](https://img.shields.io/badge/try%20it-online-blue.svg)](http://melpon.org/wandbox/permlink/EqyOe7IBRYPGVk5f) 10 | [![GitHub version](https://badge.fury.io/gh/no1msd%2Fmstch.svg)](http://badge.fury.io/gh/no1msd%2Fmstch) 11 | [![Build Status](https://travis-ci.org/no1msd/mstch.svg?branch=master)](https://travis-ci.org/no1msd/mstch) 12 | [![Build status](https://ci.appveyor.com/api/projects/status/d6mxp0uba5646x16?svg=true)](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 | "

{{header}}

    " 8 | "{{#comments}}
  • {{name}}
    " 9 | "

    {{body}}

  • {{/comments}}
"}; 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:

  • John Power
  • Jamie Walsh

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 &quot; "'<>/, 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 | --------------------------------------------------------------------------------