aboutsummaryrefslogtreecommitdiff
path: root/lib/mstch/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/mstch/src')
-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
20 files changed, 828 insertions, 0 deletions
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;
+};
+
+}