This commit is contained in:
Daniel Sipka 2015-04-23 12:54:08 +02:00
parent 29980e299c
commit f4dd438fcc
24 changed files with 457 additions and 413 deletions

View File

@ -4,6 +4,6 @@ option (WITH_UNIT_TESTS "enable building unit test executable" OFF)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -O3") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -O3")
add_subdirectory(src) add_subdirectory(src)
if(WITH_UNIT_TESTS) if(WITH_UNIT_TESTS)
enable_testing() enable_testing()
add_subdirectory(test) add_subdirectory(test)
endif() endif()

View File

@ -1,17 +1,17 @@
find_package(Boost 1.54 REQUIRED) find_package(Boost 1.54 REQUIRED)
include_directories( include_directories(
${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}
${Boost_INCLUDE_DIR}) ${Boost_INCLUDE_DIR})
set(SRC set(SRC
state/in_section.cpp state/in_section.cpp
state/outside_section.cpp state/outside_section.cpp
mstch.cpp mstch.cpp
render_context.cpp render_context.cpp
template_type.cpp template_type.cpp
token.cpp token.cpp
utils.cpp) utils.cpp)
add_library(mstch STATIC ${SRC}) add_library(mstch STATIC ${SRC})

View File

@ -7,12 +7,12 @@
using namespace mstch; using namespace mstch;
std::string mstch::render( std::string mstch::render(
const std::string& tmplt, const std::string& tmplt,
const node& root, const node& root,
const std::map<std::string,std::string>& partials) const std::map<std::string,std::string>& partials)
{ {
std::map<std::string,template_type> partial_templts; std::map<std::string, template_type> partial_templts;
for(auto& partial: partials) for (auto& partial: partials)
partial_templts.insert({partial.first, {partial.second}}); partial_templts.insert({partial.first, {partial.second}});
return render_context(root, partial_templts).render(tmplt); return render_context(root, partial_templts).render(tmplt);
} }

View File

@ -5,62 +5,63 @@
#include "visitor/has_token.hpp" #include "visitor/has_token.hpp"
using namespace mstch; using namespace mstch;
using namespace mstch::visitor;
const mstch::node render_context::null_node; const mstch::node render_context::null_node;
render_context::push::push(render_context& context, const mstch::node& node): render_context::push::push(render_context& context, const mstch::node& node):
context(context) context(context)
{ {
context.nodes.emplace_front(node); context.nodes.emplace_front(node);
context.state.push(std::unique_ptr<state::render_state>( context.state.push(std::unique_ptr<state::render_state>(
new state::outside_section)); new state::outside_section));
} }
render_context::push::~push() { render_context::push::~push() {
context.nodes.pop_front(); context.nodes.pop_front();
context.state.pop(); context.state.pop();
} }
std::string render_context::push::render(const template_type& templt) { std::string render_context::push::render(const template_type& templt) {
return context.render(templt); return context.render(templt);
} }
render_context::render_context( render_context::render_context(
const mstch::node& node, const mstch::node& node,
const std::map<std::string,template_type>& partials): const std::map<std::string, template_type>& partials):
partials{partials}, partials{partials},
nodes{node} nodes{node}
{ {
state.push(std::unique_ptr<state::render_state>( state.push(std::unique_ptr<state::render_state>(
new state::outside_section)); new state::outside_section));
} }
const mstch::node& render_context::find_node( const mstch::node& render_context::find_node(
const std::string& token, const std::string& token,
const std::deque<node>& current_nodes) const std::deque<node>& current_nodes)
{ {
if (token != "." && token.find('.') != std::string::npos) if (token != "." && token.find('.') != std::string::npos)
return find_node( return find_node(
token.substr(token.rfind('.') + 1), token.substr(token.rfind('.') + 1),
{find_node(token.substr(0, token.rfind('.')), current_nodes)}); {find_node(token.substr(0, token.rfind('.')), current_nodes)});
else else
for (auto& i: current_nodes) for (auto& node: current_nodes)
if (boost::apply_visitor(visitor::has_token(token), i)) if (visit(has_token(token), node))
return boost::apply_visitor(visitor::get_token(token, i), i); return visit(get_token(token, node), node);
return null_node; return null_node;
} }
const mstch::node& render_context::get_node(const std::string& token) { const mstch::node& render_context::get_node(const std::string& token) {
return find_node(token, nodes); return find_node(token, nodes);
} }
std::string render_context::render(const template_type& templt) { std::string render_context::render(const template_type& templt) {
std::string output; std::string output;
for (auto& token: templt) for (auto& token: templt)
output += state.top()->render(*this, token); output += state.top()->render(*this, token);
return output; return output;
} }
std::string render_context::render_partial(const std::string& partial_name) { std::string render_context::render_partial(const std::string& partial_name) {
return (partials.count(partial_name))?render(partials.at(partial_name)):""; return (partials.count(partial_name))?render(partials.at(partial_name)):"";
} }

