aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mstch/CMakeLists.txt28
-rw-r--r--lib/mstch/LICENSE22
-rw-r--r--lib/mstch/README.md281
-rw-r--r--lib/mstch/benchmark/CMakeLists.txt6
-rw-r--r--lib/mstch/benchmark/benchmark_main.cpp26
-rw-r--r--lib/mstch/cmake/mstch-config.cmake1
-rw-r--r--lib/mstch/include/mstch/mstch.hpp113
-rw-r--r--lib/mstch/src/CMakeLists.txt64
-rw-r--r--lib/mstch/src/mstch.cpp20
-rw-r--r--lib/mstch/src/render_context.cpp72
-rw-r--r--lib/mstch/src/render_context.hpp51
-rw-r--r--lib/mstch/src/state/in_section.cpp34
-rw-r--r--lib/mstch/src/state/in_section.hpp24
-rw-r--r--lib/mstch/src/state/outside_section.cpp32
-rw-r--r--lib/mstch/src/state/outside_section.hpp12
-rw-r--r--lib/mstch/src/state/render_state.hpp17
-rw-r--r--lib/mstch/src/template_type.cpp104
-rw-r--r--lib/mstch/src/template_type.hpp30
-rw-r--r--lib/mstch/src/token.cpp42
-rw-r--r--lib/mstch/src/token.hpp39
-rw-r--r--lib/mstch/src/utils.cpp44
-rw-r--r--lib/mstch/src/utils.hpp23
-rw-r--r--lib/mstch/src/visitor/get_token.hpp35
-rw-r--r--lib/mstch/src/visitor/has_token.hpp31
-rw-r--r--lib/mstch/src/visitor/is_node_empty.hpp41
-rw-r--r--lib/mstch/src/visitor/render_node.hpp56
-rw-r--r--lib/mstch/src/visitor/render_section.hpp57
-rw-r--r--lib/mstch/test/CMakeLists.txt59
-rw-r--r--lib/mstch/test/data/ampersand_escape.hpp3
-rw-r--r--lib/mstch/test/data/ampersand_escape.mustache1
-rw-r--r--lib/mstch/test/data/ampersand_escape.txt1
-rw-r--r--lib/mstch/test/data/apostrophe.hpp4
-rw-r--r--lib/mstch/test/data/apostrophe.mustache1
-rw-r--r--lib/mstch/test/data/apostrophe.txt1
-rw-r--r--lib/mstch/test/data/array_of_strings.hpp3
-rw-r--r--lib/mstch/test/data/array_of_strings.mustache1
-rw-r--r--lib/mstch/test/data/array_of_strings.txt1
-rw-r--r--lib/mstch/test/data/backslashes.hpp3
-rw-r--r--lib/mstch/test/data/backslashes.mustache7
-rw-r--r--lib/mstch/test/data/backslashes.txt7
-rw-r--r--lib/mstch/test/data/bug_11_eating_whitespace.hpp3
-rw-r--r--lib/mstch/test/data/bug_11_eating_whitespace.mustache1
-rw-r--r--lib/mstch/test/data/bug_11_eating_whitespace.txt1
-rw-r--r--lib/mstch/test/data/bug_length_property.hpp3
-rw-r--r--lib/mstch/test/data/bug_length_property.mustache1
-rw-r--r--lib/mstch/test/data/bug_length_property.txt1
-rw-r--r--lib/mstch/test/data/changing_delimiters.hpp4
-rw-r--r--lib/mstch/test/data/changing_delimiters.mustache1
-rw-r--r--lib/mstch/test/data/changing_delimiters.txt1
-rw-r--r--lib/mstch/test/data/comments.hpp3
-rw-r--r--lib/mstch/test/data/comments.mustache1
-rw-r--r--lib/mstch/test/data/comments.txt1
-rw-r--r--lib/mstch/test/data/complex.hpp69
-rw-r--r--lib/mstch/test/data/complex.mustache16
-rw-r--r--lib/mstch/test/data/complex.txt6
-rw-r--r--lib/mstch/test/data/context_lookup.hpp8
-rw-r--r--lib/mstch/test/data/context_lookup.mustache1
-rw-r--r--lib/mstch/test/data/context_lookup.txt1
-rw-r--r--lib/mstch/test/data/delimiters.hpp6
-rw-r--r--lib/mstch/test/data/delimiters.mustache7
-rw-r--r--lib/mstch/test/data/delimiters.txt5
-rw-r--r--lib/mstch/test/data/disappearing_whitespace.hpp4
-rw-r--r--lib/mstch/test/data/disappearing_whitespace.mustache1
-rw-r--r--lib/mstch/test/data/disappearing_whitespace.txt1
-rw-r--r--lib/mstch/test/data/dot_notation.hpp34
-rw-r--r--lib/mstch/test/data/dot_notation.mustache9
-rw-r--r--lib/mstch/test/data/dot_notation.txt9
-rw-r--r--lib/mstch/test/data/double_render.hpp5
-rw-r--r--lib/mstch/test/data/double_render.mustache1
-rw-r--r--lib/mstch/test/data/double_render.txt1
-rw-r--r--lib/mstch/test/data/empty_list.hpp3
-rw-r--r--lib/mstch/test/data/empty_list.mustache4
-rw-r--r--lib/mstch/test/data/empty_list.txt1
-rw-r--r--lib/mstch/test/data/empty_sections.hpp1
-rw-r--r--lib/mstch/test/data/empty_sections.mustache1
-rw-r--r--lib/mstch/test/data/empty_sections.txt1
-rw-r--r--lib/mstch/test/data/empty_string.hpp6
-rw-r--r--lib/mstch/test/data/empty_string.mustache1
-rw-r--r--lib/mstch/test/data/empty_string.txt1
-rw-r--r--lib/mstch/test/data/empty_template.hpp1
-rw-r--r--lib/mstch/test/data/empty_template.mustache1
-rw-r--r--lib/mstch/test/data/empty_template.txt1
-rw-r--r--lib/mstch/test/data/error_eof_in_section.hpp3
-rw-r--r--lib/mstch/test/data/error_eof_in_section.mustache1
-rw-r--r--lib/mstch/test/data/error_eof_in_section.txt1
-rw-r--r--lib/mstch/test/data/error_eof_in_tag.hpp1
-rw-r--r--lib/mstch/test/data/error_eof_in_tag.mustache1
-rw-r--r--lib/mstch/test/data/error_eof_in_tag.txt1
-rw-r--r--lib/mstch/test/data/error_not_found.hpp3
-rw-r--r--lib/mstch/test/data/error_not_found.mustache1
-rw-r--r--lib/mstch/test/data/error_not_found.txt0
-rw-r--r--lib/mstch/test/data/escaped.hpp4
-rw-r--r--lib/mstch/test/data/escaped.mustache2
-rw-r--r--lib/mstch/test/data/escaped.txt2
-rw-r--r--lib/mstch/test/data/falsy.hpp6
-rw-r--r--lib/mstch/test/data/falsy.mustache8
-rw-r--r--lib/mstch/test/data/falsy.txt8
-rw-r--r--lib/mstch/test/data/falsy_array.hpp8
-rw-r--r--lib/mstch/test/data/falsy_array.mustache3
-rw-r--r--lib/mstch/test/data/falsy_array.txt4
-rw-r--r--lib/mstch/test/data/grandparent_context.hpp19
-rw-r--r--lib/mstch/test/data/grandparent_context.mustache10
-rw-r--r--lib/mstch/test/data/grandparent_context.txt17
-rw-r--r--lib/mstch/test/data/higher_order_sections.hpp28
-rw-r--r--lib/mstch/test/data/higher_order_sections.mustache1
-rw-r--r--lib/mstch/test/data/higher_order_sections.txt1
-rw-r--r--lib/mstch/test/data/implicit_iterator.hpp8
-rw-r--r--lib/mstch/test/data/implicit_iterator.mustache7
-rw-r--r--lib/mstch/test/data/implicit_iterator.txt3
-rw-r--r--lib/mstch/test/data/included_tag.hpp3
-rw-r--r--lib/mstch/test/data/included_tag.mustache1
-rw-r--r--lib/mstch/test/data/included_tag.txt1
-rw-r--r--lib/mstch/test/data/inverted_section.hpp3
-rw-r--r--lib/mstch/test/data/inverted_section.mustache3
-rw-r--r--lib/mstch/test/data/inverted_section.txt3
-rw-r--r--lib/mstch/test/data/keys_with_questionmarks.hpp5
-rw-r--r--lib/mstch/test/data/keys_with_questionmarks.mustache3
-rw-r--r--lib/mstch/test/data/keys_with_questionmarks.txt1
-rw-r--r--lib/mstch/test/data/multiline_comment.hpp1
-rw-r--r--lib/mstch/test/data/multiline_comment.mustache6
-rw-r--r--lib/mstch/test/data/multiline_comment.txt1
-rw-r--r--lib/mstch/test/data/nested_dot.hpp1
-rw-r--r--lib/mstch/test/data/nested_dot.mustache1
-rw-r--r--lib/mstch/test/data/nested_dot.txt1
-rw-r--r--lib/mstch/test/data/nested_higher_order_sections.hpp6
-rw-r--r--lib/mstch/test/data/nested_higher_order_sections.mustache1
-rw-r--r--lib/mstch/test/data/nested_higher_order_sections.txt1
-rw-r--r--lib/mstch/test/data/nested_iterating.hpp8
-rw-r--r--lib/mstch/test/data/nested_iterating.mustache1
-rw-r--r--lib/mstch/test/data/nested_iterating.txt1
-rw-r--r--lib/mstch/test/data/nesting.hpp7
-rw-r--r--lib/mstch/test/data/nesting.mustache5
-rw-r--r--lib/mstch/test/data/nesting.txt3
-rw-r--r--lib/mstch/test/data/nesting_same_name.hpp8
-rw-r--r--lib/mstch/test/data/nesting_same_name.mustache1
-rw-r--r--lib/mstch/test/data/nesting_same_name.txt1
-rw-r--r--lib/mstch/test/data/null_lookup_array.hpp8
-rw-r--r--lib/mstch/test/data/null_lookup_array.mustache3
-rw-r--r--lib/mstch/test/data/null_lookup_array.txt2
-rw-r--r--lib/mstch/test/data/null_lookup_object.hpp14
-rw-r--r--lib/mstch/test/data/null_lookup_object.mustache3
-rw-r--r--lib/mstch/test/data/null_lookup_object.txt2
-rw-r--r--lib/mstch/test/data/null_string.hpp6
-rw-r--r--lib/mstch/test/data/null_string.mustache4
-rw-r--r--lib/mstch/test/data/null_string.txt4
-rw-r--r--lib/mstch/test/data/null_view.hpp4
-rw-r--r--lib/mstch/test/data/null_view.mustache1
-rw-r--r--lib/mstch/test/data/null_view.txt1
-rw-r--r--lib/mstch/test/data/partial_array.hpp3
-rw-r--r--lib/mstch/test/data/partial_array.mustache1
-rw-r--r--lib/mstch/test/data/partial_array.partial4
-rw-r--r--lib/mstch/test/data/partial_array.txt5
-rw-r--r--lib/mstch/test/data/partial_array_of_partials.hpp8
-rw-r--r--lib/mstch/test/data/partial_array_of_partials.mustache4
-rw-r--r--lib/mstch/test/data/partial_array_of_partials.partial1
-rw-r--r--lib/mstch/test/data/partial_array_of_partials.txt5
-rw-r--r--lib/mstch/test/data/partial_array_of_partials_implicit.hpp3
-rw-r--r--lib/mstch/test/data/partial_array_of_partials_implicit.mustache4
-rw-r--r--lib/mstch/test/data/partial_array_of_partials_implicit.partial1
-rw-r--r--lib/mstch/test/data/partial_array_of_partials_implicit.txt5
-rw-r--r--lib/mstch/test/data/partial_empty.hpp3
-rw-r--r--lib/mstch/test/data/partial_empty.mustache2
-rw-r--r--lib/mstch/test/data/partial_empty.partial0
-rw-r--r--lib/mstch/test/data/partial_empty.txt1
-rw-r--r--lib/mstch/test/data/partial_template.hpp4
-rw-r--r--lib/mstch/test/data/partial_template.mustache2
-rw-r--r--lib/mstch/test/data/partial_template.partial1
-rw-r--r--lib/mstch/test/data/partial_template.txt2
-rw-r--r--lib/mstch/test/data/partial_view.hpp42
-rw-r--r--lib/mstch/test/data/partial_view.mustache3
-rw-r--r--lib/mstch/test/data/partial_view.partial5
-rw-r--r--lib/mstch/test/data/partial_view.txt5
-rw-r--r--lib/mstch/test/data/partial_whitespace.hpp41
-rw-r--r--lib/mstch/test/data/partial_whitespace.mustache3
-rw-r--r--lib/mstch/test/data/partial_whitespace.partial5
-rw-r--r--lib/mstch/test/data/partial_whitespace.txt5
-rw-r--r--lib/mstch/test/data/recursion_with_same_names.hpp8
-rw-r--r--lib/mstch/test/data/recursion_with_same_names.mustache7
-rw-r--r--lib/mstch/test/data/recursion_with_same_names.txt7
-rw-r--r--lib/mstch/test/data/reuse_of_enumerables.hpp6
-rw-r--r--lib/mstch/test/data/reuse_of_enumerables.mustache8
-rw-r--r--lib/mstch/test/data/reuse_of_enumerables.txt8
-rw-r--r--lib/mstch/test/data/section_as_context.hpp10
-rw-r--r--lib/mstch/test/data/section_as_context.mustache9
-rw-r--r--lib/mstch/test/data/section_as_context.txt6
-rw-r--r--lib/mstch/test/data/section_functions_in_partials.hpp5
-rw-r--r--lib/mstch/test/data/section_functions_in_partials.mustache3
-rw-r--r--lib/mstch/test/data/section_functions_in_partials.partial1
-rw-r--r--lib/mstch/test/data/section_functions_in_partials.txt3
-rw-r--r--lib/mstch/test/data/simple.hpp32
-rw-r--r--lib/mstch/test/data/simple.mustache5
-rw-r--r--lib/mstch/test/data/simple.txt3
-rw-r--r--lib/mstch/test/data/string_as_context.hpp4
-rw-r--r--lib/mstch/test/data/string_as_context.mustache5
-rw-r--r--lib/mstch/test/data/string_as_context.txt5
-rw-r--r--lib/mstch/test/data/two_in_a_row.hpp4
-rw-r--r--lib/mstch/test/data/two_in_a_row.mustache1
-rw-r--r--lib/mstch/test/data/two_in_a_row.txt1
-rw-r--r--lib/mstch/test/data/two_sections.hpp1
-rw-r--r--lib/mstch/test/data/two_sections.mustache4
-rw-r--r--lib/mstch/test/data/two_sections.txt0
-rw-r--r--lib/mstch/test/data/unescaped.hpp3
-rw-r--r--lib/mstch/test/data/unescaped.mustache1
-rw-r--r--lib/mstch/test/data/unescaped.txt1
-rw-r--r--lib/mstch/test/data/whitespace.hpp4
-rw-r--r--lib/mstch/test/data/whitespace.mustache4
-rw-r--r--lib/mstch/test/data/whitespace.txt4
-rw-r--r--lib/mstch/test/data/zero_view.hpp1
-rw-r--r--lib/mstch/test/data/zero_view.mustache1
-rw-r--r--lib/mstch/test/data/zero_view.txt1
-rw-r--r--lib/mstch/test/specs_lambdas.hpp32
-rw-r--r--lib/mstch/test/test_context.hpp58
-rw-r--r--lib/mstch/test/test_main.cpp155
213 files changed, 2483 insertions, 0 deletions
diff --git a/lib/mstch/CMakeLists.txt b/lib/mstch/CMakeLists.txt
new file mode 100644
index 0000000..8d8e0c7
--- /dev/null
+++ b/lib/mstch/CMakeLists.txt
@@ -0,0 +1,28 @@
+cmake_minimum_required(VERSION 3.0.2)
+project(mstch)
+
+option(WITH_UNIT_TESTS "enable building unit test executable" OFF)
+option(WITH_BENCHMARK "enable building benchmark executable" OFF)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+set(CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE ON)
+set(CMAKE_BUILD_TYPE Release)
+
+set(mstch_VERSION 1.0.1)
+
+if(NOT MSVC)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -O3")
+endif()
+
+add_subdirectory(src)
+
+if(WITH_UNIT_TESTS)
+ enable_testing()
+ add_subdirectory(vendor/headerize)
+ add_subdirectory(test)
+endif()
+
+if(WITH_BENCHMARK)
+ add_subdirectory(vendor/benchmark)
+ add_subdirectory(benchmark)
+endif()
diff --git a/lib/mstch/LICENSE b/lib/mstch/LICENSE
new file mode 100644
index 0000000..b3011a2
--- /dev/null
+++ b/lib/mstch/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Daniel Sipka
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/lib/mstch/README.md b/lib/mstch/README.md
new file mode 100644
index 0000000..ff806c8
--- /dev/null
+++ b/lib/mstch/README.md
@@ -0,0 +1,281 @@
+# mstch - {{mustache}} templates in C++11
+
+![mstch logo](http://i.imgur.com/MRyStO5.png)
+
+mstch is a complete implementation of [{{mustache}}](http://mustache.github.io/)
+templates using modern C++. It's compliant with [specifications](https://github.com/mustache/spec)
+v1.1.3, including the lambda module.
+
+[![Try it online](https://img.shields.io/badge/try%20it-online-blue.svg)](http://melpon.org/wandbox/permlink/EqyOe7IBRYPGVk5f)
+[![GitHub version](https://badge.fury.io/gh/no1msd%2Fmstch.svg)](http://badge.fury.io/gh/no1msd%2Fmstch)
+[![Build Status](https://travis-ci.org/no1msd/mstch.svg?branch=master)](https://travis-ci.org/no1msd/mstch)
+[![Build status](https://ci.appveyor.com/api/projects/status/d6mxp0uba5646x16?svg=true)](https://ci.appveyor.com/project/no1msd/mstch)
+
+## Supported features
+
+mstch supports the complete feature set described in the `mustache(5)` [manpage](http://mustache.github.com/mustache.5.html):
+
+ - JSON-like data structure using [Boost.Variant](http://www.boost.org/libs/variant)
+ - variables, sections, inverted sections
+ - partials
+ - changing the delimiter
+ - C++11 lambdas
+ - C++ objects as view models
+
+## Basic usage
+
+```c++
+#include <iostream>
+#include <mstch/mstch.hpp>
+
+int main() {
+ std::string view{"{{#names}}Hi {{name}}!\n{{/names}}"};
+ mstch::map context{
+ {"names", mstch::array{
+ mstch::map{{"name", std::string{"Chris"}}},
+ mstch::map{{"name", std::string{"Mark"}}},
+ mstch::map{{"name", std::string{"Scott"}}},
+ }}
+ };
+
+ std::cout << mstch::render(view, context) << std::endl;
+
+ return 0;
+}
+
+```
+
+The output of this example will be:
+
+```html
+Hi Chris!
+Hi Mark!
+Hi Scott!
+```
+
+### Data structure
+
+The types in the example above, `mstch::array` and `mstch::map` are actually
+aliases for standard types:
+
+```c++
+using map = std::map<const std::string, node>;
+using array = std::vector<node>;
+```
+
+`mstch::node` is a `boost::variant` that can hold a `std::string`, `int`,
+`double`, `bool`, `mstch::lambda` or a `std::shared_ptr<mstch::object>`
+(see below), also a map or an array recursively. Essentially it works just like
+a JSON object.
+
+Note that when using a `std::string` as value you must explicitly specify the
+type, since a `const char*` literal like `"foobar"` would be implicitly
+converted to `bool`. Alternatively you can use [C++14 string_literals](http://en.cppreference.com/w/cpp/string/basic_string/operator%22%22s)
+if your compiler supports it.
+
+## Advanced usage
+
+### Partials
+
+Partials can be passed in a `std::map` as the third parameter of the
+`mstch::render` function:
+
+```c++
+std::string view{"{{#names}}{{> user}}{{/names}}"};
+std::string user_view{"<strong>{{name}}\n</strong>"};
+mstch::map context{
+ {"names", mstch::array{
+ mstch::map{{"name", std::string{"Chris"}}},
+ mstch::map{{"name", std::string{"Mark"}}},
+ mstch::map{{"name", std::string{"Scott"}}},
+ }}
+};
+
+std::cout << mstch::render(view, context, {{"user", user_view}}) << std::endl;
+```
+
+Output:
+
+```html
+<strong>Chris</strong>
+<strong>Mark</strong>
+<strong>Scott</strong>
+```
+
+### Lambdas
+
+C++11 lambda expressions can be used to add logic to your templates. Like a
+`const char*` literal, lambdas can be implicitly converted to `bool`, so they
+must be wrapped in a `mstch::lambda` object when used in a `mstch::node`. The
+lambda expression passed to `mstch::lambda` must itself return a `mstch::node`.
+The returned node will be rendered to a string, then it will be parsed as a
+template.
+
+The lambda expression accepts either no parameters:
+
+```c++
+std::string view{"Hello {{lambda}}!"};
+mstch::map context{
+ {"lambda", mstch::lambda{[]() -> mstch::node {
+ return std::string{"World"};
+ }}}
+};
+
+std::cout << mstch::render(view, context) << std::endl;
+```
+
+Output:
+
+```html
+Hello World!
+```
+
+Or it accepts a `const std::string&` that gets the unrendered literal block:
+
+```c++
+std::string view{"{{#bold}}{{yay}} :){{/bold}}"};
+mstch::map context{
+ {"yay", std::string{"Yay!"}},
+ {"bold", mstch::lambda{[](const std::string& text) -> mstch::node {
+ return "<b>" + text + "</b>";
+ }}}
+};
+
+std::cout << mstch::render(view, context) << std::endl;
+```
+
+Output:
+
+```html
+<b>Yay! :)</b>
+```
+
+### Objects
+
+Custom objects can also be used as context for rendering templates. The class
+must inherit from `mstch::object`, and register it's exported methods with
+`register_methods`. Exported methods must have the return type of `mstch::node`.
+Objects must be created as a `std::shared_ptr`.
+
+```c++
+class example: public mstch::object {
+ public:
+ example(): m_value(1) {
+ register_methods(this, {
+ {"count", &example::count},
+ {"names", &example::names}
+ });
+ }
+
+ mstch::node count() {
+ return m_value++;
+ }
+
+ mstch::node names() {
+ return mstch::array{
+ std::string{"Chris"}, std::string{"Mark"}, std::string{"Scott"}};
+ }
+
+ private:
+ int m_value;
+};
+
+std::string view{"{{#names}}<b>{{count}}</b>: {{.}}\n{{/names}}"};
+const auto context = std::make_shared<example>();
+
+std::cout << mstch::render(view, context) << std::endl;
+```
+
+Output:
+
+```html
+<b>1</b>: Chris
+<b>2</b>: Mark
+<b>3</b>: Scott
+```
+
+### Custom escape function
+
+By default, mstch uses HTML escaping on the output, as per specification. This
+is not useful if your output is not HTML, so mstch provides a way to supply
+your own escape implementation. Just assign any callable object to the static
+`mstch::config::escape`, which is an initially empty
+`std::function<std::string(const std::string&)>`.
+
+For example you can turn off escaping entirely with a lambda:
+
+```c++
+mstch::config::escape = [](const std::string& str) -> std::string {
+ return str;
+};
+```
+
+## Requirements
+
+ - A C++ compiler with decent C++11 support. Currently tested with:
+ - GCC 4.7, 4.8, 4.9, 5.1
+ - clang 3.5, 3.6, 3.7 (both libstdc++ and libc++ are supported)
+ - MSVC 2013, 2015
+ - Boost 1.54+ for [Boost.Variant](http://www.boost.org/libs/variant)
+ - CMake 3.0+ for building
+
+## Using mstch in your project
+
+If you are using CMake, the easiest way to include mstch in your project is to
+copy the whole directory to your source tree, and use `add_subdirectory` in your
+CMakeLists.txt. This will set a variable named `mstch_INCLUDE_DIR` that contains
+its include path, and add a static library target named `mstch`. For example:
+
+```cmake
+add_subdirectory(external/mstch)
+include_directories(${mstch_INCLUDE_DIR})
+target_link_libraries(your_project mstch)
+```
+
+If you prefer to install the library globally, you can simply do the following
+from the root of the source tree:
+
+```bash
+ $ mkdir build
+ $ cd build
+ $ cmake ..
+ $ make
+ $ make install
+```
+
+The install command may require root privileges. This will also install CMake
+config files, so you can use use `find_package` in your CMakeLists.txt:
+
+```cmake
+find_package(mstch)
+target_link_libraries(your_project mstch::mstch)
+```
+
+## Running the unit tests
+
+Unit tests are using the [Catch](https://github.com/philsquared/Catch) framework
+and [rapidjson](http://rapidjson.org/) to parse the
+[Mustache specifications](https://github.com/mustache/spec), all of which are
+included in the repository as git submodules. Various
+[Boost](http://www.boost.org/) libraries are also required to build them.
+
+Don't forget to initialize submodules:
+
+```bash
+ $ git submodule init
+ $ git submodule update
+```
+
+To build and run the unit tests:
+
+```bash
+ $ mkdir build
+ $ cd build
+ $ cmake -DWITH_UNIT_TESTS=ON ..
+ $ make
+ $ make test
+```
+
+## License
+
+mstch is licensed under the [MIT license](https://github.com/no1msd/mstch/blob/master/LICENSE).
diff --git a/lib/mstch/benchmark/CMakeLists.txt b/lib/mstch/benchmark/CMakeLists.txt
new file mode 100644
index 0000000..fa47f0d
--- /dev/null
+++ b/lib/mstch/benchmark/CMakeLists.txt
@@ -0,0 +1,6 @@
+include_directories(
+ ${CMAKE_SOURCE_DIR}/include
+ ${CMAKE_SOURCE_DIR}/vendor/benchmark/include)
+
+add_executable(mstch_benchmark benchmark_main.cpp)
+target_link_libraries(mstch_benchmark mstch benchmark)
diff --git a/lib/mstch/benchmark/benchmark_main.cpp b/lib/mstch/benchmark/benchmark_main.cpp
new file mode 100644
index 0000000..ad7f8d5
--- /dev/null
+++ b/lib/mstch/benchmark/benchmark_main.cpp
@@ -0,0 +1,26 @@
+#include <benchmark/benchmark.h>
+
+#include "mstch/mstch.hpp"
+
+static void basic_usage(benchmark::State& state) {
+ std::string comment_tmp{
+ "<div class=\"comments\"><h3>{{header}}</h3><ul>"
+ "{{#comments}}<li class=\"comment\"><h5>{{name}}</h5>"
+ "<p>{{body}}</p></li>{{/comments}}</ul></div>"};
+
+ auto comment_view = mstch::map{
+ {"header", std::string{"My Post Comments"}},
+ {"comments", mstch::array{
+ mstch::map{{"name", std::string{"Joe"}}, {"body", std::string{"Thanks for this post!"}}},
+ mstch::map{{"name", std::string{"Sam"}}, {"body", std::string{"Thanks for this post!"}}},
+ mstch::map{{"name", std::string{"Heather"}}, {"body", std::string{"Thanks for this post!"}}},
+ mstch::map{{"name", std::string{"Kathy"}}, {"body", std::string{"Thanks for this post!"}}},
+ mstch::map{{"name", std::string{"George"}}, {"body", std::string{"Thanks for this post!"}}}}}};
+
+ while (state.KeepRunning())
+ mstch::render(comment_tmp, comment_view);
+}
+
+BENCHMARK(basic_usage);
+
+BENCHMARK_MAIN();
diff --git a/lib/mstch/cmake/mstch-config.cmake b/lib/mstch/cmake/mstch-config.cmake
new file mode 100644
index 0000000..b2a50e0
--- /dev/null
+++ b/lib/mstch/cmake/mstch-config.cmake
@@ -0,0 +1 @@
+include("${CMAKE_CURRENT_LIST_DIR}/mstch-targets.cmake") \ No newline at end of file
diff --git a/lib/mstch/include/mstch/mstch.hpp b/lib/mstch/include/mstch/mstch.hpp
new file mode 100644
index 0000000..58d3330
--- /dev/null
+++ b/lib/mstch/include/mstch/mstch.hpp
@@ -0,0 +1,113 @@
+#pragma once
+
+#include <vector>
+#include <map>
+#include <string>
+#include <memory>
+#include <functional>
+
+#include <boost/variant.hpp>
+
+namespace mstch {
+
+struct config {
+ static std::function<std::string(const std::string&)> escape;
+};
+
+namespace internal {
+
+template<class N>
+class object_t {
+ public:
+ const N& at(const std::string& name) const {
+ cache[name] = (methods.at(name))();
+ return cache[name];
+ }
+
+ bool has(const std::string name) const {
+ return methods.count(name) != 0;
+ }
+
+ protected:
+ template<class S>
+ void register_methods(S* s, std::map<std::string,N(S::*)()> methods) {
+ for(auto& item: methods)
+ this->methods.insert({item.first, std::bind(item.second, s)});
+ }
+
+ private:
+ std::map<std::string, std::function<N()>> methods;
+ mutable std::map<std::string, N> cache;
+};
+
+template<class T, class N>
+class is_fun {
+ private:
+ using not_fun = char;
+ using fun_without_args = char[2];
+ using fun_with_args = char[3];
+ template <typename U, U> struct really_has;
+ template <typename C> static fun_without_args& test(
+ really_has<N(C::*)() const, &C::operator()>*);
+ template <typename C> static fun_with_args& test(
+ really_has<N(C::*)(const std::string&) const,
+ &C::operator()>*);
+ template <typename> static not_fun& test(...);
+
+ public:
+ static bool const no_args = sizeof(test<T>(0)) == sizeof(fun_without_args);
+ static bool const has_args = sizeof(test<T>(0)) == sizeof(fun_with_args);
+};
+
+template<class N>
+using node_renderer = std::function<std::string(const N& n)>;
+
+template<class N>
+class lambda_t {
+ public:
+ template<class F>
+ lambda_t(F f, typename std::enable_if<is_fun<F, N>::no_args>::type* = 0):
+ fun([f](node_renderer<N> renderer, const std::string&) {
+ return renderer(f());
+ })
+ {
+ }
+
+ template<class F>
+ lambda_t(F f, typename std::enable_if<is_fun<F, N>::has_args>::type* = 0):
+ fun([f](node_renderer<N> renderer, const std::string& text) {
+ return renderer(f(text));
+ })
+ {
+ }
+
+ std::string operator()(node_renderer<N> renderer,
+ const std::string& text = "") const
+ {
+ return fun(renderer, text);
+ }
+
+ private:
+ std::function<std::string(node_renderer<N> renderer, const std::string&)> fun;
+};
+
+}
+
+using node = boost::make_recursive_variant<
+ std::nullptr_t, std::string, int, double, bool,
+ internal::lambda_t<boost::recursive_variant_>,
+ std::shared_ptr<internal::object_t<boost::recursive_variant_>>,
+ std::map<const std::string, boost::recursive_variant_>,
+ std::vector<boost::recursive_variant_>>::type;
+using object = internal::object_t<node>;
+using lambda = internal::lambda_t<node>;
+using map = std::map<const std::string, node>;
+using array = std::vector<node>;
+
+std::string render(
+ const std::string& tmplt,
+ const node& root,
+ const std::map<std::string,std::string>& partials =
+ std::map<std::string,std::string>());
+
+}
diff --git a/lib/mstch/src/CMakeLists.txt b/lib/mstch/src/CMakeLists.txt
new file mode 100644
index 0000000..6517fc4
--- /dev/null
+++ b/lib/mstch/src/CMakeLists.txt
@@ -0,0 +1,64 @@
+find_package(Boost 1.54 REQUIRED)
+
+set(mstch_INCLUDE_DIR
+ ${PROJECT_SOURCE_DIR}/include CACHE STRING "mstch include directory")
+
+include_directories(
+ ${mstch_INCLUDE_DIR}
+ ${Boost_INCLUDE_DIR})
+
+set(SRC
+ state/in_section.cpp
+ state/outside_section.cpp
+ state/render_state.hpp
+ visitor/get_token.hpp
+ visitor/has_token.hpp
+ visitor/is_node_empty.hpp
+ visitor/render_node.hpp
+ visitor/render_section.hpp
+ mstch.cpp
+ render_context.cpp
+ template_type.cpp
+ token.cpp
+ utils.cpp)
+
+add_library(mstch STATIC ${SRC})
+
+set_property(TARGET mstch PROPERTY VERSION ${mstch_VERSION})
+
+install(
+ TARGETS mstch EXPORT mstchTargets
+ LIBRARY DESTINATION lib
+ ARCHIVE DESTINATION lib)
+
+install(
+ FILES "${PROJECT_SOURCE_DIR}/include/mstch/mstch.hpp"
+ DESTINATION include/mstch
+ COMPONENT Devel)
+
+include(CMakePackageConfigHelpers)
+write_basic_package_version_file(
+ "${CMAKE_CURRENT_BINARY_DIR}/mstch/mstch-config-version.cmake"
+ VERSION ${mstch_VERSION}
+ COMPATIBILITY AnyNewerVersion)
+
+export(
+ EXPORT mstchTargets
+ FILE "${CMAKE_CURRENT_BINARY_DIR}/mstch/mstch-targets.cmake"
+ NAMESPACE mstch::)
+
+configure_file(
+ "${PROJECT_SOURCE_DIR}/cmake/mstch-config.cmake"
+ "${CMAKE_CURRENT_BINARY_DIR}/mstch/mstch-config.cmake")
+
+install(
+ EXPORT mstchTargets
+ FILE mstch-targets.cmake
+ NAMESPACE mstch::
+ DESTINATION lib/cmake/mstch)
+
+install(FILES
+ "${PROJECT_SOURCE_DIR}/cmake/mstch-config.cmake"
+ "${CMAKE_CURRENT_BINARY_DIR}/mstch/mstch-config-version.cmake"
+ DESTINATION lib/cmake/mstch
+ COMPONENT Devel)
diff --git a/lib/mstch/src/mstch.cpp b/lib/mstch/src/mstch.cpp
new file mode 100644
index 0000000..4d84e97
--- /dev/null
+++ b/lib/mstch/src/mstch.cpp
@@ -0,0 +1,20 @@
+#include <iostream>
+
+#include "mstch/mstch.hpp"
+#include "render_context.hpp"
+
+using namespace mstch;
+
+std::function<std::string(const std::string&)> mstch::config::escape;
+
+std::string mstch::render(
+ const std::string& tmplt,
+ const node& root,
+ const std::map<std::string,std::string>& partials)
+{
+ std::map<std::string, template_type> partial_templates;
+ for (auto& partial: partials)
+ partial_templates.insert({partial.first, {partial.second}});
+
+ return render_context(root, partial_templates).render(tmplt);
+}
diff --git a/lib/mstch/src/render_context.cpp b/lib/mstch/src/render_context.cpp
new file mode 100644
index 0000000..90b2ffc
--- /dev/null
+++ b/lib/mstch/src/render_context.cpp
@@ -0,0 +1,72 @@
+#include "render_context.hpp"
+#include "state/outside_section.hpp"
+#include "visitor/get_token.hpp"
+
+using namespace mstch;
+
+const mstch::node render_context::null_node;
+
+render_context::push::push(render_context& context, const mstch::node& node):
+ m_context(context)
+{
+ context.m_nodes.emplace_front(node);
+ context.m_node_ptrs.emplace_front(&node);
+ context.m_state.push(std::unique_ptr<render_state>(new outside_section));
+}
+
+render_context::push::~push() {
+ m_context.m_nodes.pop_front();
+ m_context.m_node_ptrs.pop_front();
+ m_context.m_state.pop();
+}
+
+std::string render_context::push::render(const template_type& templt) {
+ return m_context.render(templt);
+}
+
+render_context::render_context(
+ const mstch::node& node,
+ const std::map<std::string, template_type>& partials):
+ m_partials(partials), m_nodes(1, node), m_node_ptrs(1, &node)
+{
+ m_state.push(std::unique_ptr<render_state>(new outside_section));
+}
+
+const mstch::node& render_context::find_node(
+ const std::string& token,
+ std::list<node const*> current_nodes)
+{
+ if (token != "." && token.find('.') != std::string::npos)
+ return find_node(token.substr(token.rfind('.') + 1),
+ {&find_node(token.substr(0, token.rfind('.')), current_nodes)});
+ else
+ for (auto& node: current_nodes)
+ if (visit(has_token(token), *node))
+ return visit(get_token(token, *node), *node);
+ return null_node;
+}
+
+const mstch::node& render_context::get_node(const std::string& token) {
+ return find_node(token, m_node_ptrs);
+}
+
+std::string render_context::render(
+ const template_type& templt, const std::string& prefix)
+{
+ std::string output;
+ bool prev_eol = true;
+ for (auto& token: templt) {
+ if (prev_eol && prefix.length() != 0)
+ output += m_state.top()->render(*this, {prefix});
+ output += m_state.top()->render(*this, token);
+ prev_eol = token.eol();
+ }
+ return output;
+}
+
+std::string render_context::render_partial(
+ const std::string& partial_name, const std::string& prefix)
+{
+ return m_partials.count(partial_name) ?
+ render(m_partials.at(partial_name), prefix) : "";
+}
diff --git a/lib/mstch/src/render_context.hpp b/lib/mstch/src/render_context.hpp
new file mode 100644
index 0000000..b97d41b
--- /dev/null
+++ b/lib/mstch/src/render_context.hpp
@@ -0,0 +1,51 @@
+#pragma once
+
+#include <deque>
+#include <list>
+#include <sstream>
+#include <string>
+#include <stack>
+
+#include "mstch/mstch.hpp"
+#include "state/render_state.hpp"
+#include "template_type.hpp"
+
+namespace mstch {
+
+class render_context {
+ public:
+ class push {
+ public:
+ push(render_context& context, const mstch::node& node = {});
+ ~push();
+ std::string render(const template_type& templt);
+ private:
+ render_context& m_context;
+ };
+
+ render_context(
+ const mstch::node& node,
+ const std::map<std::string, template_type>& partials);
+ const mstch::node& get_node(const std::string& token);
+ std::string render(
+ const template_type& templt, const std::string& prefix = "");
+ std::string render_partial(
+ const std::string& partial_name, const std::string& prefix);
+ template<class T, class... Args>
+ void set_state(Args&& ... args) {
+ m_state.top() = std::unique_ptr<render_state>(
+ new T(std::forward<Args>(args)...));
+ }
+
+ private:
+ static const mstch::node null_node;
+ const mstch::node& find_node(
+ const std::string& token,
+ std::list<node const*> current_nodes);
+ std::map<std::string, template_type> m_partials;
+ std::deque<mstch::node> m_nodes;
+ std::list<const mstch::node*> m_node_ptrs;
+ std::stack<std::unique_ptr<render_state>> m_state;
+};
+
+}
diff --git a/lib/mstch/src/state/in_section.cpp b/lib/mstch/src/state/in_section.cpp
new file mode 100644
index 0000000..a139913
--- /dev/null
+++ b/lib/mstch/src/state/in_section.cpp
@@ -0,0 +1,34 @@
+#include "in_section.hpp"
+#include "outside_section.hpp"
+#include "visitor/is_node_empty.hpp"
+#include "visitor/render_section.hpp"
+
+using namespace mstch;
+
+in_section::in_section(type type, const token& start_token):
+ m_type(type), m_start_token(start_token), m_skipped_openings(0)
+{
+}
+
+std::string in_section::render(render_context& ctx, const token& token) {
+ if (token.token_type() == token::type::section_close)
+ if (token.name() == m_start_token.name() && m_skipped_openings == 0) {
+ auto& node = ctx.get_node(m_start_token.name());
+ std::string out;
+
+ if (m_type == type::normal && !visit(is_node_empty(), node))
+ out = visit(render_section(ctx, m_section, m_start_token.delims()), node);
+ else if (m_type == type::inverted && visit(is_node_empty(), node))
+ out = render_context::push(ctx).render(m_section);
+
+ ctx.set_state<outside_section>();
+ return out;
+ } else
+ m_skipped_openings--;
+ else if (token.token_type() == token::type::inverted_section_open ||
+ token.token_type() == token::type::section_open)
+ m_skipped_openings++;
+
+ m_section << token;
+ return "";
+}
diff --git a/lib/mstch/src/state/in_section.hpp b/lib/mstch/src/state/in_section.hpp
new file mode 100644
index 0000000..14ca2f7
--- /dev/null
+++ b/lib/mstch/src/state/in_section.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <sstream>
+#include <vector>
+
+#include "render_state.hpp"
+#include "template_type.hpp"
+
+namespace mstch {
+
+class in_section: public render_state {
+ public:
+ enum class type { inverted, normal };
+ in_section(type type, const token& start_token);
+ std::string render(render_context& context, const token& token) override;
+
+ private:
+ const type m_type;
+ const token& m_start_token;
+ template_type m_section;
+ int m_skipped_openings;
+};
+
+}
diff --git a/lib/mstch/src/state/outside_section.cpp b/lib/mstch/src/state/outside_section.cpp
new file mode 100644
index 0000000..c9817b1
--- /dev/null
+++ b/lib/mstch/src/state/outside_section.cpp
@@ -0,0 +1,32 @@
+#include "outside_section.hpp"
+
+#include "visitor/render_node.hpp"
+#include "in_section.hpp"
+#include "render_context.hpp"
+
+using namespace mstch;
+
+std::string outside_section::render(
+ render_context& ctx, const token& token)
+{
+ using flag = render_node::flag;
+ switch (token.token_type()) {
+ case token::type::section_open:
+ ctx.set_state<in_section>(in_section::type::normal, token);
+ break;
+ case token::type::inverted_section_open:
+ ctx.set_state<in_section>(in_section::type::inverted, token);
+ break;
+ case token::type::variable:
+ return visit(render_node(ctx, flag::escape_html), ctx.get_node(token.name()));
+ case token::type::unescaped_variable:
+ return visit(render_node(ctx, flag::none), ctx.get_node(token.name()));
+ case token::type::text:
+ return token.raw();
+ case token::type::partial:
+ return ctx.render_partial(token.name(), token.partial_prefix());
+ default:
+ break;
+ }
+ return "";
+}
diff --git a/lib/mstch/src/state/outside_section.hpp b/lib/mstch/src/state/outside_section.hpp
new file mode 100644
index 0000000..8617dce
--- /dev/null
+++ b/lib/mstch/src/state/outside_section.hpp
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "render_state.hpp"
+
+namespace mstch {
+
+class outside_section: public render_state {
+ public:
+ std::string render(render_context& context, const token& token) override;
+};
+
+}
diff --git a/lib/mstch/src/state/render_state.hpp b/lib/mstch/src/state/render_state.hpp
new file mode 100644
index 0000000..e44a956
--- /dev/null
+++ b/lib/mstch/src/state/render_state.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <memory>
+
+#include "token.hpp"
+
+namespace mstch {
+
+class render_context;
+
+class render_state {
+ public:
+ virtual ~render_state() {}
+ virtual std::string render(render_context& context, const token& token) = 0;
+};
+
+}
diff --git a/lib/mstch/src/template_type.cpp b/lib/mstch/src/template_type.cpp
new file mode 100644
index 0000000..5b40e40
--- /dev/null
+++ b/lib/mstch/src/template_type.cpp
@@ -0,0 +1,104 @@
+#include "template_type.hpp"
+
+using namespace mstch;
+
+template_type::template_type(const std::string& str, const delim_type& delims):
+ m_open(delims.first), m_close(delims.second)
+{
+ tokenize(str);
+ strip_whitespace();
+}
+
+template_type::template_type(const std::string& str):
+ m_open("{{"), m_close("}}")
+{
+ tokenize(str);
+ strip_whitespace();
+}
+
+void template_type::process_text(citer begin, citer end) {
+ if (begin == end)
+ return;
+ auto start = begin;
+ for (auto it = begin; it != end; ++it)
+ if (*it == '\n' || it == end - 1) {
+ m_tokens.push_back({{start, it + 1}});
+ start = it + 1;
+ }
+}
+
+void template_type::tokenize(const std::string& tmp) {
+ citer beg = tmp.begin();
+ auto npos = std::string::npos;
+
+ for (std::size_t cur_pos = 0; cur_pos < tmp.size();) {
+ auto open_pos = tmp.find(m_open, cur_pos);
+ auto close_pos = tmp.find(
+ m_close, open_pos == npos ? open_pos : open_pos + 1);
+
+ if (close_pos != npos && open_pos != npos) {
+ if (*(beg + open_pos + m_open.size()) == '{' &&
+ *(beg + close_pos + m_close.size()) == '}')
+ ++close_pos;
+
+ process_text(beg + cur_pos, beg + open_pos);
+ cur_pos = close_pos + m_close.size();
+ m_tokens.push_back({{beg + open_pos, beg + close_pos + m_close.size()},
+ m_open.size(), m_close.size()});
+
+ if (cur_pos == tmp.size()) {
+ m_tokens.push_back({{""}});
+ m_tokens.back().eol(true);
+ }
+
+ if (*(beg + open_pos + m_open.size()) == '=' &&
+ *(beg + close_pos - 1) == '=')
+ {
+ auto tok_beg = beg + open_pos + m_open.size() + 1;
+ auto tok_end = beg + close_pos - 1;
+ auto front_skip = first_not_ws(tok_beg, tok_end);
+ auto back_skip = first_not_ws(reverse(tok_end), reverse(tok_beg));
+ m_open = {front_skip, beg + tmp.find(' ', front_skip - beg)};
+ m_close = {beg + tmp.rfind(' ', back_skip - beg) + 1, back_skip + 1};
+ }
+ } else {
+ process_text(beg + cur_pos, tmp.end());
+ cur_pos = close_pos;
+ }
+ }
+}
+
+void template_type::strip_whitespace() {
+ auto line_begin = m_tokens.begin();
+ bool has_tag = false, non_space = false;
+
+ for (auto it = m_tokens.begin(); it != m_tokens.end(); ++it) {
+ auto type = (*it).token_type();
+ if (type != token::type::text && type != token::type::variable &&
+ type != token::type::unescaped_variable)
+ has_tag = true;
+ else if (!(*it).ws_only())
+ non_space = true;
+
+ if ((*it).eol()) {
+ if (has_tag && !non_space) {
+ store_prefixes(line_begin);
+
+ auto c = line_begin;
+ for (bool end = false; !end; (*c).ws_only() ? c = m_tokens.erase(c) : ++c)
+ if ((end = (*c).eol()))
+ it = c - 1;
+ }
+
+ non_space = has_tag = false;
+ line_begin = it + 1;
+ }
+ }
+}
+
+void template_type::store_prefixes(std::vector<token>::iterator beg) {
+ for (auto cur = beg; !(*cur).eol(); ++cur)
+ if ((*cur).token_type() == token::type::partial &&
+ cur != beg && (*(cur - 1)).ws_only())
+ (*cur).partial_prefix((*(cur - 1)).raw());
+}
diff --git a/lib/mstch/src/template_type.hpp b/lib/mstch/src/template_type.hpp
new file mode 100644
index 0000000..3c5bf91
--- /dev/null
+++ b/lib/mstch/src/template_type.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "token.hpp"
+#include "utils.hpp"
+
+namespace mstch {
+
+class template_type {
+ public:
+ template_type() = default;
+ template_type(const std::string& str);
+ template_type(const std::string& str, const delim_type& delims);
+ std::vector<token>::const_iterator begin() const { return m_tokens.begin(); }
+ std::vector<token>::const_iterator end() const { return m_tokens.end(); }
+ void operator<<(const token& token) { m_tokens.push_back(token); }
+
+ private:
+ std::vector<token> m_tokens;
+ std::string m_open;
+ std::string m_close;
+ void strip_whitespace();
+ void process_text(citer beg, citer end);
+ void tokenize(const std::string& tmp);
+ void store_prefixes(std::vector<token>::iterator beg);
+};
+
+}
diff --git a/lib/mstch/src/token.cpp b/lib/mstch/src/token.cpp
new file mode 100644
index 0000000..9443a22
--- /dev/null
+++ b/lib/mstch/src/token.cpp
@@ -0,0 +1,42 @@
+#include "token.hpp"
+#include "utils.hpp"
+
+using namespace mstch;
+
+token::type token::token_info(char c) {
+ switch (c) {
+ case '>': return type::partial;
+ case '^': return type::inverted_section_open;
+ case '/': return type::section_close;
+ case '&': return type::unescaped_variable;
+ case '#': return type::section_open;
+ case '!': return type::comment;
+ default: return type::variable;
+ }
+}
+
+token::token(const std::string& str, std::size_t left, std::size_t right):
+ m_raw(str), m_eol(false), m_ws_only(false)
+{
+ if (left != 0 && right != 0) {
+ if (str[left] == '=' && str[str.size() - right - 1] == '=') {
+ m_type = type::delimiter_change;
+ } else if (str[left] == '{' && str[str.size() - right - 1] == '}') {
+ m_type = type::unescaped_variable;
+ m_name = {first_not_ws(str.begin() + left + 1, str.end() - right),
+ first_not_ws(str.rbegin() + 1 + right, str.rend() - left) + 1};
+ } else {
+ auto c = first_not_ws(str.begin() + left, str.end() - right);
+ m_type = token_info(*c);
+ if (m_type != type::variable)
+ c = first_not_ws(c + 1, str.end() - right);
+ m_name = {c, first_not_ws(str.rbegin() + right, str.rend() - left) + 1};
+ m_delims = {{str.begin(), str.begin() + left},
+ {str.end() - right, str.end()}};
+ }
+ } else {
+ m_type = type::text;
+ m_eol = (str.size() > 0 && str[str.size() - 1] == '\n');
+ m_ws_only = (str.find_first_not_of(" \r\n\t") == std::string::npos);
+ }
+}
diff --git a/lib/mstch/src/token.hpp b/lib/mstch/src/token.hpp
new file mode 100644
index 0000000..fde8017
--- /dev/null
+++ b/lib/mstch/src/token.hpp
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <string>
+
+namespace mstch {
+
+using delim_type = std::pair<std::string, std::string>;
+
+class token {
+ public:
+ enum class type {
+ text, variable, section_open, section_close, inverted_section_open,
+ unescaped_variable, comment, partial, delimiter_change
+ };
+ token(const std::string& str, std::size_t left = 0, std::size_t right = 0);
+ type token_type() const { return m_type; };
+ const std::string& raw() const { return m_raw; };
+ const std::string& name() const { return m_name; };
+ const std::string& partial_prefix() const { return m_partial_prefix; };
+ const delim_type& delims() const { return m_delims; };
+ void partial_prefix(const std::string& p_partial_prefix) {
+ m_partial_prefix = p_partial_prefix;
+ };
+ bool eol() const { return m_eol; }
+ void eol(bool eol) { m_eol = eol; }
+ bool ws_only() const { return m_ws_only; }
+
+ private:
+ type m_type;
+ std::string m_name;
+ std::string m_raw;
+ std::string m_partial_prefix;
+ delim_type m_delims;
+ bool m_eol;
+ bool m_ws_only;
+ type token_info(char c);
+};
+
+}
diff --git a/lib/mstch/src/utils.cpp b/lib/mstch/src/utils.cpp
new file mode 100644
index 0000000..1646170
--- /dev/null
+++ b/lib/mstch/src/utils.cpp
@@ -0,0 +1,44 @@
+#include "utils.hpp"
+#include "mstch/mstch.hpp"
+
+mstch::citer mstch::first_not_ws(mstch::citer begin, mstch::citer end) {
+ for (auto it = begin; it != end; ++it)
+ if (*it != ' ') return it;
+ return end;
+}
+
+mstch::citer mstch::first_not_ws(mstch::criter begin, mstch::criter end) {
+ for (auto rit = begin; rit != end; ++rit)
+ if (*rit != ' ') return --(rit.base());
+ return --(end.base());
+}
+
+mstch::criter mstch::reverse(mstch::citer it) {
+ return std::reverse_iterator<mstch::citer>(it);
+}
+
+std::string mstch::html_escape(const std::string& str) {
+ if (mstch::config::escape)
+ return mstch::config::escape(str);
+
+ std::string out;
+ citer start = str.begin();
+
+ auto add_escape = [&out, &start](const std::string& escaped, citer& it) {
+ out += std::string{start, it} + escaped;
+ start = it + 1;
+ };
+
+ for (auto it = str.begin(); it != str.end(); ++it)
+ switch (*it) {
+ case '&': add_escape("&amp;", it); break;
+ case '\'': add_escape("&#39;", it); break;
+ case '"': add_escape("&quot;", it); break;
+ case '<': add_escape("&lt;", it); break;
+ case '>': add_escape("&gt;", it); break;
+ case '/': add_escape("&#x2F;", it); break;
+ default: break;
+ }
+
+ return out + std::string{start, str.end()};
+}
diff --git a/lib/mstch/src/utils.hpp b/lib/mstch/src/utils.hpp
new file mode 100644
index 0000000..9041a3e
--- /dev/null
+++ b/lib/mstch/src/utils.hpp
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <string>
+#include <boost/variant/apply_visitor.hpp>
+
+namespace mstch {
+
+using citer = std::string::const_iterator;
+using criter = std::string::const_reverse_iterator;
+
+citer first_not_ws(citer begin, citer end);
+citer first_not_ws(criter begin, criter end);
+std::string html_escape(const std::string& str);
+criter reverse(citer it);
+
+template<class... Args>
+auto visit(Args&&... args) -> decltype(boost::apply_visitor(
+ std::forward<Args>(args)...))
+{
+ return boost::apply_visitor(std::forward<Args>(args)...);
+}
+
+}
diff --git a/lib/mstch/src/visitor/get_token.hpp b/lib/mstch/src/visitor/get_token.hpp
new file mode 100644
index 0000000..d41ab6e
--- /dev/null
+++ b/lib/mstch/src/visitor/get_token.hpp
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <boost/variant/static_visitor.hpp>
+
+#include "mstch/mstch.hpp"
+#include "has_token.hpp"
+
+namespace mstch {
+
+class get_token: public boost::static_visitor<const mstch::node&> {
+ public:
+ get_token(const std::string& token, const mstch::node& node):
+ m_token(token), m_node(node)
+ {
+ }
+
+ template<class T>
+ const mstch::node& operator()(const T&) const {
+ return m_node;
+ }
+
+ const mstch::node& operator()(const map& map) const {
+ return map.at(m_token);
+ }
+
+ const mstch::node& operator()(const std::shared_ptr<object>& object) const {
+ return object->at(m_token);
+ }
+
+ private:
+ const std::string& m_token;
+ const mstch::node& m_node;
+};
+
+}
diff --git a/lib/mstch/src/visitor/has_token.hpp b/lib/mstch/src/visitor/has_token.hpp
new file mode 100644
index 0000000..5ab30d4
--- /dev/null
+++ b/lib/mstch/src/visitor/has_token.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <boost/variant/static_visitor.hpp>
+
+#include "mstch/mstch.hpp"
+
+namespace mstch {
+
+class has_token: public boost::static_visitor<bool> {
+ public:
+ has_token(const std::string& token): m_token(token) {
+ }
+
+ template<class T>
+ bool operator()(const T&) const {
+ return m_token == ".";
+ }
+
+ bool operator()(const map& map) const {
+ return map.count(m_token) == 1;
+ }
+
+ bool operator()(const std::shared_ptr<object>& object) const {
+ return object->has(m_token);
+ }
+
+ private:
+ const std::string& m_token;
+};
+
+}
diff --git a/lib/mstch/src/visitor/is_node_empty.hpp b/lib/mstch/src/visitor/is_node_empty.hpp
new file mode 100644
index 0000000..a0ae432
--- /dev/null
+++ b/lib/mstch/src/visitor/is_node_empty.hpp
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <boost/variant/static_visitor.hpp>
+
+#include "mstch/mstch.hpp"
+
+namespace mstch {
+
+class is_node_empty: public boost::static_visitor<bool> {
+ public:
+ template<class T>
+ bool operator()(const T&) const {
+ return false;
+ }
+
+ bool operator()(const std::nullptr_t&) const {
+ return true;
+ }
+
+ bool operator()(const int& value) const {
+ return value == 0;
+ }
+
+ bool operator()(const double& value) const {
+ return value == 0;
+ }
+
+ bool operator()(const bool& value) const {
+ return !value;
+ }
+
+ bool operator()(const std::string& value) const {
+ return value == "";
+ }
+
+ bool operator()(const array& array) const {
+ return array.size() == 0;
+ }
+};
+
+}
diff --git a/lib/mstch/src/visitor/render_node.hpp b/lib/mstch/src/visitor/render_node.hpp
new file mode 100644
index 0000000..633dd4d
--- /dev/null
+++ b/lib/mstch/src/visitor/render_node.hpp
@@ -0,0 +1,56 @@
+#pragma once
+
+#include <sstream>
+#include <boost/variant/static_visitor.hpp>
+
+#include "render_context.hpp"
+#include "mstch/mstch.hpp"
+#include "utils.hpp"
+
+namespace mstch {
+
+class render_node: public boost::static_visitor<std::string> {
+ public:
+ enum class flag { none, escape_html };
+ render_node(render_context& ctx, flag p_flag = flag::none):
+ m_ctx(ctx), m_flag(p_flag)
+ {
+ }
+
+ template<class T>
+ std::string operator()(const T&) const {
+ return "";
+ }
+
+ std::string operator()(const int& value) const {
+ return std::to_string(value);
+ }
+
+ std::string operator()(const double& value) const {
+ std::stringstream ss;
+ ss << value;
+ return ss.str();
+ }
+
+ std::string operator()(const bool& value) const {
+ return value ? "true" : "false";
+ }
+
+ std::string operator()(const lambda& value) const {
+ template_type interpreted{value([this](const mstch::node& n) {
+ return visit(render_node(m_ctx), n);
+ })};
+ auto rendered = render_context::push(m_ctx).render(interpreted);
+ return (m_flag == flag::escape_html) ? html_escape(rendered) : rendered;
+ }
+
+ std::string operator()(const std::string& value) const {
+ return (m_flag == flag::escape_html) ? html_escape(value) : value;
+ }
+
+ private:
+ render_context& m_ctx;
+ flag m_flag;
+};
+
+}
diff --git a/lib/mstch/src/visitor/render_section.hpp b/lib/mstch/src/visitor/render_section.hpp
new file mode 100644
index 0000000..f2d5259
--- /dev/null
+++ b/lib/mstch/src/visitor/render_section.hpp
@@ -0,0 +1,57 @@
+#pragma once
+
+#include <boost/variant/static_visitor.hpp>
+
+#include "render_context.hpp"
+#include "mstch/mstch.hpp"
+#include "utils.hpp"
+#include "render_node.hpp"
+
+namespace mstch {
+
+class render_section: public boost::static_visitor<std::string> {
+ public:
+ enum class flag { none, keep_array };
+ render_section(
+ render_context& ctx,
+ const template_type& section,
+ const delim_type& delims,
+ flag p_flag = flag::none):
+ m_ctx(ctx), m_section(section), m_delims(delims), m_flag(p_flag)
+ {
+ }
+
+ template<class T>
+ std::string operator()(const T& t) const {
+ return render_context::push(m_ctx, t).render(m_section);
+ }
+
+ std::string operator()(const lambda& fun) const {
+ std::string section_str;
+ for (auto& token: m_section)
+ section_str += token.raw();
+ template_type interpreted{fun([this](const mstch::node& n) {
+ return visit(render_node(m_ctx), n);
+ }, section_str), m_delims};
+ return render_context::push(m_ctx).render(interpreted);
+ }
+
+ std::string operator()(const array& array) const {
+ std::string out;
+ if (m_flag == flag::keep_array)
+ return render_context::push(m_ctx, array).render(m_section);
+ else
+ for (auto& item: array)
+ out += visit(render_section(
+ m_ctx, m_section, m_delims, flag::keep_array), item);
+ return out;
+ }
+
+ private:
+ render_context& m_ctx;
+ const template_type& m_section;
+ const delim_type& m_delims;
+ flag m_flag;
+};
+
+}
diff --git a/lib/mstch/test/CMakeLists.txt b/lib/mstch/test/CMakeLists.txt
new file mode 100644
index 0000000..061bc2e
--- /dev/null
+++ b/lib/mstch/test/CMakeLists.txt
@@ -0,0 +1,59 @@
+find_package(Boost 1.54 REQUIRED)
+
+include_directories(
+ ${CMAKE_SOURCE_DIR}/include
+ ${CMAKE_SOURCE_DIR}/vendor/Catch/single_include
+ ${CMAKE_SOURCE_DIR}/vendor/rapidjson/include
+ ${Boost_INCLUDE_DIR})
+
+file(GLOB data_files RELATIVE
+ "${CMAKE_SOURCE_DIR}/test/data"
+ "${CMAKE_SOURCE_DIR}/test/data/*.hpp")
+
+foreach(data_file ${data_files})
+ string(REGEX REPLACE "\\.hpp" "" test_name "${data_file}")
+ list(APPEND tests "${test_name}")
+endforeach(data_file)
+
+file(GLOB string_files RELATIVE
+ "${CMAKE_SOURCE_DIR}/test/data"
+ "${CMAKE_SOURCE_DIR}/test/data/*.mustache"
+ "${CMAKE_SOURCE_DIR}/test/data/*.txt"
+ "${CMAKE_SOURCE_DIR}/test/data/*.partial")
+
+foreach(string_file ${string_files})
+ list(APPEND genargs "-i${string_file}")
+endforeach(string_file)
+
+add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/test_data.hpp
+ COMMAND headerize --output ${CMAKE_CURRENT_BINARY_DIR}/test_data.hpp --namespace mstchtest ${genargs}
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/test/data/)
+set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/test_data.hpp PROPERTIES GENERATED TRUE)
+add_custom_target(test_data_hpp DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/test_data.hpp)
+
+file(GLOB specs_files RELATIVE
+ "${CMAKE_SOURCE_DIR}/vendor/spec/specs"
+ "${CMAKE_SOURCE_DIR}/vendor/spec/specs/*.json")
+
+foreach(specs_file ${specs_files})
+ list(APPEND specsargs "-i${specs_file}")
+ string(REGEX REPLACE "\\.json" "" test_name "${specs_file}")
+ string(REGEX REPLACE "~" "" test_name "${test_name}")
+ list(APPEND tests "specs_${test_name}")
+endforeach(specs_file)
+
+add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/specs_data.hpp
+ COMMAND headerize --output ${CMAKE_CURRENT_BINARY_DIR}/specs_data.hpp --namespace mstchtest ${specsargs}
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/vendor/spec/specs/)
+set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/specs_data.hpp PROPERTIES GENERATED TRUE)
+add_custom_target(specs_data_hpp DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/specs_data.hpp)
+
+add_executable(mstch_test test_main.cpp)
+target_link_libraries(mstch_test mstch)
+add_dependencies(mstch_test test_data_hpp specs_data_hpp)
+
+foreach(test ${tests})
+ add_test(NAME ${test} COMMAND mstch_test ${test})
+endforeach(test)
diff --git a/lib/mstch/test/data/ampersand_escape.hpp b/lib/mstch/test/data/ampersand_escape.hpp
new file mode 100644
index 0000000..b307d50
--- /dev/null
+++ b/lib/mstch/test/data/ampersand_escape.hpp
@@ -0,0 +1,3 @@
+const auto ampersand_escape_data = mstch::map{
+ {"message", std::string{"Some <code>"}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/ampersand_escape.mustache b/lib/mstch/test/data/ampersand_escape.mustache
new file mode 100644
index 0000000..6501a48
--- /dev/null
+++ b/lib/mstch/test/data/ampersand_escape.mustache
@@ -0,0 +1 @@
+{{&message}}
diff --git a/lib/mstch/test/data/ampersand_escape.txt b/lib/mstch/test/data/ampersand_escape.txt
new file mode 100644
index 0000000..2ed3fd3
--- /dev/null
+++ b/lib/mstch/test/data/ampersand_escape.txt
@@ -0,0 +1 @@
+Some <code>
diff --git a/lib/mstch/test/data/apostrophe.hpp b/lib/mstch/test/data/apostrophe.hpp
new file mode 100644
index 0000000..94738f4
--- /dev/null
+++ b/lib/mstch/test/data/apostrophe.hpp
@@ -0,0 +1,4 @@
+const auto apostrophe_data = mstch::map{
+ {"apos", std::string{"'"}},
+ {"control", std::string{"X"}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/apostrophe.mustache b/lib/mstch/test/data/apostrophe.mustache
new file mode 100644
index 0000000..e8687aa
--- /dev/null
+++ b/lib/mstch/test/data/apostrophe.mustache
@@ -0,0 +1 @@
+{{apos}}{{control}}
diff --git a/lib/mstch/test/data/apostrophe.txt b/lib/mstch/test/data/apostrophe.txt
new file mode 100644
index 0000000..4427c30
--- /dev/null
+++ b/lib/mstch/test/data/apostrophe.txt
@@ -0,0 +1 @@
+&#39;X
diff --git a/lib/mstch/test/data/array_of_strings.hpp b/lib/mstch/test/data/array_of_strings.hpp
new file mode 100644
index 0000000..5511c5d
--- /dev/null
+++ b/lib/mstch/test/data/array_of_strings.hpp
@@ -0,0 +1,3 @@
+const auto array_of_strings_data = mstch::map{
+ {"array_of_strings", mstch::array{std::string{"hello"}, std::string{"world"}}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/array_of_strings.mustache b/lib/mstch/test/data/array_of_strings.mustache
new file mode 100644
index 0000000..4d65738
--- /dev/null
+++ b/lib/mstch/test/data/array_of_strings.mustache
@@ -0,0 +1 @@
+{{#array_of_strings}}{{.}} {{/array_of_strings}}
diff --git a/lib/mstch/test/data/array_of_strings.txt b/lib/mstch/test/data/array_of_strings.txt
new file mode 100644
index 0000000..4a1f475
--- /dev/null
+++ b/lib/mstch/test/data/array_of_strings.txt
@@ -0,0 +1 @@
+hello world
diff --git a/lib/mstch/test/data/backslashes.hpp b/lib/mstch/test/data/backslashes.hpp
new file mode 100644
index 0000000..7969339
--- /dev/null
+++ b/lib/mstch/test/data/backslashes.hpp
@@ -0,0 +1,3 @@
+const auto backslashes_data = mstch::map{
+ {"value", std::string{"\\abc"}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/backslashes.mustache b/lib/mstch/test/data/backslashes.mustache
new file mode 100644
index 0000000..fe7745b
--- /dev/null
+++ b/lib/mstch/test/data/backslashes.mustache
@@ -0,0 +1,7 @@
+* {{value}}
+* {{{value}}}
+* {{&value}}
+<script>
+foo = { bar: 'abc\"xyz\"' };
+foo = { bar: 'x\'y' };
+</script>
diff --git a/lib/mstch/test/data/backslashes.txt b/lib/mstch/test/data/backslashes.txt
new file mode 100644
index 0000000..038dd37
--- /dev/null
+++ b/lib/mstch/test/data/backslashes.txt
@@ -0,0 +1,7 @@
+* \abc
+* \abc
+* \abc
+<script>
+foo = { bar: 'abc\"xyz\"' };
+foo = { bar: 'x\'y' };
+</script>
diff --git a/lib/mstch/test/data/bug_11_eating_whitespace.hpp b/lib/mstch/test/data/bug_11_eating_whitespace.hpp
new file mode 100644
index 0000000..57ce819
--- /dev/null
+++ b/lib/mstch/test/data/bug_11_eating_whitespace.hpp
@@ -0,0 +1,3 @@
+const auto bug_11_eating_whitespace_data = mstch::map{
+ {"tag", std::string{"yo"}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/bug_11_eating_whitespace.mustache b/lib/mstch/test/data/bug_11_eating_whitespace.mustache
new file mode 100644
index 0000000..8d5cd92
--- /dev/null
+++ b/lib/mstch/test/data/bug_11_eating_whitespace.mustache
@@ -0,0 +1 @@
+{{tag}} foo
diff --git a/lib/mstch/test/data/bug_11_eating_whitespace.txt b/lib/mstch/test/data/bug_11_eating_whitespace.txt
new file mode 100644
index 0000000..f5bbc85
--- /dev/null
+++ b/lib/mstch/test/data/bug_11_eating_whitespace.txt
@@ -0,0 +1 @@
+yo foo
diff --git a/lib/mstch/test/data/bug_length_property.hpp b/lib/mstch/test/data/bug_length_property.hpp
new file mode 100644
index 0000000..533b596
--- /dev/null
+++ b/lib/mstch/test/data/bug_length_property.hpp
@@ -0,0 +1,3 @@
+const auto bug_length_property_data = mstch::map{
+ {"length", std::string{"hello"}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/bug_length_property.mustache b/lib/mstch/test/data/bug_length_property.mustache
new file mode 100644
index 0000000..b000887
--- /dev/null
+++ b/lib/mstch/test/data/bug_length_property.mustache
@@ -0,0 +1 @@
+{{#length}}The length variable is: {{length}}{{/length}}
diff --git a/lib/mstch/test/data/bug_length_property.txt b/lib/mstch/test/data/bug_length_property.txt
new file mode 100644
index 0000000..f5355d3
--- /dev/null
+++ b/lib/mstch/test/data/bug_length_property.txt
@@ -0,0 +1 @@
+The length variable is: hello
diff --git a/lib/mstch/test/data/changing_delimiters.hpp b/lib/mstch/test/data/changing_delimiters.hpp
new file mode 100644
index 0000000..1322543
--- /dev/null
+++ b/lib/mstch/test/data/changing_delimiters.hpp
@@ -0,0 +1,4 @@
+const mstch::node changing_delimiters_data = mstch::map{
+ {"foo", std::string{"foooooooooooooo"}},
+ {"bar", std::string{"<b>bar!</b>"}}
+};
diff --git a/lib/mstch/test/data/changing_delimiters.mustache b/lib/mstch/test/data/changing_delimiters.mustache
new file mode 100644
index 0000000..0cd044c
--- /dev/null
+++ b/lib/mstch/test/data/changing_delimiters.mustache
@@ -0,0 +1 @@
+{{=<% %>=}}<% foo %> {{foo}} <%{bar}%> {{{bar}}}
diff --git a/lib/mstch/test/data/changing_delimiters.txt b/lib/mstch/test/data/changing_delimiters.txt
new file mode 100644
index 0000000..1b1510d
--- /dev/null
+++ b/lib/mstch/test/data/changing_delimiters.txt
@@ -0,0 +1 @@
+foooooooooooooo {{foo}} <b>bar!</b> {{{bar}}}
diff --git a/lib/mstch/test/data/comments.hpp b/lib/mstch/test/data/comments.hpp
new file mode 100644
index 0000000..96baccb
--- /dev/null
+++ b/lib/mstch/test/data/comments.hpp
@@ -0,0 +1,3 @@
+const mstch::node comments_data = mstch::map{
+ {"title", mstch::lambda{[]()->mstch::node{return std::string{"A Comedy of Errors"};}}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/comments.mustache b/lib/mstch/test/data/comments.mustache
new file mode 100644
index 0000000..5036801
--- /dev/null
+++ b/lib/mstch/test/data/comments.mustache
@@ -0,0 +1 @@
+<h1>{{title}}{{! just something interesting... or not... }}</h1>
diff --git a/lib/mstch/test/data/comments.txt b/lib/mstch/test/data/comments.txt
new file mode 100644
index 0000000..0133517
--- /dev/null
+++ b/lib/mstch/test/data/comments.txt
@@ -0,0 +1 @@
+<h1>A Comedy of Errors</h1>
diff --git a/lib/mstch/test/data/complex.hpp b/lib/mstch/test/data/complex.hpp
new file mode 100644
index 0000000..a438098
--- /dev/null
+++ b/lib/mstch/test/data/complex.hpp
@@ -0,0 +1,69 @@
+class complex_item: public mstch::object {
+private:
+ std::string m_name;
+ bool m_current;
+ std::string m_url;
+public:
+ complex_item(const std::string& name, bool current, const std::string& url):
+ m_name(name), m_current(current), m_url(url)
+ {
+ register_methods(this, std::map<std::string,mstch::node(complex_item::*)()>{
+ {"name", &complex_item::name}, {"current", &complex_item::current},
+ {"url", &complex_item::url}, {"link", &complex_item::link}
+ });
+ }
+
+ mstch::node current() {
+ return m_current;
+ }
+
+ mstch::node url() {
+ return m_url;
+ }
+
+ mstch::node name() {
+ return m_name;
+ }
+
+ mstch::node link() {
+ return !m_current;
+ }
+};
+
+class complex: public mstch::object {
+private:
+ std::string m_header;
+ mstch::array m_item;
+public:
+ complex():
+ m_header("Colors"),
+ m_item(mstch::array{
+ std::make_shared<complex_item>("red", true, "#Red"),
+ std::make_shared<complex_item>("green", false, "#Green"),
+ std::make_shared<complex_item>("blue", false, "#Blue")
+ })
+ {
+ register_methods(this, std::map<std::string,mstch::node(complex::*)()>{
+ {"header", &complex::header}, {"item", &complex::item},
+ {"list", &complex::list}, {"empty", &complex::empty}
+ });
+ }
+
+ mstch::node header() {
+ return m_header;
+ }
+
+ mstch::node item() {
+ return m_item;
+ }
+
+ mstch::node list() {
+ return m_item.size() != 0;
+ }
+
+ mstch::node empty() {
+ return m_item.size() == 0;
+ }
+};
+
+const auto complex_data = std::make_shared<complex>(); \ No newline at end of file
diff --git a/lib/mstch/test/data/complex.mustache b/lib/mstch/test/data/complex.mustache
new file mode 100644
index 0000000..869a4f0
--- /dev/null
+++ b/lib/mstch/test/data/complex.mustache
@@ -0,0 +1,16 @@
+<h1>{{header}}</h1>
+{{#list}}
+ <ul>
+ {{#item}}
+ {{#current}}
+ <li><strong>{{name}}</strong></li>
+ {{/current}}
+ {{#link}}
+ <li><a href="{{url}}">{{name}}</a></li>
+ {{/link}}
+ {{/item}}
+ </ul>
+{{/list}}
+{{#empty}}
+ <p>The list is empty.</p>
+{{/empty}}
diff --git a/lib/mstch/test/data/complex.txt b/lib/mstch/test/data/complex.txt
new file mode 100644
index 0000000..596d3f6
--- /dev/null
+++ b/lib/mstch/test/data/complex.txt
@@ -0,0 +1,6 @@
+<h1>Colors</h1>
+ <ul>
+ <li><strong>red</strong></li>
+ <li><a href="#Green">green</a></li>
+ <li><a href="#Blue">blue</a></li>
+ </ul>
diff --git a/lib/mstch/test/data/context_lookup.hpp b/lib/mstch/test/data/context_lookup.hpp
new file mode 100644
index 0000000..c799080
--- /dev/null
+++ b/lib/mstch/test/data/context_lookup.hpp
@@ -0,0 +1,8 @@
+const auto context_lookup_data = mstch::map{
+ {"outer", mstch::map{
+ {"id", 1},
+ {"second", mstch::map{
+ {"nothing", 2}
+ }}
+ }}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/context_lookup.mustache b/lib/mstch/test/data/context_lookup.mustache
new file mode 100644
index 0000000..3c7b767
--- /dev/null
+++ b/lib/mstch/test/data/context_lookup.mustache
@@ -0,0 +1 @@
+{{#outer}}{{#second}}{{id}}{{/second}}{{/outer}}
diff --git a/lib/mstch/test/data/context_lookup.txt b/lib/mstch/test/data/context_lookup.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/lib/mstch/test/data/context_lookup.txt
@@ -0,0 +1 @@
+1
diff --git a/lib/mstch/test/data/delimiters.hpp b/lib/mstch/test/data/delimiters.hpp
new file mode 100644
index 0000000..0e560ab
--- /dev/null
+++ b/lib/mstch/test/data/delimiters.hpp
@@ -0,0 +1,6 @@
+const mstch::node delimiters_data = mstch::map{
+ {"first", std::string{"It worked the first time."}},
+ {"second", std::string{"And it worked the second time."}},
+ {"third", std::string{"Then, surprisingly, it worked the third time."}},
+ {"fourth", std::string{"Fourth time also fine!."}}
+};
diff --git a/lib/mstch/test/data/delimiters.mustache b/lib/mstch/test/data/delimiters.mustache
new file mode 100644
index 0000000..7fac846
--- /dev/null
+++ b/lib/mstch/test/data/delimiters.mustache
@@ -0,0 +1,7 @@
+{{=<% %>=}}*
+<% first %>
+* <% second %>
+<%=| |=%>
+* | third |
+|={{ }}=|
+* {{ fourth }}
diff --git a/lib/mstch/test/data/delimiters.txt b/lib/mstch/test/data/delimiters.txt
new file mode 100644
index 0000000..698a6bb
--- /dev/null
+++ b/lib/mstch/test/data/delimiters.txt
@@ -0,0 +1,5 @@
+*
+It worked the first time.
+* And it worked the second time.
+* Then, surprisingly, it worked the third time.
+* Fourth time also fine!.
diff --git a/lib/mstch/test/data/disappearing_whitespace.hpp b/lib/mstch/test/data/disappearing_whitespace.hpp
new file mode 100644
index 0000000..5322fe4
--- /dev/null
+++ b/lib/mstch/test/data/disappearing_whitespace.hpp
@@ -0,0 +1,4 @@
+const auto disappearing_whitespace_data = mstch::map{
+ {"bedrooms", true},
+ {"total", 1}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/disappearing_whitespace.mustache b/lib/mstch/test/data/disappearing_whitespace.mustache
new file mode 100644
index 0000000..16c16e0
--- /dev/null
+++ b/lib/mstch/test/data/disappearing_whitespace.mustache
@@ -0,0 +1 @@
+{{#bedrooms}}{{total}}{{/bedrooms}} BED
diff --git a/lib/mstch/test/data/disappearing_whitespace.txt b/lib/mstch/test/data/disappearing_whitespace.txt
new file mode 100644
index 0000000..66e98ef
--- /dev/null
+++ b/lib/mstch/test/data/disappearing_whitespace.txt
@@ -0,0 +1 @@
+1 BED
diff --git a/lib/mstch/test/data/dot_notation.hpp b/lib/mstch/test/data/dot_notation.hpp
new file mode 100644
index 0000000..c287107
--- /dev/null
+++ b/lib/mstch/test/data/dot_notation.hpp
@@ -0,0 +1,34 @@
+class dot_notation_price: public mstch::object {
+private:
+ int m_value;
+ mstch::map m_currency;
+public:
+ dot_notation_price():
+ m_value(200), m_currency(mstch::map{{"symbol", std::string{"$"}}, {"name", std::string{"USD"}}})
+ {
+ register_methods(this, std::map<std::string,mstch::node(dot_notation_price::*)()>{
+ {"value", &dot_notation_price::value},
+ {"vat", &dot_notation_price::vat},
+ {"currency", &dot_notation_price::currency}});
+ }
+
+ mstch::node value() {
+ return m_value;
+ }
+
+ mstch::node vat() {
+ return m_value * 0.2;
+ }
+
+ mstch::node currency() {
+ return m_currency;
+ }
+};
+
+const auto dot_notation_data = mstch::map{
+ {"name", std::string{"A Book"}},
+ {"authors", mstch::array{std::string{"John Power"}, std::string{"Jamie Walsh"}}},
+ {"price", std::make_shared<dot_notation_price>()},
+ {"availability", mstch::map{{"status", true}, {"text", std::string{"In Stock"}}}},
+ {"truthy", mstch::map{{"zero", 0}, {"notTrue", false}}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/dot_notation.mustache b/lib/mstch/test/data/dot_notation.mustache
new file mode 100644
index 0000000..f89d70b
--- /dev/null
+++ b/lib/mstch/test/data/dot_notation.mustache
@@ -0,0 +1,9 @@
+<!-- exciting part -->
+<h1>{{name}}</h1>
+<p>Authors: <ul>{{#authors}}<li>{{.}}</li>{{/authors}}</ul></p>
+<p>Price: {{{price.currency.symbol}}}{{price.value}} {{#price.currency}}{{name}} <b>{{availability.text}}</b>{{/price.currency}}</p>
+<p>VAT: {{{price.currency.symbol}}}{{#price}}{{vat}}{{/price}}</p>
+<!-- boring part -->
+<h2>Test truthy false values:</h2>
+<p>Zero: {{truthy.zero}}</p>
+<p>False: {{truthy.notTrue}}</p>
diff --git a/lib/mstch/test/data/dot_notation.txt b/lib/mstch/test/data/dot_notation.txt
new file mode 100644
index 0000000..08afa05
--- /dev/null
+++ b/lib/mstch/test/data/dot_notation.txt
@@ -0,0 +1,9 @@
+<!-- exciting part -->
+<h1>A Book</h1>
+<p>Authors: <ul><li>John Power</li><li>Jamie Walsh</li></ul></p>
+<p>Price: $200 USD <b>In Stock</b></p>
+<p>VAT: $40</p>
+<!-- boring part -->
+<h2>Test truthy false values:</h2>
+<p>Zero: 0</p>
+<p>False: false</p>
diff --git a/lib/mstch/test/data/double_render.hpp b/lib/mstch/test/data/double_render.hpp
new file mode 100644
index 0000000..eb24dc1
--- /dev/null
+++ b/lib/mstch/test/data/double_render.hpp
@@ -0,0 +1,5 @@
+const auto double_render_data = mstch::map{
+ {"foo", true},
+ {"bar", std::string{"{{win}}"}},
+ {"win", std::string{"FAIL"}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/double_render.mustache b/lib/mstch/test/data/double_render.mustache
new file mode 100644
index 0000000..4500fd7
--- /dev/null
+++ b/lib/mstch/test/data/double_render.mustache
@@ -0,0 +1 @@
+{{#foo}}{{bar}}{{/foo}}
diff --git a/lib/mstch/test/data/double_render.txt b/lib/mstch/test/data/double_render.txt
new file mode 100644
index 0000000..b6e652d
--- /dev/null
+++ b/lib/mstch/test/data/double_render.txt
@@ -0,0 +1 @@
+{{win}}
diff --git a/lib/mstch/test/data/empty_list.hpp b/lib/mstch/test/data/empty_list.hpp
new file mode 100644
index 0000000..9a4d893
--- /dev/null
+++ b/lib/mstch/test/data/empty_list.hpp
@@ -0,0 +1,3 @@
+const auto empty_list_data = mstch::map{
+ {"jobs", mstch::array{}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/empty_list.mustache b/lib/mstch/test/data/empty_list.mustache
new file mode 100644
index 0000000..4fdf13d
--- /dev/null
+++ b/lib/mstch/test/data/empty_list.mustache
@@ -0,0 +1,4 @@
+These are the jobs:
+{{#jobs}}
+{{.}}
+{{/jobs}}
diff --git a/lib/mstch/test/data/empty_list.txt b/lib/mstch/test/data/empty_list.txt
new file mode 100644
index 0000000..d9b4a67
--- /dev/null
+++ b/lib/mstch/test/data/empty_list.txt
@@ -0,0 +1 @@
+These are the jobs:
diff --git a/lib/mstch/test/data/empty_sections.hpp b/lib/mstch/test/data/empty_sections.hpp
new file mode 100644
index 0000000..c6caee0
--- /dev/null
+++ b/lib/mstch/test/data/empty_sections.hpp
@@ -0,0 +1 @@
+const auto empty_sections_data = mstch::map{}; \ No newline at end of file
diff --git a/lib/mstch/test/data/empty_sections.mustache b/lib/mstch/test/data/empty_sections.mustache
new file mode 100644
index 0000000..b6065db
--- /dev/null
+++ b/lib/mstch/test/data/empty_sections.mustache
@@ -0,0 +1 @@
+{{#foo}}{{/foo}}foo{{#bar}}{{/bar}}
diff --git a/lib/mstch/test/data/empty_sections.txt b/lib/mstch/test/data/empty_sections.txt
new file mode 100644
index 0000000..257cc56
--- /dev/null
+++ b/lib/mstch/test/data/empty_sections.txt
@@ -0,0 +1 @@
+foo
diff --git a/lib/mstch/test/data/empty_string.hpp b/lib/mstch/test/data/empty_string.hpp
new file mode 100644
index 0000000..32e70bf
--- /dev/null
+++ b/lib/mstch/test/data/empty_string.hpp
@@ -0,0 +1,6 @@
+const auto empty_string_data = mstch::map{
+ {"description", std::string{"That is all!"}},
+ {"child", mstch::map{
+ {"description", std::string{""}}
+ }}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/empty_string.mustache b/lib/mstch/test/data/empty_string.mustache
new file mode 100644
index 0000000..f568441
--- /dev/null
+++ b/lib/mstch/test/data/empty_string.mustache
@@ -0,0 +1 @@
+{{description}}{{#child}}{{description}}{{/child}}
diff --git a/lib/mstch/test/data/empty_string.txt b/lib/mstch/test/data/empty_string.txt
new file mode 100644
index 0000000..22e2a6e
--- /dev/null
+++ b/lib/mstch/test/data/empty_string.txt
@@ -0,0 +1 @@
+That is all!
diff --git a/lib/mstch/test/data/empty_template.hpp b/lib/mstch/test/data/empty_template.hpp
new file mode 100644
index 0000000..f0208af
--- /dev/null
+++ b/lib/mstch/test/data/empty_template.hpp
@@ -0,0 +1 @@
+const auto empty_template_data = mstch::map{}; \ No newline at end of file
diff --git a/lib/mstch/test/data/empty_template.mustache b/lib/mstch/test/data/empty_template.mustache
new file mode 100644
index 0000000..bb2367a
--- /dev/null
+++ b/lib/mstch/test/data/empty_template.mustache
@@ -0,0 +1 @@
+<html><head></head><body><h1>Test</h1></body></html> \ No newline at end of file
diff --git a/lib/mstch/test/data/empty_template.txt b/lib/mstch/test/data/empty_template.txt
new file mode 100644
index 0000000..bb2367a
--- /dev/null
+++ b/lib/mstch/test/data/empty_template.txt
@@ -0,0 +1 @@
+<html><head></head><body><h1>Test</h1></body></html> \ No newline at end of file
diff --git a/lib/mstch/test/data/error_eof_in_section.hpp b/lib/mstch/test/data/error_eof_in_section.hpp
new file mode 100644
index 0000000..2e54aab
--- /dev/null
+++ b/lib/mstch/test/data/error_eof_in_section.hpp
@@ -0,0 +1,3 @@
+const auto error_eof_in_section_data = mstch::map{
+ {"hello", mstch::array{std::string{"a"}, std::string{"b"}}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/error_eof_in_section.mustache b/lib/mstch/test/data/error_eof_in_section.mustache
new file mode 100644
index 0000000..1035f91
--- /dev/null
+++ b/lib/mstch/test/data/error_eof_in_section.mustache
@@ -0,0 +1 @@
+yay{{#hello}}{{.}} \ No newline at end of file
diff --git a/lib/mstch/test/data/error_eof_in_section.txt b/lib/mstch/test/data/error_eof_in_section.txt
new file mode 100644
index 0000000..fc0e7cb
--- /dev/null
+++ b/lib/mstch/test/data/error_eof_in_section.txt
@@ -0,0 +1 @@
+yay \ No newline at end of file
diff --git a/lib/mstch/test/data/error_eof_in_tag.hpp b/lib/mstch/test/data/error_eof_in_tag.hpp
new file mode 100644
index 0000000..6f3fb1d
--- /dev/null
+++ b/lib/mstch/test/data/error_eof_in_tag.hpp
@@ -0,0 +1 @@
+const auto error_eof_in_tag_data = mstch::map{{"hello", std::string{"world"}}}; \ No newline at end of file
diff --git a/lib/mstch/test/data/error_eof_in_tag.mustache b/lib/mstch/test/data/error_eof_in_tag.mustache
new file mode 100644
index 0000000..ba4f670
--- /dev/null
+++ b/lib/mstch/test/data/error_eof_in_tag.mustache
@@ -0,0 +1 @@
+{{hello{{hello}}{{hello \ No newline at end of file
diff --git a/lib/mstch/test/data/error_eof_in_tag.txt b/lib/mstch/test/data/error_eof_in_tag.txt
new file mode 100644
index 0000000..25e277c
--- /dev/null
+++ b/lib/mstch/test/data/error_eof_in_tag.txt
@@ -0,0 +1 @@
+{{hello \ No newline at end of file
diff --git a/lib/mstch/test/data/error_not_found.hpp b/lib/mstch/test/data/error_not_found.hpp
new file mode 100644
index 0000000..c8a5420
--- /dev/null
+++ b/lib/mstch/test/data/error_not_found.hpp
@@ -0,0 +1,3 @@
+const auto error_not_found_data = mstch::map{
+ {"bar", 2}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/error_not_found.mustache b/lib/mstch/test/data/error_not_found.mustache
new file mode 100644
index 0000000..24369f7
--- /dev/null
+++ b/lib/mstch/test/data/error_not_found.mustache
@@ -0,0 +1 @@
+{{foo}} \ No newline at end of file
diff --git a/lib/mstch/test/data/error_not_found.txt b/lib/mstch/test/data/error_not_found.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/mstch/test/data/error_not_found.txt
diff --git a/lib/mstch/test/data/escaped.hpp b/lib/mstch/test/data/escaped.hpp
new file mode 100644
index 0000000..bcb21bb
--- /dev/null
+++ b/lib/mstch/test/data/escaped.hpp
@@ -0,0 +1,4 @@
+const mstch::node escaped_data = mstch::map{
+ {"title", mstch::lambda{[]()->mstch::node{ return std::string{"Bear > Shark"}; }}},
+ {"entities", mstch::lambda{[]()->mstch::node{ return std::string{"&quot; \"'<>/"}; }}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/escaped.mustache b/lib/mstch/test/data/escaped.mustache
new file mode 100644
index 0000000..93e800b
--- /dev/null
+++ b/lib/mstch/test/data/escaped.mustache
@@ -0,0 +1,2 @@
+<h1>{{title}}</h1>
+And even {{entities}}, but not {{{entities}}}.
diff --git a/lib/mstch/test/data/escaped.txt b/lib/mstch/test/data/escaped.txt
new file mode 100644
index 0000000..c1527d5
--- /dev/null
+++ b/lib/mstch/test/data/escaped.txt
@@ -0,0 +1,2 @@
+<h1>Bear &gt; Shark</h1>
+And even &amp;quot; &quot;&#39;&lt;&gt;&#x2F;, but not &quot; "'<>/.
diff --git a/lib/mstch/test/data/falsy.hpp b/lib/mstch/test/data/falsy.hpp
new file mode 100644
index 0000000..736de44
--- /dev/null
+++ b/lib/mstch/test/data/falsy.hpp
@@ -0,0 +1,6 @@
+const auto falsy_data = mstch::map{
+ {"emptyString", std::string{""}},
+ {"emptyArray", mstch::array{}},
+ {"zero", 0},
+ {"null", mstch::node{}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/falsy.mustache b/lib/mstch/test/data/falsy.mustache
new file mode 100644
index 0000000..4d992fd
--- /dev/null
+++ b/lib/mstch/test/data/falsy.mustache
@@ -0,0 +1,8 @@
+{{#emptyString}}empty string{{/emptyString}}
+{{^emptyString}}inverted empty string{{/emptyString}}
+{{#emptyArray}}empty array{{/emptyArray}}
+{{^emptyArray}}inverted empty array{{/emptyArray}}
+{{#zero}}zero{{/zero}}
+{{^zero}}inverted zero{{/zero}}
+{{#null}}null{{/null}}
+{{^null}}inverted null{{/null}}
diff --git a/lib/mstch/test/data/falsy.txt b/lib/mstch/test/data/falsy.txt
new file mode 100644
index 0000000..fde133c
--- /dev/null
+++ b/lib/mstch/test/data/falsy.txt
@@ -0,0 +1,8 @@
+
+inverted empty string
+
+inverted empty array
+
+inverted zero
+
+inverted null
diff --git a/lib/mstch/test/data/falsy_array.hpp b/lib/mstch/test/data/falsy_array.hpp
new file mode 100644
index 0000000..20a6394
--- /dev/null
+++ b/lib/mstch/test/data/falsy_array.hpp
@@ -0,0 +1,8 @@
+const auto falsy_array_data = mstch::map{
+ {"list", mstch::array{
+ mstch::array{std::string{""}, std::string{"emptyString"}},
+ mstch::array{mstch::array{}, std::string{"emptyArray"}},
+ mstch::array{0, std::string{"zero"}},
+ mstch::array{mstch::node{}, std::string{"null"}}}
+ }
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/falsy_array.mustache b/lib/mstch/test/data/falsy_array.mustache
new file mode 100644
index 0000000..2be7b37
--- /dev/null
+++ b/lib/mstch/test/data/falsy_array.mustache
@@ -0,0 +1,3 @@
+{{#list}}
+{{#.}}{{#.}}{{.}}{{/.}}{{^.}}inverted {{/.}}{{/.}}
+{{/list}} \ No newline at end of file
diff --git a/lib/mstch/test/data/falsy_array.txt b/lib/mstch/test/data/falsy_array.txt
new file mode 100644
index 0000000..a001172
--- /dev/null
+++ b/lib/mstch/test/data/falsy_array.txt
@@ -0,0 +1,4 @@
+inverted emptyString
+inverted emptyArray
+inverted zero
+inverted null
diff --git a/lib/mstch/test/data/grandparent_context.hpp b/lib/mstch/test/data/grandparent_context.hpp
new file mode 100644
index 0000000..a291143
--- /dev/null
+++ b/lib/mstch/test/data/grandparent_context.hpp
@@ -0,0 +1,19 @@
+const auto grandparent_context_data = mstch::map{
+ {"grand_parent_id", std::string{"grand_parent1"}},
+ {"parent_contexts", mstch::array{
+ mstch::map{
+ {"parent_id", std::string{"parent1"}},
+ {"child_contexts", mstch::array{
+ mstch::map{{"child_id", std::string{"parent1-child1"}}},
+ mstch::map{{"child_id", std::string{"parent1-child2"}}}
+ }}
+ },
+ mstch::map{
+ {"parent_id", std::string{"parent2"}},
+ {"child_contexts", mstch::array{
+ mstch::map{{"child_id", std::string{"parent2-child1"}}},
+ mstch::map{{"child_id", std::string{"parent2-child2"}}}
+ }}
+ }
+ }}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/grandparent_context.mustache b/lib/mstch/test/data/grandparent_context.mustache
new file mode 100644
index 0000000..e6c07a2
--- /dev/null
+++ b/lib/mstch/test/data/grandparent_context.mustache
@@ -0,0 +1,10 @@
+{{grand_parent_id}}
+{{#parent_contexts}}
+{{grand_parent_id}}
+{{parent_id}}
+{{#child_contexts}}
+{{grand_parent_id}}
+{{parent_id}}
+{{child_id}}
+{{/child_contexts}}
+{{/parent_contexts}}
diff --git a/lib/mstch/test/data/grandparent_context.txt b/lib/mstch/test/data/grandparent_context.txt
new file mode 100644
index 0000000..64996ad
--- /dev/null
+++ b/lib/mstch/test/data/grandparent_context.txt
@@ -0,0 +1,17 @@
+grand_parent1
+grand_parent1
+parent1
+grand_parent1
+parent1
+parent1-child1
+grand_parent1
+parent1
+parent1-child2
+grand_parent1
+parent2
+grand_parent1
+parent2
+parent2-child1
+grand_parent1
+parent2
+parent2-child2
diff --git a/lib/mstch/test/data/higher_order_sections.hpp b/lib/mstch/test/data/higher_order_sections.hpp
new file mode 100644
index 0000000..ef2582c
--- /dev/null
+++ b/lib/mstch/test/data/higher_order_sections.hpp
@@ -0,0 +1,28 @@
+class higher_order_sections: public mstch::object {
+ private:
+ std::string m_helper;
+ public:
+ higher_order_sections(): m_helper("To tinker?") {
+ register_methods(this, std::map<std::string,mstch::node(higher_order_sections::*)()>{
+ {"name", &higher_order_sections::name},
+ {"helper", &higher_order_sections::helper},
+ {"bolder", &higher_order_sections::bolder}
+ });
+ }
+
+ mstch::node name() {
+ return std::string{"Tater"};
+ }
+
+ mstch::node helper() {
+ return m_helper;
+ }
+
+ mstch::node bolder() {
+ return mstch::lambda{[this](const std::string& text) -> mstch::node {
+ return "<b>" + text + "</b> " + m_helper;
+ }};
+ }
+};
+
+const mstch::node higher_order_sections_data = std::make_shared<higher_order_sections>(); \ No newline at end of file
diff --git a/lib/mstch/test/data/higher_order_sections.mustache b/lib/mstch/test/data/higher_order_sections.mustache
new file mode 100644
index 0000000..04f5318
--- /dev/null
+++ b/lib/mstch/test/data/higher_order_sections.mustache
@@ -0,0 +1 @@
+{{#bolder}}Hi {{name}}.{{/bolder}}
diff --git a/lib/mstch/test/data/higher_order_sections.txt b/lib/mstch/test/data/higher_order_sections.txt
new file mode 100644
index 0000000..9db786a
--- /dev/null
+++ b/lib/mstch/test/data/higher_order_sections.txt
@@ -0,0 +1 @@
+<b>Hi Tater.</b> To tinker?
diff --git a/lib/mstch/test/data/implicit_iterator.hpp b/lib/mstch/test/data/implicit_iterator.hpp
new file mode 100644
index 0000000..a610a95
--- /dev/null
+++ b/lib/mstch/test/data/implicit_iterator.hpp
@@ -0,0 +1,8 @@
+const auto implicit_iterator_data = mstch::map{
+ {"data", mstch::map{
+ {"author", mstch::map{
+ {"twitter_id", 819606},
+ {"name", std::string{"janl"}}
+ }}
+ }}
+};
diff --git a/lib/mstch/test/data/implicit_iterator.mustache b/lib/mstch/test/data/implicit_iterator.mustache
new file mode 100644
index 0000000..ae31f34
--- /dev/null
+++ b/lib/mstch/test/data/implicit_iterator.mustache
@@ -0,0 +1,7 @@
+{{# data.author.twitter_id }}
+<meta name="twitter:site:id" content="{{.}}">
+{{/ data.author.twitter_id }}
+
+{{# data.author.name }}
+<meta name="twitter:site" content="{{.}}">
+{{/ data.author.name }}
diff --git a/lib/mstch/test/data/implicit_iterator.txt b/lib/mstch/test/data/implicit_iterator.txt
new file mode 100644
index 0000000..0fccefd
--- /dev/null
+++ b/lib/mstch/test/data/implicit_iterator.txt
@@ -0,0 +1,3 @@
+<meta name="twitter:site:id" content="819606">
+
+<meta name="twitter:site" content="janl">
diff --git a/lib/mstch/test/data/included_tag.hpp b/lib/mstch/test/data/included_tag.hpp
new file mode 100644
index 0000000..d094c3b
--- /dev/null
+++ b/lib/mstch/test/data/included_tag.hpp
@@ -0,0 +1,3 @@
+const auto included_tag_data = mstch::map{
+ {"html", std::string{"I like {{mustache}}"}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/included_tag.mustache b/lib/mstch/test/data/included_tag.mustache
new file mode 100644
index 0000000..70631c2
--- /dev/null
+++ b/lib/mstch/test/data/included_tag.mustache
@@ -0,0 +1 @@
+You said "{{{html}}}" today
diff --git a/lib/mstch/test/data/included_tag.txt b/lib/mstch/test/data/included_tag.txt
new file mode 100644
index 0000000..1af4556
--- /dev/null
+++ b/lib/mstch/test/data/included_tag.txt
@@ -0,0 +1 @@
+You said "I like {{mustache}}" today
diff --git a/lib/mstch/test/data/inverted_section.hpp b/lib/mstch/test/data/inverted_section.hpp
new file mode 100644
index 0000000..38c54c5
--- /dev/null
+++ b/lib/mstch/test/data/inverted_section.hpp
@@ -0,0 +1,3 @@
+const auto inverted_section_data = mstch::map{
+ {"repos", mstch::array{}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/inverted_section.mustache b/lib/mstch/test/data/inverted_section.mustache
new file mode 100644
index 0000000..b0a183b
--- /dev/null
+++ b/lib/mstch/test/data/inverted_section.mustache
@@ -0,0 +1,3 @@
+{{#repos}}<b>{{name}}</b>{{/repos}}
+{{^repos}}No repos :({{/repos}}
+{{^nothin}}Hello!{{/nothin}}
diff --git a/lib/mstch/test/data/inverted_section.txt b/lib/mstch/test/data/inverted_section.txt
new file mode 100644
index 0000000..b421582
--- /dev/null
+++ b/lib/mstch/test/data/inverted_section.txt
@@ -0,0 +1,3 @@
+
+No repos :(
+Hello!
diff --git a/lib/mstch/test/data/keys_with_questionmarks.hpp b/lib/mstch/test/data/keys_with_questionmarks.hpp
new file mode 100644
index 0000000..49661ca
--- /dev/null
+++ b/lib/mstch/test/data/keys_with_questionmarks.hpp
@@ -0,0 +1,5 @@
+const auto keys_with_questionmarks_data = mstch::map{
+ {"person?", mstch::map{
+ {"name", std::string{"Jon"}}
+ }}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/keys_with_questionmarks.mustache b/lib/mstch/test/data/keys_with_questionmarks.mustache
new file mode 100644
index 0000000..417f17f
--- /dev/null
+++ b/lib/mstch/test/data/keys_with_questionmarks.mustache
@@ -0,0 +1,3 @@
+{{#person?}}
+ Hi {{name}}!
+{{/person?}}
diff --git a/lib/mstch/test/data/keys_with_questionmarks.txt b/lib/mstch/test/data/keys_with_questionmarks.txt
new file mode 100644
index 0000000..0f69b94
--- /dev/null
+++ b/lib/mstch/test/data/keys_with_questionmarks.txt
@@ -0,0 +1 @@
+ Hi Jon!
diff --git a/lib/mstch/test/data/multiline_comment.hpp b/lib/mstch/test/data/multiline_comment.hpp
new file mode 100644
index 0000000..8a655f6
--- /dev/null
+++ b/lib/mstch/test/data/multiline_comment.hpp
@@ -0,0 +1 @@
+const auto multiline_comment_data = mstch::map{}; \ No newline at end of file
diff --git a/lib/mstch/test/data/multiline_comment.mustache b/lib/mstch/test/data/multiline_comment.mustache
new file mode 100644
index 0000000..dff0893
--- /dev/null
+++ b/lib/mstch/test/data/multiline_comment.mustache
@@ -0,0 +1,6 @@
+{{!
+
+This is a multi-line comment.
+
+}}
+Hello world!
diff --git a/lib/mstch/test/data/multiline_comment.txt b/lib/mstch/test/data/multiline_comment.txt
new file mode 100644
index 0000000..cd08755
--- /dev/null
+++ b/lib/mstch/test/data/multiline_comment.txt
@@ -0,0 +1 @@
+Hello world!
diff --git a/lib/mstch/test/data/nested_dot.hpp b/lib/mstch/test/data/nested_dot.hpp
new file mode 100644
index 0000000..313202f
--- /dev/null
+++ b/lib/mstch/test/data/nested_dot.hpp
@@ -0,0 +1 @@
+const auto nested_dot_data = mstch::map{{"name", std::string{"Bruno"}}}; \ No newline at end of file
diff --git a/lib/mstch/test/data/nested_dot.mustache b/lib/mstch/test/data/nested_dot.mustache
new file mode 100644
index 0000000..12b0728
--- /dev/null
+++ b/lib/mstch/test/data/nested_dot.mustache
@@ -0,0 +1 @@
+{{#name}}Hello {{.}}{{/name}} \ No newline at end of file
diff --git a/lib/mstch/test/data/nested_dot.txt b/lib/mstch/test/data/nested_dot.txt
new file mode 100644
index 0000000..58df047
--- /dev/null
+++ b/lib/mstch/test/data/nested_dot.txt
@@ -0,0 +1 @@
+Hello Bruno \ No newline at end of file
diff --git a/lib/mstch/test/data/nested_higher_order_sections.hpp b/lib/mstch/test/data/nested_higher_order_sections.hpp
new file mode 100644
index 0000000..26ed72a
--- /dev/null
+++ b/lib/mstch/test/data/nested_higher_order_sections.hpp
@@ -0,0 +1,6 @@
+const mstch::node nested_higher_order_sections_data = mstch::map{
+ {"bold", mstch::lambda{[](const std::string& text) -> mstch::node {
+ return std::string{"<b>"} + text + std::string{"</b>"};
+ }}},
+ {"person", mstch::map{{"name", std::string{"Jonas"}}}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/nested_higher_order_sections.mustache b/lib/mstch/test/data/nested_higher_order_sections.mustache
new file mode 100644
index 0000000..e312fe7
--- /dev/null
+++ b/lib/mstch/test/data/nested_higher_order_sections.mustache
@@ -0,0 +1 @@
+{{#bold}}{{#person}}My name is {{name}}!{{/person}}{{/bold}}
diff --git a/lib/mstch/test/data/nested_higher_order_sections.txt b/lib/mstch/test/data/nested_higher_order_sections.txt
new file mode 100644
index 0000000..0ee6a40
--- /dev/null
+++ b/lib/mstch/test/data/nested_higher_order_sections.txt
@@ -0,0 +1 @@
+<b>My name is Jonas!</b>
diff --git a/lib/mstch/test/data/nested_iterating.hpp b/lib/mstch/test/data/nested_iterating.hpp
new file mode 100644
index 0000000..ac2f478
--- /dev/null
+++ b/lib/mstch/test/data/nested_iterating.hpp
@@ -0,0 +1,8 @@
+const auto nested_iterating_data = mstch::map{
+ {"inner", mstch::array{mstch::map{
+ {"foo", std::string{"foo"}},
+ {"inner", mstch::array{mstch::map{
+ {"bar", std::string{"bar"}}
+ }}}
+ }}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/nested_iterating.mustache b/lib/mstch/test/data/nested_iterating.mustache
new file mode 100644
index 0000000..1a3bb1a
--- /dev/null
+++ b/lib/mstch/test/data/nested_iterating.mustache
@@ -0,0 +1 @@
+{{#inner}}{{foo}}{{#inner}}{{bar}}{{/inner}}{{/inner}}
diff --git a/lib/mstch/test/data/nested_iterating.txt b/lib/mstch/test/data/nested_iterating.txt
new file mode 100644
index 0000000..323fae0
--- /dev/null
+++ b/lib/mstch/test/data/nested_iterating.txt
@@ -0,0 +1 @@
+foobar
diff --git a/lib/mstch/test/data/nesting.hpp b/lib/mstch/test/data/nesting.hpp
new file mode 100644
index 0000000..7008030
--- /dev/null
+++ b/lib/mstch/test/data/nesting.hpp
@@ -0,0 +1,7 @@
+const auto nesting_data = mstch::map{
+ {"foo", mstch::array{
+ mstch::map{{"a", mstch::map{{"b", 1}}}},
+ mstch::map{{"a", mstch::map{{"b", 2}}}},
+ mstch::map{{"a", mstch::map{{"b", 3}}}}
+ }}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/nesting.mustache b/lib/mstch/test/data/nesting.mustache
new file mode 100644
index 0000000..551366d
--- /dev/null
+++ b/lib/mstch/test/data/nesting.mustache
@@ -0,0 +1,5 @@
+{{#foo}}
+ {{#a}}
+ {{b}}
+ {{/a}}
+{{/foo}}
diff --git a/lib/mstch/test/data/nesting.txt b/lib/mstch/test/data/nesting.txt
new file mode 100644
index 0000000..7db34b1
--- /dev/null
+++ b/lib/mstch/test/data/nesting.txt
@@ -0,0 +1,3 @@
+ 1
+ 2
+ 3
diff --git a/lib/mstch/test/data/nesting_same_name.hpp b/lib/mstch/test/data/nesting_same_name.hpp
new file mode 100644
index 0000000..c696cc9
--- /dev/null
+++ b/lib/mstch/test/data/nesting_same_name.hpp
@@ -0,0 +1,8 @@
+const auto nesting_same_name_data = mstch::map{
+ {"items", mstch::array{
+ mstch::map{
+ {"name", std::string{"name"}},
+ {"items", mstch::array{1, 2, 3, 4}}
+ }
+ }}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/nesting_same_name.mustache b/lib/mstch/test/data/nesting_same_name.mustache
new file mode 100644
index 0000000..777dbd6
--- /dev/null
+++ b/lib/mstch/test/data/nesting_same_name.mustache
@@ -0,0 +1 @@
+{{#items}}{{name}}{{#items}}{{.}}{{/items}}{{/items}}
diff --git a/lib/mstch/test/data/nesting_same_name.txt b/lib/mstch/test/data/nesting_same_name.txt
new file mode 100644
index 0000000..34fcfd3
--- /dev/null
+++ b/lib/mstch/test/data/nesting_same_name.txt
@@ -0,0 +1 @@
+name1234
diff --git a/lib/mstch/test/data/null_lookup_array.hpp b/lib/mstch/test/data/null_lookup_array.hpp
new file mode 100644
index 0000000..ea6c961
--- /dev/null
+++ b/lib/mstch/test/data/null_lookup_array.hpp
@@ -0,0 +1,8 @@
+const auto null_lookup_array_data = mstch::map{
+ {"name", std::string{"David"}},
+ {"twitter", std::string{"@dasilvacontin"}},
+ {"farray", mstch::array{
+ mstch::array{std::string{"Flor"}, std::string{"@florrts"}},
+ mstch::array{std::string{"Miquel"}, mstch::node{}},
+ }}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/null_lookup_array.mustache b/lib/mstch/test/data/null_lookup_array.mustache
new file mode 100644
index 0000000..0543895
--- /dev/null
+++ b/lib/mstch/test/data/null_lookup_array.mustache
@@ -0,0 +1,3 @@
+{{#farray}}
+{{#.}}{{#.}}{{.}} {{/.}}{{^.}}no twitter{{/.}}{{/.}}
+{{/farray}}
diff --git a/lib/mstch/test/data/null_lookup_array.txt b/lib/mstch/test/data/null_lookup_array.txt
new file mode 100644
index 0000000..d4f4dc5
--- /dev/null
+++ b/lib/mstch/test/data/null_lookup_array.txt
@@ -0,0 +1,2 @@
+Flor @florrts
+Miquel no twitter
diff --git a/lib/mstch/test/data/null_lookup_object.hpp b/lib/mstch/test/data/null_lookup_object.hpp
new file mode 100644
index 0000000..cbee5f9
--- /dev/null
+++ b/lib/mstch/test/data/null_lookup_object.hpp
@@ -0,0 +1,14 @@
+const auto null_lookup_object_data = mstch::map{
+ {"name", std::string{"David"}},
+ {"twitter", std::string{"@dasilvacontin"}},
+ {"fobject", mstch::array{
+ mstch::map{
+ {"name", std::string{"Flor"}},
+ {"twitter", std::string{"@florrts"}}
+ },
+ mstch::map{
+ {"name", std::string{"Miquel"}},
+ {"twitter", mstch::node{}}
+ }
+ }}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/null_lookup_object.mustache b/lib/mstch/test/data/null_lookup_object.mustache
new file mode 100644
index 0000000..e709ae4
--- /dev/null
+++ b/lib/mstch/test/data/null_lookup_object.mustache
@@ -0,0 +1,3 @@
+{{#fobject}}
+{{name}}'s twitter: {{#twitter}}{{.}}{{/twitter}}{{^twitter}}unknown{{/twitter}}.
+{{/fobject}}
diff --git a/lib/mstch/test/data/null_lookup_object.txt b/lib/mstch/test/data/null_lookup_object.txt
new file mode 100644
index 0000000..d1291ee
--- /dev/null
+++ b/lib/mstch/test/data/null_lookup_object.txt
@@ -0,0 +1,2 @@
+Flor's twitter: @florrts.
+Miquel's twitter: unknown.
diff --git a/lib/mstch/test/data/null_string.hpp b/lib/mstch/test/data/null_string.hpp
new file mode 100644
index 0000000..619a6f9
--- /dev/null
+++ b/lib/mstch/test/data/null_string.hpp
@@ -0,0 +1,6 @@
+const auto null_string_data = mstch::map{
+ {"name", std::string{"Elise"}},
+ {"glytch", true},
+ {"binary", false},
+ {"value", mstch::node{}}
+};
diff --git a/lib/mstch/test/data/null_string.mustache b/lib/mstch/test/data/null_string.mustache
new file mode 100644
index 0000000..d087a39
--- /dev/null
+++ b/lib/mstch/test/data/null_string.mustache
@@ -0,0 +1,4 @@
+Hello {{name}}
+glytch {{glytch}}
+binary {{binary}}
+value {{value}}
diff --git a/lib/mstch/test/data/null_string.txt b/lib/mstch/test/data/null_string.txt
new file mode 100644
index 0000000..8a0428b
--- /dev/null
+++ b/lib/mstch/test/data/null_string.txt
@@ -0,0 +1,4 @@
+Hello Elise
+glytch true
+binary false
+value
diff --git a/lib/mstch/test/data/null_view.hpp b/lib/mstch/test/data/null_view.hpp
new file mode 100644
index 0000000..a3ad5ea
--- /dev/null
+++ b/lib/mstch/test/data/null_view.hpp
@@ -0,0 +1,4 @@
+const auto null_view_data = mstch::map{
+ {"name", std::string{"Joe"}},
+ {"friends", mstch::node{}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/null_view.mustache b/lib/mstch/test/data/null_view.mustache
new file mode 100644
index 0000000..115b376
--- /dev/null
+++ b/lib/mstch/test/data/null_view.mustache
@@ -0,0 +1 @@
+{{name}}'s friends: {{#friends}}{{name}}, {{/friends}} \ No newline at end of file
diff --git a/lib/mstch/test/data/null_view.txt b/lib/mstch/test/data/null_view.txt
new file mode 100644
index 0000000..15ed2ab
--- /dev/null
+++ b/lib/mstch/test/data/null_view.txt
@@ -0,0 +1 @@
+Joe's friends: \ No newline at end of file
diff --git a/lib/mstch/test/data/partial_array.hpp b/lib/mstch/test/data/partial_array.hpp
new file mode 100644
index 0000000..a03b0e9
--- /dev/null
+++ b/lib/mstch/test/data/partial_array.hpp
@@ -0,0 +1,3 @@
+const auto partial_array_data = mstch::map{
+ {"array", mstch::array{std::string{"1"}, std::string{"2"}, std::string{"3"}, std::string{"4"}}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/partial_array.mustache b/lib/mstch/test/data/partial_array.mustache
new file mode 100644
index 0000000..7a336fe
--- /dev/null
+++ b/lib/mstch/test/data/partial_array.mustache
@@ -0,0 +1 @@
+{{>partial}} \ No newline at end of file
diff --git a/lib/mstch/test/data/partial_array.partial b/lib/mstch/test/data/partial_array.partial
new file mode 100644
index 0000000..0ba652c
--- /dev/null
+++ b/lib/mstch/test/data/partial_array.partial
@@ -0,0 +1,4 @@
+Here's a non-sense array of values
+{{#array}}
+ {{.}}
+{{/array}}
diff --git a/lib/mstch/test/data/partial_array.txt b/lib/mstch/test/data/partial_array.txt
new file mode 100644
index 0000000..892837c
--- /dev/null
+++ b/lib/mstch/test/data/partial_array.txt
@@ -0,0 +1,5 @@
+Here's a non-sense array of values
+ 1
+ 2
+ 3
+ 4
diff --git a/lib/mstch/test/data/partial_array_of_partials.hpp b/lib/mstch/test/data/partial_array_of_partials.hpp
new file mode 100644
index 0000000..2b00c5a
--- /dev/null
+++ b/lib/mstch/test/data/partial_array_of_partials.hpp
@@ -0,0 +1,8 @@
+const auto partial_array_of_partials_data = mstch::map{
+ {"numbers", mstch::array{
+ mstch::map{{"i", std::string{"1"}}},
+ mstch::map{{"i", std::string{"2"}}},
+ mstch::map{{"i", std::string{"3"}}},
+ mstch::map{{"i", std::string{"4"}}}
+ }}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/partial_array_of_partials.mustache b/lib/mstch/test/data/partial_array_of_partials.mustache
new file mode 100644
index 0000000..1af6d68
--- /dev/null
+++ b/lib/mstch/test/data/partial_array_of_partials.mustache
@@ -0,0 +1,4 @@
+Here is some stuff!
+{{#numbers}}
+{{>partial}}
+{{/numbers}}
diff --git a/lib/mstch/test/data/partial_array_of_partials.partial b/lib/mstch/test/data/partial_array_of_partials.partial
new file mode 100644
index 0000000..bdde77d
--- /dev/null
+++ b/lib/mstch/test/data/partial_array_of_partials.partial
@@ -0,0 +1 @@
+{{i}}
diff --git a/lib/mstch/test/data/partial_array_of_partials.txt b/lib/mstch/test/data/partial_array_of_partials.txt
new file mode 100644
index 0000000..f622375
--- /dev/null
+++ b/lib/mstch/test/data/partial_array_of_partials.txt
@@ -0,0 +1,5 @@
+Here is some stuff!
+1
+2
+3
+4
diff --git a/lib/mstch/test/data/partial_array_of_partials_implicit.hpp b/lib/mstch/test/data/partial_array_of_partials_implicit.hpp
new file mode 100644
index 0000000..28e3a3c
--- /dev/null
+++ b/lib/mstch/test/data/partial_array_of_partials_implicit.hpp
@@ -0,0 +1,3 @@
+const auto partial_array_of_partials_implicit_data = mstch::map{
+ {"numbers", mstch::array{std::string{"1"}, std::string{"2"}, std::string{"3"}, std::string{"4"}}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/partial_array_of_partials_implicit.mustache b/lib/mstch/test/data/partial_array_of_partials_implicit.mustache
new file mode 100644
index 0000000..1af6d68
--- /dev/null
+++ b/lib/mstch/test/data/partial_array_of_partials_implicit.mustache
@@ -0,0 +1,4 @@
+Here is some stuff!
+{{#numbers}}
+{{>partial}}
+{{/numbers}}
diff --git a/lib/mstch/test/data/partial_array_of_partials_implicit.partial b/lib/mstch/test/data/partial_array_of_partials_implicit.partial
new file mode 100644
index 0000000..12f7159
--- /dev/null
+++ b/lib/mstch/test/data/partial_array_of_partials_implicit.partial
@@ -0,0 +1 @@
+{{.}}
diff --git a/lib/mstch/test/data/partial_array_of_partials_implicit.txt b/lib/mstch/test/data/partial_array_of_partials_implicit.txt
new file mode 100644
index 0000000..f622375
--- /dev/null
+++ b/lib/mstch/test/data/partial_array_of_partials_implicit.txt
@@ -0,0 +1,5 @@
+Here is some stuff!
+1
+2
+3
+4
diff --git a/lib/mstch/test/data/partial_empty.hpp b/lib/mstch/test/data/partial_empty.hpp
new file mode 100644
index 0000000..276c972
--- /dev/null
+++ b/lib/mstch/test/data/partial_empty.hpp
@@ -0,0 +1,3 @@
+const auto partial_empty_data = mstch::map{
+ {"foo", 1}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/partial_empty.mustache b/lib/mstch/test/data/partial_empty.mustache
new file mode 100644
index 0000000..a710047
--- /dev/null
+++ b/lib/mstch/test/data/partial_empty.mustache
@@ -0,0 +1,2 @@
+hey {{foo}}
+{{>partial}}
diff --git a/lib/mstch/test/data/partial_empty.partial b/lib/mstch/test/data/partial_empty.partial
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/mstch/test/data/partial_empty.partial
diff --git a/lib/mstch/test/data/partial_empty.txt b/lib/mstch/test/data/partial_empty.txt
new file mode 100644
index 0000000..1a67907
--- /dev/null
+++ b/lib/mstch/test/data/partial_empty.txt
@@ -0,0 +1 @@
+hey 1
diff --git a/lib/mstch/test/data/partial_template.hpp b/lib/mstch/test/data/partial_template.hpp
new file mode 100644
index 0000000..88c659b
--- /dev/null
+++ b/lib/mstch/test/data/partial_template.hpp
@@ -0,0 +1,4 @@
+const mstch::node partial_template_data = mstch::map{
+ {"title", mstch::lambda{[]()->mstch::node{ return std::string{"Welcome"}; }}},
+ {"again", mstch::lambda{[]()->mstch::node{ return std::string{"Goodbye"}; }}},
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/partial_template.mustache b/lib/mstch/test/data/partial_template.mustache
new file mode 100644
index 0000000..6a7492e
--- /dev/null
+++ b/lib/mstch/test/data/partial_template.mustache
@@ -0,0 +1,2 @@
+<h1>{{title}}</h1>
+{{>partial}}
diff --git a/lib/mstch/test/data/partial_template.partial b/lib/mstch/test/data/partial_template.partial
new file mode 100644
index 0000000..a404529
--- /dev/null
+++ b/lib/mstch/test/data/partial_template.partial
@@ -0,0 +1 @@
+Again, {{again}}!
diff --git a/lib/mstch/test/data/partial_template.txt b/lib/mstch/test/data/partial_template.txt
new file mode 100644
index 0000000..692698f
--- /dev/null
+++ b/lib/mstch/test/data/partial_template.txt
@@ -0,0 +1,2 @@
+<h1>Welcome</h1>
+Again, Goodbye!
diff --git a/lib/mstch/test/data/partial_view.hpp b/lib/mstch/test/data/partial_view.hpp
new file mode 100644
index 0000000..5c2c3eb
--- /dev/null
+++ b/lib/mstch/test/data/partial_view.hpp
@@ -0,0 +1,42 @@
+class partial_view: public mstch::object {
+private:
+ int m_value;
+
+public:
+ partial_view(): m_value(10000) {
+ register_methods(this, std::map<std::string,mstch::node(partial_view::*)()>{
+ {"greeting", &partial_view::greeting},
+ {"farewell", &partial_view::farewell},
+ {"name", &partial_view::name},
+ {"value", &partial_view::value},
+ {"taxed_value", &partial_view::taxed_value},
+ {"in_ca", &partial_view::in_ca}
+ });
+ }
+
+ mstch::node greeting() {
+ return std::string{"Welcome"};
+ }
+
+ mstch::node farewell() {
+ return std::string{"Fair enough, right?"};
+ }
+
+ mstch::node name() {
+ return std::string{"Chris"};
+ }
+
+ mstch::node value() {
+ return m_value;
+ }
+
+ mstch::node taxed_value() {
+ return m_value - (m_value * 0.4);
+ }
+
+ mstch::node in_ca() {
+ return true;
+ }
+};
+
+const auto partial_view_data = std::make_shared<partial_view>(); \ No newline at end of file
diff --git a/lib/mstch/test/data/partial_view.mustache b/lib/mstch/test/data/partial_view.mustache
new file mode 100644
index 0000000..f8f6a5b
--- /dev/null
+++ b/lib/mstch/test/data/partial_view.mustache
@@ -0,0 +1,3 @@
+<h1>{{greeting}}</h1>
+{{>partial}}
+<h3>{{farewell}}</h3>
diff --git a/lib/mstch/test/data/partial_view.partial b/lib/mstch/test/data/partial_view.partial
new file mode 100644
index 0000000..03df206
--- /dev/null
+++ b/lib/mstch/test/data/partial_view.partial
@@ -0,0 +1,5 @@
+Hello {{name}}
+You have just won ${{value}}!
+{{#in_ca}}
+Well, ${{ taxed_value }}, after taxes.
+{{/in_ca}} \ No newline at end of file
diff --git a/lib/mstch/test/data/partial_view.txt b/lib/mstch/test/data/partial_view.txt
new file mode 100644
index 0000000..c09147c
--- /dev/null
+++ b/lib/mstch/test/data/partial_view.txt
@@ -0,0 +1,5 @@
+<h1>Welcome</h1>
+Hello Chris
+You have just won $10000!
+Well, $6000, after taxes.
+<h3>Fair enough, right?</h3>
diff --git a/lib/mstch/test/data/partial_whitespace.hpp b/lib/mstch/test/data/partial_whitespace.hpp
new file mode 100644
index 0000000..85fa5dc
--- /dev/null
+++ b/lib/mstch/test/data/partial_whitespace.hpp
@@ -0,0 +1,41 @@
+class partial_whitespace: public mstch::object {
+private:
+ int m_value;
+public:
+ partial_whitespace(): m_value(10000) {
+ register_methods(this, std::map<std::string,mstch::node(partial_whitespace::*)()>{
+ {"greeting", &partial_whitespace::greeting},
+ {"farewell", &partial_whitespace::farewell},
+ {"name", &partial_whitespace::name},
+ {"value", &partial_whitespace::value},
+ {"taxed_value", &partial_whitespace::taxed_value},
+ {"in_ca", &partial_whitespace::in_ca}
+ });
+ }
+
+ mstch::node greeting() {
+ return std::string{"Welcome"};
+ }
+
+ mstch::node farewell() {
+ return std::string{"Fair enough, right?"};
+ }
+
+ mstch::node name() {
+ return std::string{"Chris"};
+ }
+
+ mstch::node value() {
+ return m_value;
+ }
+
+ mstch::node taxed_value() {
+ return static_cast<int>(m_value - (m_value * 0.4));
+ }
+
+ mstch::node in_ca() {
+ return true;
+ }
+};
+
+const auto partial_whitespace_data = std::make_shared<partial_whitespace>(); \ No newline at end of file
diff --git a/lib/mstch/test/data/partial_whitespace.mustache b/lib/mstch/test/data/partial_whitespace.mustache
new file mode 100644
index 0000000..48bd1ff
--- /dev/null
+++ b/lib/mstch/test/data/partial_whitespace.mustache
@@ -0,0 +1,3 @@
+<h1>{{ greeting }}</h1>
+{{> partial }}
+<h3>{{ farewell }}</h3>
diff --git a/lib/mstch/test/data/partial_whitespace.partial b/lib/mstch/test/data/partial_whitespace.partial
new file mode 100644
index 0000000..30de8f6
--- /dev/null
+++ b/lib/mstch/test/data/partial_whitespace.partial
@@ -0,0 +1,5 @@
+Hello {{ name}}
+You have just won ${{value }}!
+{{# in_ca }}
+Well, ${{ taxed_value }}, after taxes.
+{{/ in_ca }} \ No newline at end of file
diff --git a/lib/mstch/test/data/partial_whitespace.txt b/lib/mstch/test/data/partial_whitespace.txt
new file mode 100644
index 0000000..c09147c
--- /dev/null
+++ b/lib/mstch/test/data/partial_whitespace.txt
@@ -0,0 +1,5 @@
+<h1>Welcome</h1>
+Hello Chris
+You have just won $10000!
+Well, $6000, after taxes.
+<h3>Fair enough, right?</h3>
diff --git a/lib/mstch/test/data/recursion_with_same_names.hpp b/lib/mstch/test/data/recursion_with_same_names.hpp
new file mode 100644
index 0000000..a9170db
--- /dev/null
+++ b/lib/mstch/test/data/recursion_with_same_names.hpp
@@ -0,0 +1,8 @@
+const auto recursion_with_same_names_data = mstch::map{
+ {"name", std::string{"name"}},
+ {"description", std::string{"desc"}},
+ {"terms", mstch::array{
+ mstch::map{{"name", std::string{"t1"}}, {"index", 0}},
+ mstch::map{{"name", std::string{"t2"}}, {"index", 1}}
+ }}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/recursion_with_same_names.mustache b/lib/mstch/test/data/recursion_with_same_names.mustache
new file mode 100644
index 0000000..c331d04
--- /dev/null
+++ b/lib/mstch/test/data/recursion_with_same_names.mustache
@@ -0,0 +1,7 @@
+{{ name }}
+{{ description }}
+
+{{#terms}}
+ {{name}}
+ {{index}}
+{{/terms}}
diff --git a/lib/mstch/test/data/recursion_with_same_names.txt b/lib/mstch/test/data/recursion_with_same_names.txt
new file mode 100644
index 0000000..cb15d75
--- /dev/null
+++ b/lib/mstch/test/data/recursion_with_same_names.txt
@@ -0,0 +1,7 @@
+name
+desc
+
+ t1
+ 0
+ t2
+ 1
diff --git a/lib/mstch/test/data/reuse_of_enumerables.hpp b/lib/mstch/test/data/reuse_of_enumerables.hpp
new file mode 100644
index 0000000..88c1c8e
--- /dev/null
+++ b/lib/mstch/test/data/reuse_of_enumerables.hpp
@@ -0,0 +1,6 @@
+const auto reuse_of_enumerables_data = mstch::map{
+ {"terms", mstch::array{
+ mstch::map{{"name", std::string{"t1"}}, {"index", 0}},
+ mstch::map{{"name", std::string{"t2"}}, {"index", 1}}
+ }}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/reuse_of_enumerables.mustache b/lib/mstch/test/data/reuse_of_enumerables.mustache
new file mode 100644
index 0000000..cc0cb7a
--- /dev/null
+++ b/lib/mstch/test/data/reuse_of_enumerables.mustache
@@ -0,0 +1,8 @@
+{{#terms}}
+ {{name}}
+ {{index}}
+{{/terms}}
+{{#terms}}
+ {{name}}
+ {{index}}
+{{/terms}}
diff --git a/lib/mstch/test/data/reuse_of_enumerables.txt b/lib/mstch/test/data/reuse_of_enumerables.txt
new file mode 100644
index 0000000..6d05d96
--- /dev/null
+++ b/lib/mstch/test/data/reuse_of_enumerables.txt
@@ -0,0 +1,8 @@
+ t1
+ 0
+ t2
+ 1
+ t1
+ 0
+ t2
+ 1
diff --git a/lib/mstch/test/data/section_as_context.hpp b/lib/mstch/test/data/section_as_context.hpp
new file mode 100644
index 0000000..fc5333a
--- /dev/null
+++ b/lib/mstch/test/data/section_as_context.hpp
@@ -0,0 +1,10 @@
+const auto section_as_context_data = mstch::map{
+ {"a_object", mstch::map{
+ {"title", std::string{"this is an object"}},
+ {"description", std::string{"one of its attributes is a list"}},
+ {"a_list", mstch::array{
+ mstch::map{{"label", std::string{"listitem1"}}},
+ mstch::map{{"label", std::string{"listitem2"}}}
+ }}
+ }}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/section_as_context.mustache b/lib/mstch/test/data/section_as_context.mustache
new file mode 100644
index 0000000..59990f6
--- /dev/null
+++ b/lib/mstch/test/data/section_as_context.mustache
@@ -0,0 +1,9 @@
+{{#a_object}}
+ <h1>{{title}}</h1>
+ <p>{{description}}</p>
+ <ul>
+ {{#a_list}}
+ <li>{{label}}</li>
+ {{/a_list}}
+ </ul>
+{{/a_object}}
diff --git a/lib/mstch/test/data/section_as_context.txt b/lib/mstch/test/data/section_as_context.txt
new file mode 100644
index 0000000..d834e80
--- /dev/null
+++ b/lib/mstch/test/data/section_as_context.txt
@@ -0,0 +1,6 @@
+ <h1>this is an object</h1>
+ <p>one of its attributes is a list</p>
+ <ul>
+ <li>listitem1</li>
+ <li>listitem2</li>
+ </ul>
diff --git a/lib/mstch/test/data/section_functions_in_partials.hpp b/lib/mstch/test/data/section_functions_in_partials.hpp
new file mode 100644
index 0000000..1fc4434
--- /dev/null
+++ b/lib/mstch/test/data/section_functions_in_partials.hpp
@@ -0,0 +1,5 @@
+const mstch::node section_functions_in_partials_data = mstch::map{
+ {"bold", mstch::lambda{[](const std::string& text) -> mstch::node {
+ return std::string{"<b>"} + text + std::string{"</b>"};
+ }}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/section_functions_in_partials.mustache b/lib/mstch/test/data/section_functions_in_partials.mustache
new file mode 100644
index 0000000..8164932
--- /dev/null
+++ b/lib/mstch/test/data/section_functions_in_partials.mustache
@@ -0,0 +1,3 @@
+{{> partial}}
+
+<p>some more text</p>
diff --git a/lib/mstch/test/data/section_functions_in_partials.partial b/lib/mstch/test/data/section_functions_in_partials.partial
new file mode 100644
index 0000000..3e90b00
--- /dev/null
+++ b/lib/mstch/test/data/section_functions_in_partials.partial
@@ -0,0 +1 @@
+{{#bold}}Hello There{{/bold}}
diff --git a/lib/mstch/test/data/section_functions_in_partials.txt b/lib/mstch/test/data/section_functions_in_partials.txt
new file mode 100644
index 0000000..2f5955c
--- /dev/null
+++ b/lib/mstch/test/data/section_functions_in_partials.txt
@@ -0,0 +1,3 @@
+<b>Hello There</b>
+
+<p>some more text</p>
diff --git a/lib/mstch/test/data/simple.hpp b/lib/mstch/test/data/simple.hpp
new file mode 100644
index 0000000..254a6d3
--- /dev/null
+++ b/lib/mstch/test/data/simple.hpp
@@ -0,0 +1,32 @@
+class simple: public mstch::object {
+private:
+ int m_value;
+public:
+ simple():
+ m_value(10000)
+ {
+ register_methods(this, std::map<std::string,mstch::node(simple::*)()>{
+ {"name", &simple::name},
+ {"value", &simple::value},
+ {"taxed_value", &simple::taxed_value},
+ {"in_ca", &simple::in_ca}});
+ }
+
+ mstch::node name() {
+ return std::string{"Chris"};
+ }
+
+ mstch::node value() {
+ return m_value;
+ }
+
+ mstch::node taxed_value() {
+ return m_value - (m_value * 0.4);
+ }
+
+ mstch::node in_ca() {
+ return true;
+ }
+};
+
+const auto simple_data = std::make_shared<simple>(); \ No newline at end of file
diff --git a/lib/mstch/test/data/simple.mustache b/lib/mstch/test/data/simple.mustache
new file mode 100644
index 0000000..2fea632
--- /dev/null
+++ b/lib/mstch/test/data/simple.mustache
@@ -0,0 +1,5 @@
+Hello {{name}}
+You have just won ${{value}}!
+{{#in_ca}}
+Well, ${{ taxed_value }}, after taxes.
+{{/in_ca}}
diff --git a/lib/mstch/test/data/simple.txt b/lib/mstch/test/data/simple.txt
new file mode 100644
index 0000000..5d75d65
--- /dev/null
+++ b/lib/mstch/test/data/simple.txt
@@ -0,0 +1,3 @@
+Hello Chris
+You have just won $10000!
+Well, $6000, after taxes.
diff --git a/lib/mstch/test/data/string_as_context.hpp b/lib/mstch/test/data/string_as_context.hpp
new file mode 100644
index 0000000..56ee3a1
--- /dev/null
+++ b/lib/mstch/test/data/string_as_context.hpp
@@ -0,0 +1,4 @@
+const auto string_as_context_data = mstch::map{
+ {"a_string", std::string{"aa"}},
+ {"a_list", mstch::array{std::string{"a"},std::string{"b"},std::string{"c"}}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/string_as_context.mustache b/lib/mstch/test/data/string_as_context.mustache
new file mode 100644
index 0000000..00f7181
--- /dev/null
+++ b/lib/mstch/test/data/string_as_context.mustache
@@ -0,0 +1,5 @@
+<ul>
+{{#a_list}}
+ <li>{{a_string}}/{{.}}</li>
+{{/a_list}}
+</ul> \ No newline at end of file
diff --git a/lib/mstch/test/data/string_as_context.txt b/lib/mstch/test/data/string_as_context.txt
new file mode 100644
index 0000000..8bd87ff
--- /dev/null
+++ b/lib/mstch/test/data/string_as_context.txt
@@ -0,0 +1,5 @@
+<ul>
+ <li>aa/a</li>
+ <li>aa/b</li>
+ <li>aa/c</li>
+</ul> \ No newline at end of file
diff --git a/lib/mstch/test/data/two_in_a_row.hpp b/lib/mstch/test/data/two_in_a_row.hpp
new file mode 100644
index 0000000..496ef8f
--- /dev/null
+++ b/lib/mstch/test/data/two_in_a_row.hpp
@@ -0,0 +1,4 @@
+const auto two_in_a_row_data = mstch::map{
+ {"name", std::string{"Joe"}},
+ {"greeting", std::string{"Welcome"}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/two_in_a_row.mustache b/lib/mstch/test/data/two_in_a_row.mustache
new file mode 100644
index 0000000..b23f29e
--- /dev/null
+++ b/lib/mstch/test/data/two_in_a_row.mustache
@@ -0,0 +1 @@
+{{greeting}}, {{name}}!
diff --git a/lib/mstch/test/data/two_in_a_row.txt b/lib/mstch/test/data/two_in_a_row.txt
new file mode 100644
index 0000000..c6d6a9b
--- /dev/null
+++ b/lib/mstch/test/data/two_in_a_row.txt
@@ -0,0 +1 @@
+Welcome, Joe!
diff --git a/lib/mstch/test/data/two_sections.hpp b/lib/mstch/test/data/two_sections.hpp
new file mode 100644
index 0000000..16f0cc0
--- /dev/null
+++ b/lib/mstch/test/data/two_sections.hpp
@@ -0,0 +1 @@
+const auto two_sections_data = mstch::map{}; \ No newline at end of file
diff --git a/lib/mstch/test/data/two_sections.mustache b/lib/mstch/test/data/two_sections.mustache
new file mode 100644
index 0000000..a4b9f2a
--- /dev/null
+++ b/lib/mstch/test/data/two_sections.mustache
@@ -0,0 +1,4 @@
+{{#foo}}
+{{/foo}}
+{{#bar}}
+{{/bar}}
diff --git a/lib/mstch/test/data/two_sections.txt b/lib/mstch/test/data/two_sections.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/mstch/test/data/two_sections.txt
diff --git a/lib/mstch/test/data/unescaped.hpp b/lib/mstch/test/data/unescaped.hpp
new file mode 100644
index 0000000..66914a9
--- /dev/null
+++ b/lib/mstch/test/data/unescaped.hpp
@@ -0,0 +1,3 @@
+const mstch::node unescaped_data = mstch::map{
+ {"title", mstch::lambda{[]()->mstch::node{ return std::string{"Bear > Shark"}; }}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/unescaped.mustache b/lib/mstch/test/data/unescaped.mustache
new file mode 100644
index 0000000..6b07d7b
--- /dev/null
+++ b/lib/mstch/test/data/unescaped.mustache
@@ -0,0 +1 @@
+<h1>{{{title}}}</h1>
diff --git a/lib/mstch/test/data/unescaped.txt b/lib/mstch/test/data/unescaped.txt
new file mode 100644
index 0000000..089ad79
--- /dev/null
+++ b/lib/mstch/test/data/unescaped.txt
@@ -0,0 +1 @@
+<h1>Bear > Shark</h1>
diff --git a/lib/mstch/test/data/whitespace.hpp b/lib/mstch/test/data/whitespace.hpp
new file mode 100644
index 0000000..9fa087f
--- /dev/null
+++ b/lib/mstch/test/data/whitespace.hpp
@@ -0,0 +1,4 @@
+const auto whitespace_data = mstch::map{
+ {"tag1", std::string{"Hello"}},
+ {"tag2", std::string{"World"}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/data/whitespace.mustache b/lib/mstch/test/data/whitespace.mustache
new file mode 100644
index 0000000..aa76e08
--- /dev/null
+++ b/lib/mstch/test/data/whitespace.mustache
@@ -0,0 +1,4 @@
+{{tag1}}
+
+
+{{tag2}}.
diff --git a/lib/mstch/test/data/whitespace.txt b/lib/mstch/test/data/whitespace.txt
new file mode 100644
index 0000000..851fa74
--- /dev/null
+++ b/lib/mstch/test/data/whitespace.txt
@@ -0,0 +1,4 @@
+Hello
+
+
+World.
diff --git a/lib/mstch/test/data/zero_view.hpp b/lib/mstch/test/data/zero_view.hpp
new file mode 100644
index 0000000..f6006b3
--- /dev/null
+++ b/lib/mstch/test/data/zero_view.hpp
@@ -0,0 +1 @@
+const auto zero_view_data = mstch::map{{"nums", mstch::array{0, 1, 2}}}; \ No newline at end of file
diff --git a/lib/mstch/test/data/zero_view.mustache b/lib/mstch/test/data/zero_view.mustache
new file mode 100644
index 0000000..1cdc190
--- /dev/null
+++ b/lib/mstch/test/data/zero_view.mustache
@@ -0,0 +1 @@
+{{#nums}}{{.}},{{/nums}} \ No newline at end of file
diff --git a/lib/mstch/test/data/zero_view.txt b/lib/mstch/test/data/zero_view.txt
new file mode 100644
index 0000000..2aee585
--- /dev/null
+++ b/lib/mstch/test/data/zero_view.txt
@@ -0,0 +1 @@
+0,1,2, \ No newline at end of file
diff --git a/lib/mstch/test/specs_lambdas.hpp b/lib/mstch/test/specs_lambdas.hpp
new file mode 100644
index 0000000..603c9b6
--- /dev/null
+++ b/lib/mstch/test/specs_lambdas.hpp
@@ -0,0 +1,32 @@
+std::map<std::string,mstch::node> specs_lambdas {
+ {"Interpolation", mstch::lambda{[](const std::string&) -> mstch::node {
+ return std::string{"world"};
+ }}},
+ {"Interpolation - Expansion", mstch::lambda{[](const std::string&) -> mstch::node {
+ return std::string{"{{planet}}"};
+ }}},
+ {"Interpolation - Alternate Delimiters", mstch::lambda{[](const std::string&) -> mstch::node {
+ return std::string{"|planet| => {{planet}}"};
+ }}},
+ {"Interpolation - Multiple Calls", mstch::lambda{[](const std::string&) -> mstch::node {
+ static int calls = 0; return ++calls;
+ }}},
+ {"Escaping", mstch::lambda{[](const std::string&) -> mstch::node {
+ return std::string{">"};
+ }}},
+ {"Section", mstch::lambda{[](const std::string& txt) -> mstch::node {
+ return std::string{(txt == "{{x}}") ? "yes" : "no"};
+ }}},
+ {"Section - Expansion", mstch::lambda{[](const std::string& txt) -> mstch::node {
+ return txt + std::string{"{{planet}}"} + txt;
+ }}},
+ {"Section - Alternate Delimiters", mstch::lambda{[](const std::string& txt) -> mstch::node {
+ return txt + std::string{"{{planet}} => |planet|"} + txt;
+ }}},
+ {"Section - Multiple Calls", mstch::lambda{[](const std::string& txt) -> mstch::node {
+ return "__" + txt + "__";
+ }}},
+ {"Inverted Section", mstch::lambda{[](const std::string&) -> mstch::node {
+ return false;
+ }}}
+}; \ No newline at end of file
diff --git a/lib/mstch/test/test_context.hpp b/lib/mstch/test/test_context.hpp
new file mode 100644
index 0000000..edef5e3
--- /dev/null
+++ b/lib/mstch/test/test_context.hpp
@@ -0,0 +1,58 @@
+#include "data/simple.hpp"
+#include "data/empty_string.hpp"
+#include "data/multiline_comment.hpp"
+#include "data/included_tag.hpp"
+#include "data/string_as_context.hpp"
+#include "data/falsy_array.hpp"
+#include "data/nested_dot.hpp"
+#include "data/escaped.hpp"
+#include "data/partial_view.hpp"
+#include "data/nested_higher_order_sections.hpp"
+#include "data/zero_view.hpp"
+#include "data/disappearing_whitespace.hpp"
+#include "data/ampersand_escape.hpp"
+#include "data/falsy.hpp"
+#include "data/reuse_of_enumerables.hpp"
+#include "data/apostrophe.hpp"
+#include "data/grandparent_context.hpp"
+#include "data/higher_order_sections.hpp"
+#include "data/empty_list.hpp"
+#include "data/two_in_a_row.hpp"
+#include "data/partial_array_of_partials.hpp"
+#include "data/keys_with_questionmarks.hpp"
+#include "data/error_not_found.hpp"
+#include "data/complex.hpp"
+#include "data/double_render.hpp"
+#include "data/null_lookup_object.hpp"
+#include "data/error_eof_in_tag.hpp"
+#include "data/delimiters.hpp"
+#include "data/null_view.hpp"
+#include "data/null_string.hpp"
+#include "data/comments.hpp"
+#include "data/null_lookup_array.hpp"
+#include "data/section_as_context.hpp"
+#include "data/unescaped.hpp"
+#include "data/dot_notation.hpp"
+#include "data/recursion_with_same_names.hpp"
+#include "data/two_sections.hpp"
+#include "data/partial_array_of_partials_implicit.hpp"
+#include "data/changing_delimiters.hpp"
+#include "data/nesting_same_name.hpp"
+#include "data/partial_empty.hpp"
+#include "data/inverted_section.hpp"
+#include "data/nested_iterating.hpp"
+#include "data/partial_template.hpp"
+#include "data/nesting.hpp"
+#include "data/bug_11_eating_whitespace.hpp"
+#include "data/implicit_iterator.hpp"
+#include "data/whitespace.hpp"
+#include "data/array_of_strings.hpp"
+#include "data/empty_sections.hpp"
+#include "data/context_lookup.hpp"
+#include "data/section_functions_in_partials.hpp"
+#include "data/partial_whitespace.hpp"
+#include "data/backslashes.hpp"
+#include "data/error_eof_in_section.hpp"
+#include "data/empty_template.hpp"
+#include "data/partial_array.hpp"
+#include "data/bug_length_property.hpp"
diff --git a/lib/mstch/test/test_main.cpp b/lib/mstch/test/test_main.cpp
new file mode 100644
index 0000000..a52fe3c
--- /dev/null
+++ b/lib/mstch/test/test_main.cpp
@@ -0,0 +1,155 @@
+#define CATCH_CONFIG_MAIN
+
+#include "catch.hpp"
+#include "rapidjson/document.h"
+#include "mstch/mstch.hpp"
+#include "test_context.hpp"
+#include "test_data.hpp"
+#include "specs_data.hpp"
+#include "specs_lambdas.hpp"
+
+using namespace mstchtest;
+
+mstch::node to_value(const rapidjson::Value& val) {
+ if (val.IsString())
+ return std::string{val.GetString()};
+ if (val.IsBool())
+ return val.GetBool();
+ if (val.IsDouble())
+ return val.GetDouble();
+ if (val.IsInt())
+ return val.GetInt();
+ return mstch::node{};
+}
+
+mstch::array to_array(const rapidjson::Value& val);
+
+mstch::map to_object(const rapidjson::Value& val) {
+ mstch::map ret;
+ for (auto i = val.MemberBegin(); i != val.MemberEnd(); ++i) {
+ if (i->value.IsArray())
+ ret.insert(std::make_pair(i->name.GetString(), to_array(i->value)));
+ else if (i->value.IsObject())
+ ret.insert(std::make_pair(i->name.GetString(), to_object(i->value)));
+ else
+ ret.insert(std::make_pair(i->name.GetString(), to_value(i->value)));
+ }
+ return ret;
+}
+
+mstch::array to_array(const rapidjson::Value& val) {
+ mstch::array ret;
+ for (auto i = val.Begin(); i != val.End(); ++i) {
+ if (i->IsArray())
+ ret.push_back(to_array(*i));
+ else if (i->IsObject())
+ ret.push_back(to_object(*i));
+ else
+ ret.push_back(to_value(*i));
+ }
+ return ret;
+}
+
+mstch::node parse_with_rapidjson(const std::string& str) {
+ rapidjson::Document doc;
+ doc.Parse(str.c_str());
+ return to_object(doc);
+}
+
+#define MSTCH_PARTIAL_TEST(x) TEST_CASE(#x) { \
+ REQUIRE(x ## _txt == mstch::render(x ## _mustache, x ## _data, {{"partial", x ## _partial}})); \
+}
+
+#define MSTCH_TEST(x) TEST_CASE(#x) { \
+ REQUIRE(x ## _txt == mstch::render(x ## _mustache, x ## _data)); \
+}
+
+#define SPECS_TEST(x) TEST_CASE("specs_" #x) { \
+ using boost::get; \
+ auto data = parse_with_rapidjson(x ## _json); \
+ for (auto& test_item: get<mstch::array>(get<mstch::map>(data)["tests"])) {\
+ auto test = get<mstch::map>(test_item); \
+ std::map<std::string,std::string> partials; \
+ if (test.count("partials")) \
+ for (auto& partial_item: get<mstch::map>(test["partials"])) \
+ partials.insert(std::make_pair(partial_item.first, get<std::string>(partial_item.second))); \
+ mstch::map context; \
+ for (auto& data_item: get<mstch::map>(test["data"])) \
+ if (data_item.first == "lambda") \
+ context.insert(std::make_pair(data_item.first, specs_lambdas[get<std::string>(test["name"])])); \
+ else \
+ context.insert(data_item); \
+ SECTION(get<std::string>(test["name"])) \
+ REQUIRE(mstch::render( \
+ get<std::string>(test["template"]), \
+ context, partials) == \
+ get<std::string>(test["expected"])); \
+ } \
+}
+
+MSTCH_TEST(ampersand_escape)
+MSTCH_TEST(apostrophe)
+MSTCH_TEST(array_of_strings)
+MSTCH_TEST(backslashes)
+MSTCH_TEST(bug_11_eating_whitespace)
+MSTCH_TEST(bug_length_property)
+MSTCH_TEST(changing_delimiters)
+MSTCH_TEST(comments)
+MSTCH_TEST(complex)
+MSTCH_TEST(context_lookup)
+MSTCH_TEST(delimiters)
+MSTCH_TEST(disappearing_whitespace)
+MSTCH_TEST(dot_notation)
+MSTCH_TEST(double_render)
+MSTCH_TEST(empty_list)
+MSTCH_TEST(empty_sections)
+MSTCH_TEST(empty_string)
+MSTCH_TEST(empty_template)
+MSTCH_TEST(error_eof_in_section)
+MSTCH_TEST(error_eof_in_tag)
+MSTCH_TEST(error_not_found)
+MSTCH_TEST(escaped)
+MSTCH_TEST(falsy)
+MSTCH_TEST(falsy_array)
+MSTCH_TEST(grandparent_context)
+MSTCH_TEST(higher_order_sections)
+MSTCH_TEST(implicit_iterator)
+MSTCH_TEST(included_tag)
+MSTCH_TEST(inverted_section)
+MSTCH_TEST(keys_with_questionmarks)
+MSTCH_TEST(multiline_comment)
+MSTCH_TEST(nested_dot)
+MSTCH_TEST(nested_higher_order_sections)
+MSTCH_TEST(nested_iterating)
+MSTCH_TEST(nesting)
+MSTCH_TEST(nesting_same_name)
+MSTCH_TEST(null_lookup_array)
+MSTCH_TEST(null_lookup_object)
+MSTCH_TEST(null_string)
+MSTCH_TEST(null_view)
+MSTCH_PARTIAL_TEST(partial_array)
+MSTCH_PARTIAL_TEST(partial_array_of_partials)
+MSTCH_PARTIAL_TEST(partial_array_of_partials_implicit)
+MSTCH_PARTIAL_TEST(partial_empty)
+MSTCH_PARTIAL_TEST(partial_template)
+MSTCH_PARTIAL_TEST(partial_view)
+MSTCH_PARTIAL_TEST(partial_whitespace)
+MSTCH_TEST(recursion_with_same_names)
+MSTCH_TEST(reuse_of_enumerables)
+MSTCH_TEST(section_as_context)
+MSTCH_PARTIAL_TEST(section_functions_in_partials)
+MSTCH_TEST(simple)
+MSTCH_TEST(string_as_context)
+MSTCH_TEST(two_in_a_row)
+MSTCH_TEST(two_sections)
+MSTCH_TEST(unescaped)
+MSTCH_TEST(whitespace)
+MSTCH_TEST(zero_view)
+
+SPECS_TEST(comments)
+SPECS_TEST(delimiters)
+SPECS_TEST(interpolation)
+SPECS_TEST(inverted)
+SPECS_TEST(partials)
+SPECS_TEST(sections)
+SPECS_TEST(lambdas)