View File

@ -10,34 +10,38 @@
#include "template_type.hpp" #include "template_type.hpp"
namespace mstch { namespace mstch {
class render_context {
public: class render_context {
class push { public:
public: class push {
push(render_context& context, const mstch::node& node = {}); public:
~push(); push(render_context& context, const mstch::node& node = {});
std::string render(const template_type& templt); ~push();
private: std::string render(const template_type& templt);
render_context& context; private:
}; render_context& context;
render_context( };
const mstch::node& node,
const std::map<std::string,template_type>& partials); render_context(
const mstch::node& get_node(const std::string& token); const mstch::node& node,
std::string render(const template_type& templt); const std::map<std::string, template_type>& partials);
std::string render_partial(const std::string& partial_name); const mstch::node& get_node(const std::string& token);
template<class T, class... Args> std::string render(const template_type& templt);
void set_state(Args&&... args) { std::string render_partial(const std::string& partial_name);
state.top() = std::unique_ptr<state::render_state>( template<class T, class... Args>
new T(std::forward<Args>(args)...)); void set_state(Args&& ... args) {
} state.top() = std::unique_ptr<state::render_state>(
private: new T(std::forward<Args>(args)...));
static const mstch::node null_node; }
const mstch::node& find_node(
const std::string& token, private:
const std::deque<node>& current_nodes); static const mstch::node null_node;
const std::map<std::string,template_type>& partials; const mstch::node& find_node(
std::deque<mstch::node> nodes; const std::string& token,
std::stack<std::unique_ptr<state::render_state>> state; const std::deque<node>& current_nodes);
}; const std::map<std::string, template_type>& partials;
std::deque<mstch::node> nodes;
std::stack<std::unique_ptr<state::render_state>> state;
};
} }

View File

@ -2,8 +2,10 @@
#include "outside_section.hpp" #include "outside_section.hpp"
#include "visitor/is_node_empty.hpp" #include "visitor/is_node_empty.hpp"
#include "visitor/render_section.hpp" #include "visitor/render_section.hpp"
#include "utils.hpp"
using namespace mstch; using namespace mstch;
using namespace mstch::visitor;
state::in_section::in_section(type type, const std::string& section_name): state::in_section::in_section(type type, const std::string& section_name):
m_type(type), section_name(section_name), skipped_openings(0) m_type(type), section_name(section_name), skipped_openings(0)
@ -11,21 +13,20 @@ state::in_section::in_section(type type, const std::string& section_name):
} }
std::string state::in_section::render(render_context& ctx, const token& token) { std::string state::in_section::render(render_context& ctx, const token& token) {
if(token.token_type() == token::type::section_close) if (token.token_type() == token::type::section_close)
if(token.name() == section_name && skipped_openings == 0) { if (token.name() == section_name && skipped_openings == 0) {
auto& sn = ctx.get_node(section_name); auto& node = ctx.get_node(section_name);
std::string out; std::string out;
if(m_type == type::normal && if (m_type == type::normal && !visit(is_node_empty(), node))
!boost::apply_visitor(visitor::is_node_empty(), sn)) out = visit(render_section(ctx, section), node);
out = boost::apply_visitor(visitor::render_section(ctx, section), sn); else if (m_type == type::inverted && visit(is_node_empty(), node))
else if(m_type == type::inverted &&
boost::apply_visitor(visitor::is_node_empty(), sn))
out = render_context::push(ctx).render(section); out = render_context::push(ctx).render(section);
ctx.set_state<outside_section>(); ctx.set_state<outside_section>();
return out; return out;
} else } else {
skipped_openings--; skipped_openings--;
else if(token.token_type() == token::type::inverted_section_open || }
else if (token.token_type() == token::type::inverted_section_open ||
token.token_type() == token::type::section_open) token.token_type() == token::type::section_open)
skipped_openings++; skipped_openings++;
section << token; section << token;

View File

@ -1,23 +1,28 @@
#pragma once #pragma once
#include "render_state.hpp"
#include <sstream> #include <sstream>
#include <vector> #include <vector>
#include "render_state.hpp"
#include "template_type.hpp" #include "template_type.hpp"
namespace mstch { namespace mstch {
namespace state { namespace state {
class in_section: public render_state {
public: class in_section: public render_state {
enum class type { inverted, normal }; public:
in_section(type type, const std::string& section_name); enum class type {
std::string render( inverted, normal
render_context& context, const token& token) override; };
private: in_section(type type, const std::string &section_name);
const type m_type; std::string render(render_context &context, const token &token) override;
const std::string section_name;
template_type section; private:
int skipped_openings; const type m_type;
}; const std::string section_name;
} template_type section;
int skipped_openings;
};
}
} }

View File

@ -1,33 +1,34 @@
#include "visitor/render_node.hpp"
#include "outside_section.hpp" #include "outside_section.hpp"
#include "visitor/render_node.hpp"
#include "in_section.hpp" #include "in_section.hpp"
#include "render_context.hpp" #include "render_context.hpp"
#include "utils.hpp"
using namespace mstch; using namespace mstch;
using namespace mstch::visitor;
std::string state::outside_section::render( std::string state::outside_section::render(
render_context& ctx, const token& token) render_context& ctx, const token& token)
{ {
switch(token.token_type()) { using flag = render_node::flag;
switch (token.token_type()) {
case token::type::section_open: case token::type::section_open:
ctx.set_state<in_section>(in_section::type::normal, token.name()); ctx.set_state<in_section>(in_section::type::normal, token.name());
break; break;
case token::type::inverted_section_open: case token::type::inverted_section_open:
ctx.set_state<in_section>(in_section::type::inverted, token.name()); ctx.set_state<in_section>(in_section::type::inverted, token.name());
break; break;
case token::type::variable: case token::type::variable:
return boost::apply_visitor( return visit(render_node(flag::escape_html), ctx.get_node(token.name()));
visitor::render_node(visitor::render_node::flag::escape_html),
ctx.get_node(token.name()));
case token::type::unescaped_variable: case token::type::unescaped_variable:
return boost::apply_visitor( return visit(render_node(flag::none), ctx.get_node(token.name()));
visitor::render_node(visitor::render_node::flag::none),
ctx.get_node(token.name()));
case token::type::text: case token::type::text:
return token.raw(); return token.raw();
case token::type::partial: case token::type::partial:
return ctx.render_partial(token.name()); return ctx.render_partial(token.name());
default: break; default:
} break;
return ""; }
return "";
} }

View File

@ -3,11 +3,12 @@
#include "render_state.hpp" #include "render_state.hpp"
namespace mstch { namespace mstch {
namespace state { namespace state {
class outside_section: public render_state {
public: class outside_section: public render_state {
std::string render( public:
render_context& context, const token& token) override; std::string render(render_context& context, const token& token) override;
}; };
}
}
} }

View File

@ -1,15 +1,19 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include "token.hpp" #include "token.hpp"
namespace mstch { namespace mstch {
class render_context;
namespace state { class render_context;
class render_state {
public: namespace state {
virtual std::string render(
render_context& context, const token& token) = 0; class render_state {
}; public:
} virtual std::string render(render_context& context, const token& token) = 0;
};
}
} }

View File

@ -3,82 +3,82 @@
using namespace mstch; using namespace mstch;
template_type::template_type(const std::string& str) { template_type::template_type(const std::string& str) {
tokenize(str); tokenize(str);
strip_whitespace(); strip_whitespace();
} }
void template_type::tokenize(const std::string& t) { void template_type::tokenize(const std::string& t) {
std::string delim_start{"{{"}; std::string delim_start{"{{"};
std::string delim_end{"}}"}; std::string delim_end{"}}"};
std::string::const_iterator tok_end, tok_start = t.begin(); std::string::const_iterator tok_end, tok_start = t.begin();
parse_state pstate = parse_state::start; parse_state pstate = parse_state::start;
unsigned int del_pos = 0; unsigned int del_pos = 0;
for (std::string::const_iterator it = t.begin(); it != t.end(); ++it) { for (std::string::const_iterator it = t.begin(); it != t.end(); ++it) {
if (pstate == parse_state::start) { if (pstate == parse_state::start) {
if (*it == delim_start[0]) { if (*it == delim_start[0]) {
pstate = parse_state::in_del_start; pstate = parse_state::in_del_start;
tok_end = it; tok_end = it;
del_pos = 1; del_pos = 1;
} else if(*it == '\n') { } else if (*it == '\n') {
tokens.push_back({{tok_start, it + 1}}); tokens.push_back({{tok_start, it + 1}});
tok_start = it + 1; tok_start = it + 1;
} }
} else if(pstate == parse_state::in_del_start) { } else if (pstate == parse_state::in_del_start) {
if (*it == delim_start[del_pos] && ++del_pos == delim_start.size()) if (*it == delim_start[del_pos] && ++del_pos == delim_start.size())
pstate = parse_state::in_del; pstate = parse_state::in_del;
else else
pstate = parse_state::start; pstate = parse_state::start;
} else if(pstate == parse_state::in_del) { } else if (pstate == parse_state::in_del) {
if (*it == '{') if (*it == '{')
pstate = parse_state::in_esccontent; pstate = parse_state::in_esccontent;
else if (*it == delim_end[0] && (del_pos = 1)) else if (*it == delim_end[0] && (del_pos = 1))
pstate = parse_state::in_del_end; pstate = parse_state::in_del_end;
else else
pstate = parse_state::in_content; pstate = parse_state::in_content;
} else if(pstate == parse_state::in_esccontent && *it == '}') { } else if (pstate == parse_state::in_esccontent && *it == '}') {
pstate = parse_state::in_content; pstate = parse_state::in_content;
} else if(pstate == parse_state::in_content && *it == delim_end[0]) { } else if (pstate == parse_state::in_content && *it == delim_end[0]) {
pstate = parse_state::in_del_end; pstate = parse_state::in_del_end;
del_pos = 1; del_pos = 1;
} else if(pstate == parse_state::in_del_end) { } else if (pstate == parse_state::in_del_end) {
if (*it == delim_end[del_pos] && ++del_pos == delim_end.size()) { if (*it == delim_end[del_pos] && ++del_pos == delim_end.size()) {
pstate = parse_state::start; pstate = parse_state::start;
tokens.push_back({{tok_start, tok_end}}); tokens.push_back({{tok_start, tok_end}});
tokens.push_back( tokens.push_back(
{{tok_end, it + 1}, {{tok_end, it + 1},
delim_start.size(), delim_start.size(),
delim_end.size()}); delim_end.size()});
tok_start = it + 1; tok_start = it + 1;
} else { } else {
pstate = parse_state::start; pstate = parse_state::start;
} }
}
} }
tokens.push_back({{tok_start, t.end()}}); }
tokens.push_back({{tok_start, t.end()}});
} }
void template_type::strip_whitespace() { void template_type::strip_whitespace() {
auto line_begin = tokens.begin(); auto line_begin = tokens.begin();
bool has_tag = false, non_space = false; bool has_tag = false, non_space = false;
for (auto it = tokens.begin(); it != tokens.end(); ++it) { for (auto it = tokens.begin(); it != tokens.end(); ++it) {
auto type = (*it).token_type(); auto type = (*it).token_type();
if (type != token::type::text && type != token::type::variable && if (type != token::type::text && type != token::type::variable &&
type != token::type::unescaped_variable) type != token::type::unescaped_variable)
has_tag = true; has_tag = true;
else if (!(*it).ws_only()) else if (!(*it).ws_only())
non_space = true; non_space = true;
if ((*it).eol()) { if ((*it).eol()) {
if (has_tag && !non_space) { if (has_tag && !non_space) {
auto line_it = line_begin; auto line_it = line_begin;
for (; !(*line_it).eol(); ++line_it) for (; !(*line_it).eol(); ++line_it)
if ((*line_it).ws_only()) if ((*line_it).ws_only())
line_it = tokens.erase(line_it); line_it = tokens.erase(line_it);
if ((*line_it).ws_only()) if ((*line_it).ws_only())
line_it = tokens.erase(line_it); line_it = tokens.erase(line_it);
it = line_it - 1; it = line_it - 1;
} }
non_space = has_tag = false; non_space = has_tag = false;
line_begin = it + 1; line_begin = it + 1;
}
} }
}
} }

View File

@ -6,19 +6,22 @@
#include "token.hpp" #include "token.hpp"
namespace mstch { namespace mstch {
class template_type {
public: class template_type {
template_type() = default; public:
template_type(const std::string& str); template_type() = default;
std::vector<token>::const_iterator begin() const{return tokens.begin();} template_type(const std::string& str);
std::vector<token>::const_iterator end() const{return tokens.end();} std::vector<token>::const_iterator begin() const { return tokens.begin(); }
void operator<<(const token& token) { tokens.push_back(token); } std::vector<token>::const_iterator end() const { return tokens.end(); }
private: void operator<<(const token& token) { tokens.push_back(token); }
enum class parse_state {
start, in_del_start, in_del, in_content, in_esccontent, in_del_end private:
}; enum class parse_state {
void tokenize(const std::string& str); start, in_del_start, in_del, in_content, in_esccontent, in_del_end
void strip_whitespace(); };
std::vector<token> tokens; void tokenize(const std::string& str);
}; void strip_whitespace();
std::vector<token> tokens;
};
} }

View File

@ -4,7 +4,7 @@
using namespace mstch; using namespace mstch;
token::type token::token_info(char c) { token::type token::token_info(char c) {
switch (c) { switch (c) {
case '>': return type::partial; case '>': return type::partial;
case '^': return type::inverted_section_open; case '^': return type::inverted_section_open;
case '/': return type::section_close; case '/': return type::section_close;
@ -12,27 +12,27 @@ token::type token::token_info(char c) {
case '#': return type::section_open; case '#': return type::section_open;
case '!': return type::comment; case '!': return type::comment;
default: return type::variable; default: return type::variable;
} }
} }
token::token(const std::string& str, std::size_t left, std::size_t right): token::token(const std::string& str, std::size_t left, std::size_t right):
m_raw(str), m_eol(false), m_ws_only(false) m_raw(str), m_eol(false), m_ws_only(false)
{ {
if(left != 0 && right != 0) { if (left != 0 && right != 0) {
if(str[left] == '{' && str[str.size() - right - 1] == '}') { if (str[left] == '{' && str[str.size() - right - 1] == '}') {
m_type = type::unescaped_variable; m_type = type::unescaped_variable;
m_name = {first_not_ws(str.begin() + left + 1, str.end() - right), m_name = {first_not_ws(str.begin() + left + 1, str.end() - right),
first_not_ws(str.rbegin() + 1 + right, str.rend() - left) + 1}; first_not_ws(str.rbegin() + 1 + right, str.rend() - left) + 1};
} else {
auto first = first_not_ws(str.begin() + left, str.end() - right);
m_type = token_info(*first);
if(m_type != type::variable)
first = first_not_ws(first + 1, str.end() - right);
m_name = {first, first_not_ws(str.rbegin() + right, str.rend() - left) + 1};
}
} else { } else {
m_type = type::text; auto c = first_not_ws(str.begin() + left, str.end() - right);
m_eol = (str.size() > 0 && str[str.size() - 1] == '\n'); m_type = token_info(*c);
m_ws_only = (str.find_first_not_of(" \n\t") == std::string::npos); 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};
} }
} else {
m_type = type::text;
m_eol = (str.size() > 0 && str[str.size() - 1] == '\n');
m_ws_only = (str.find_first_not_of(" \n\t") == std::string::npos);
}
} }

View File

@ -3,24 +3,27 @@
#include <string> #include <string>
namespace mstch { namespace mstch {
class token {
public: class token {
enum class type { public:
text, variable, section_open, section_close, inverted_section_open, enum class type {
unescaped_variable, comment, partial text, variable, section_open, section_close, inverted_section_open,
}; unescaped_variable, comment, partial
token(const std::string& str, std::size_t left = 0, std::size_t right = 0); };
type token_type() const { return m_type; }; token(const std::string& str, std::size_t left = 0, std::size_t right = 0);
const std::string& raw() const { return m_raw; }; type token_type() const { return m_type; };
const std::string& name() const { return m_name; }; const std::string& raw() const { return m_raw; };
bool eol() const { return m_eol; } const std::string& name() const { return m_name; };
bool ws_only() const { return m_ws_only; } bool eol() const { return m_eol; }
private: bool ws_only() const { return m_ws_only; }
type m_type;
std::string m_name; private:
std::string m_raw; type m_type;
bool m_eol; std::string m_name;
bool m_ws_only; std::string m_raw;
type token_info(char c); bool m_eol;
}; bool m_ws_only;
type token_info(char c);
};
} }

View File

@ -3,23 +3,23 @@
#include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/replace.hpp>
mstch::citer mstch::first_not_ws(mstch::citer begin, mstch::citer end) { mstch::citer mstch::first_not_ws(mstch::citer begin, mstch::citer end) {
for(auto it = begin; it != end; ++it) for (auto it = begin; it != end; ++it)
if(*it != ' ') return it; if (*it != ' ') return it;
return end; return end;
} }
mstch::citer mstch::first_not_ws(mstch::criter begin, mstch::criter end) { mstch::citer mstch::first_not_ws(mstch::criter begin, mstch::criter end) {
for(auto rit = begin; rit != end; ++rit) for (auto rit = begin; rit != end; ++rit)
if(*rit != ' ') return --(rit.base()); if (*rit != ' ') return --(rit.base());
return --(end.base()); return --(end.base());
} }
std::string mstch::html_escape(std::string str) { std::string mstch::html_escape(std::string str) {
boost::replace_all(str, "&", "&amp;"); boost::replace_all(str, "&", "&amp;");
boost::replace_all(str, "'", "&#39;"); boost::replace_all(str, "'", "&#39;");
boost::replace_all(str, "\"", "&quot;"); boost::replace_all(str, "\"", "&quot;");
boost::replace_all(str, "<", "&lt;"); boost::replace_all(str, "<", "&lt;");
boost::replace_all(str, ">", "&gt;"); boost::replace_all(str, ">", "&gt;");
boost::replace_all(str, "/", "&#x2F;"); boost::replace_all(str, "/", "&#x2F;");
return str; return str;
} }

View File

@ -1,11 +1,22 @@
#pragma once #pragma once
#include <string> #include <string>
#include <boost/variant/apply_visitor.hpp>
namespace mstch { namespace mstch {
using citer = std::string::const_iterator;
using criter = std::string::const_reverse_iterator; using citer = std::string::const_iterator;
citer first_not_ws(citer begin, citer end); using criter = std::string::const_reverse_iterator;
citer first_not_ws(criter begin, criter end);
std::string html_escape(std::string str); citer first_not_ws(citer begin, citer end);
citer first_not_ws(criter begin, criter end);
std::string html_escape(std::string str);
template<class... Args>
auto visit(Args&& ... args) -> decltype(boost::apply_visitor(
std::forward<Args>(args)...))
{
return boost::apply_visitor(std::forward<Args>(args)...);
}
} }

View File

@ -2,6 +2,7 @@
#include <boost/variant/static_visitor.hpp> #include <boost/variant/static_visitor.hpp>
#include <boost/blank.hpp> #include <boost/blank.hpp>
#include "mstch/mstch.hpp" #include "mstch/mstch.hpp"
namespace mstch { namespace mstch {

View File

@ -2,6 +2,7 @@
#include <boost/variant/static_visitor.hpp> #include <boost/variant/static_visitor.hpp>
#include <boost/blank.hpp> #include <boost/blank.hpp>
#include "mstch/mstch.hpp" #include "mstch/mstch.hpp"
namespace mstch { namespace mstch {

View File

@ -5,6 +5,7 @@
#include "render_context.hpp" #include "render_context.hpp"
#include "mstch/mstch.hpp" #include "mstch/mstch.hpp"
#include "utils.hpp"
namespace mstch { namespace mstch {
namespace visitor { namespace visitor {
@ -49,8 +50,7 @@ inline std::string render_section::operator()<array>(const array& array) const {
out += render_context::push(ctx, array).render(section); out += render_context::push(ctx, array).render(section);
else else
for (auto& item: array) for (auto& item: array)
out += boost::apply_visitor( out += visit(render_section(ctx, section, flag::keep_array), item);
render_section(ctx, section, flag::keep_array), item);
return out; return out;
} }

View File

@ -1,44 +1,43 @@
find_package(Boost 1.54 COMPONENTS program_options REQUIRED) find_package(Boost 1.54 COMPONENTS program_options REQUIRED)
include_directories( include_directories(
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}
${Boost_INCLUDE_DIR}) ${Boost_INCLUDE_DIR})
add_executable(benchmark benchmark_main.cpp) add_executable(benchmark benchmark_main.cpp)
target_link_libraries(benchmark mstch) target_link_libraries(benchmark mstch)
add_executable(filetoheader filetoheader.cpp) add_executable(headerize headerize.cpp)
target_link_libraries(filetoheader ${Boost_PROGRAM_OPTIONS_LIBRARY}) target_link_libraries(headerize ${Boost_PROGRAM_OPTIONS_LIBRARY})
set(filetoheader_exe ${CMAKE_CURRENT_BINARY_DIR}/filetoheader${CMAKE_EXECUTABLE_SUFFIX}) set(headerize_exe ${CMAKE_CURRENT_BINARY_DIR}/headerize${CMAKE_EXECUTABLE_SUFFIX})
file(GLOB data_files RELATIVE file(GLOB data_files RELATIVE
"${CMAKE_SOURCE_DIR}/test/data" "${CMAKE_SOURCE_DIR}/test/data"
"${CMAKE_SOURCE_DIR}/test/data/*.hpp") "${CMAKE_SOURCE_DIR}/test/data/*.hpp")
foreach(data_file ${data_files}) foreach(data_file ${data_files})
list(APPEND genargs "-C${data_file}") list(APPEND genargs "-C${data_file}")
string(REGEX REPLACE "\\.hpp" "" test_name "${data_file}") string(REGEX REPLACE "\\.hpp" "" test_name "${data_file}")
list(APPEND tests "${test_name}") list(APPEND tests "${test_name}")
endforeach(data_file) endforeach(data_file)
file(GLOB string_files RELATIVE file(GLOB string_files RELATIVE
"${CMAKE_SOURCE_DIR}/test/data" "${CMAKE_SOURCE_DIR}/test/data"
"${CMAKE_SOURCE_DIR}/test/data/*.mustache" "${CMAKE_SOURCE_DIR}/test/data/*.mustache"
"${CMAKE_SOURCE_DIR}/test/data/*.txt" "${CMAKE_SOURCE_DIR}/test/data/*.txt"
"${CMAKE_SOURCE_DIR}/test/data/*.partial" "${CMAKE_SOURCE_DIR}/test/data/*.partial")
)
foreach(string_file ${string_files}) foreach(string_file ${string_files})
list(APPEND genargs "-S${string_file}") list(APPEND genargs "-S${string_file}")
endforeach(string_file) endforeach(string_file)
add_custom_command( add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/test_data.hpp OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/test_data.hpp
COMMAND ${filetoheader_exe} --output ${CMAKE_CURRENT_BINARY_DIR}/test_data.hpp --namespace mstchtest ${genargs} COMMAND ${headerize_exe} --output ${CMAKE_CURRENT_BINARY_DIR}/test_data.hpp --namespace mstchtest ${genargs}
DEPENDS ${filetoheader_exe} DEPENDS ${headerize_exe}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/test/data/) WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/test/data/)
set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/test_data.hpp PROPERTIES GENERATED TRUE) 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) add_custom_target(test_data_hpp DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/test_data.hpp)
@ -47,5 +46,5 @@ target_link_libraries(mstch_test mstch)
add_dependencies(mstch_test test_data_hpp) add_dependencies(mstch_test test_data_hpp)
foreach(test ${tests}) foreach(test ${tests})
add_test(NAME ${test} COMMAND mstch_test ${test}) add_test(NAME ${test} COMMAND mstch_test ${test})
endforeach(test) endforeach(test)

View File

@ -3,40 +3,42 @@
#include <chrono> #include <chrono>
#include <iostream> #include <iostream>
unsigned long current_msec() {
return std::chrono::system_clock::now().time_since_epoch() /
std::chrono::milliseconds(1);
}
int main() { int main() {
std::string comment_tmp{ std::string comment_tmp{
"<div class=\"comments\"><h3>{{header}}</h3><ul>" "<div class=\"comments\"><h3>{{header}}</h3><ul>"
"{{#comments}}<li class=\"comment\"><h5>{{name}}</h5>" "{{#comments}}<li class=\"comment\"><h5>{{name}}</h5>"
"<p>{{body}}</p></li>{{/comments}}</ul></div>" "<p>{{body}}</p></li>{{/comments}}</ul></div>"
}; };
auto comment_view = mstch::map{ auto comment_view = mstch::map{
{"header", std::string{"My Post Comments"}}, {"header", std::string{"My Post Comments"}},
{"comments", mstch::array{ {"comments", mstch::array{
mstch::map{{"name", std::string{"Joe"}}, {"body", std::string{"Thanks for this post!"}}}, 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{"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{"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{"Kathy"}}, {"body", std::string{"Thanks for this post!"}}},
mstch::map{{"name", std::string{"George"}}, {"body", std::string{"Thanks for this post!"}}} mstch::map{{"name", std::string{"George"}}, {"body", std::string{"Thanks for this post!"}}}
}} }}
}; };
std::vector<int> times; std::vector<unsigned long> times;
for(int j = 0; j < 10; j++) { for (int j = 0; j < 10; j++) {
unsigned long start = unsigned long start = current_msec();
std::chrono::system_clock::now().time_since_epoch() / for (int i = 0; i < 5000; i++)
std::chrono::milliseconds(1); mstch::render(comment_tmp, comment_view);
for(int i = 0; i < 5000; i++) { times.push_back(current_msec() - start);
mstch::render(comment_tmp, comment_view); }
}
times.push_back((std::chrono::system_clock::now().time_since_epoch() /
std::chrono::milliseconds(1)) - start);
}
float avg = 0; float avg = 0;
for(int i: times) avg += i; for (auto i: times)
avg /= times.size(); avg += i;
avg /= times.size();
std::cout << avg << std::endl; std::cout << avg << std::endl;
return 0; return 0;
} }

View File

@ -1,82 +0,0 @@
#include <iostream>
#include <fstream>
#include <boost/algorithm/string/replace.hpp>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <boost/program_options/parsers.hpp>
void wrap_code(std::istream& input, std::ostream& output) {
std::string line;
while (std::getline(input, line)) {
output << line;
if(!input.eof()) output << std::endl;
}
output << std::endl;
}
void wrap_string(std::istream& input, std::ostream& output, const std::string& variable_name) {
output << "const std::string " << variable_name << "{" << std::endl;;
std::string line;
while (std::getline(input, line)) {
boost::replace_all(line, "\\", "\\\\");
boost::replace_all(line, "\"", "\\\"");
output << " \"" << line;
if(!input.eof()) output << "\\n";
output << "\"" << std::endl;
}
output << "};" << std::endl;
}
int main(int argc, char* argv[]) {
namespace po = boost::program_options;
po::options_description desc("Allowed options");
desc.add_options()
("help", "show help")
("output", po::value<std::string>(), "output file")
("namespace", po::value<std::string>(), "namespace to use")
("input-string,S", po::value<std::vector<std::string>>(), "files to parse as strings")
("input-code,C", po::value<std::vector<std::string>>(), "files to parse as code");
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
if (vm.count("help")) {
std::cout << desc << std::endl;
return 1;
}
if (!vm.count("output")) {
std::cout << "Output file not set" << std::endl;
return 1;
}
std::ofstream output(vm["output"].as<std::string>(), std::ios::out);
if(vm.count("namespace"))
output << "namespace " << vm["namespace"].as<std::string>() << " {" << std::endl;
if(vm.count("input-string")) {
for (auto &string_filename: vm["input-string"].as<std::vector<std::string>>()) {
std::ifstream input(string_filename, std::ios::in);
wrap_string(input, output, boost::replace_all_copy(string_filename, ".", "_"));
input.close();
}
}
if(vm.count("input-code")) {
for(auto& data_filename: vm["input-code"].as<std::vector<std::string>>()) {
std::ifstream input(data_filename, std::ios::in);
wrap_code(input, output);
input.close();
}
}
if(vm.count("namespace"))
output << "}" << std::endl;
output.close();
return 0;
}

89
test/headerize.cpp Normal file
View File

@ -0,0 +1,89 @@
#include <iostream>
#include <fstream>
#include <boost/algorithm/string/replace.hpp>
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <boost/program_options/parsers.hpp>
void wrap_code(std::istream& input, std::ostream& output) {
std::string line;
while (std::getline(input, line)) {
output << line;
if (!input.eof())
output << std::endl;
}
output << std::endl;
}
void wrap_string(std::istream& input, std::ostream& output,
const std::string& variable_name)
{
output << "const std::string " << variable_name << "{" << std::endl;;
std::string line;
while (std::getline(input, line)) {
boost::replace_all(line, "\\", "\\\\");
boost::replace_all(line, "\"", "\\\"");
output << " \"" << line;
if (!input.eof())
output << "\\n";
output << "\"" << std::endl;
}
output << "};" << std::endl;
}
int main(int argc, char* argv[]) {
namespace po = boost::program_options;
po::options_description desc("Allowed options");
desc.add_options()
("help", "show help")
("output", po::value<std::string>(), "output file")
("namespace", po::value<std::string>(), "namespace to use")
("input-string,S", po::value<std::vector<std::string>>(),
"files to parse as strings")
("input-code,C", po::value<std::vector<std::string>>(),
"files to parse as code");
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
if (vm.count("help")) {
std::cout << desc << std::endl;
return 1;
}
if (!vm.count("output")) {
std::cout << "Output file not set" << std::endl;
return 1;
}
std::ofstream output(vm["output"].as<std::string>(), std::ios::out);
if (vm.count("namespace"))
output << "namespace " << vm["namespace"].as<std::string>() << " {" << std::endl;
if (vm.count("input-string")) {
for (auto& string_filename: vm["input-string"].as<std::vector<std::string>>()) {
std::ifstream input(string_filename, std::ios::in);
wrap_string(input, output,
boost::replace_all_copy(string_filename, ".", "_"));
input.close();
}
}
if (vm.count("input-code")) {
for (auto& data_filename: vm["input-code"].as<std::vector<std::string>>()) {
std::ifstream input(data_filename, std::ios::in);
wrap_code(input, output);
input.close();
}
}
if (vm.count("namespace"))
output << "}" << std::endl;
output.close();
return 0;
}

View File

@ -7,11 +7,11 @@
using namespace mstchtest; using namespace mstchtest;
#define MSTCH_PARTIAL_TEST(x) TEST_CASE(#x) { \ #define MSTCH_PARTIAL_TEST(x) TEST_CASE(#x) { \
REQUIRE(x ## _txt == mstch::render(x ## _mustache, x ## _data, {{"partial", x ## _partial}})); \ REQUIRE(x ## _txt == mstch::render(x ## _mustache, x ## _data, {{"partial", x ## _partial}})); \
} }
#define MSTCH_TEST(x) TEST_CASE(#x) { \ #define MSTCH_TEST(x) TEST_CASE(#x) { \
REQUIRE(x ## _txt == mstch::render(x ## _mustache, x ## _data)); \ REQUIRE(x ## _txt == mstch::render(x ## _mustache, x ## _data)); \
} }
MSTCH_TEST(ampersand_escape) MSTCH_TEST(ampersand_escape)