specs compliant lambdas
This commit is contained in:
parent
ae4b413788
commit
1d0d1bde8f
11
README.md
11
README.md
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
mstch is a complete implementation of [{{mustache}}](http://mustache.github.io/)
|
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)
|
templates using modern C++. It's compliant with [specifications](https://github.com/mustache/spec)
|
||||||
v1.1.2.
|
v1.1.2, including the lambda module.
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/no1msd/mstch.svg?branch=master)](https://travis-ci.org/no1msd/mstch)
|
[![Build Status](https://travis-ci.org/no1msd/mstch.svg?branch=master)](https://travis-ci.org/no1msd/mstch)
|
||||||
|
|
||||||
@ -125,16 +125,15 @@ Output:
|
|||||||
Hello World!
|
Hello World!
|
||||||
```
|
```
|
||||||
|
|
||||||
Or it accepts a `const std::string&` and a `mstch::renderer`. The first one is
|
Or it accepts a `const std::string&` that gets the unrendered literal block.
|
||||||
passed the unrendered literal block, the second is a `std::function` that can be
|
The returned string will be parsed in both cases:
|
||||||
called to render it:
|
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
std::string view{"{{#bold}}{{yay}} :){{/bold}}"};
|
std::string view{"{{#bold}}{{yay}} :){{/bold}}"};
|
||||||
mstch::map context{
|
mstch::map context{
|
||||||
{"yay", std::string{"Yay!"}},
|
{"yay", std::string{"Yay!"}},
|
||||||
{"bold", mstch::lambda{[](const std::string& text, mstch::renderer render) {
|
{"bold", mstch::lambda{[](const std::string& text) {
|
||||||
return "<b>" + render(text) + "</b>";
|
return "<b>" + text + "</b>";
|
||||||
}}}
|
}}}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -9,8 +9,6 @@
|
|||||||
|
|
||||||
namespace mstch {
|
namespace mstch {
|
||||||
|
|
||||||
using renderer = std::function<std::string(const std::string&)>;
|
|
||||||
|
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
template<class N>
|
template<class N>
|
||||||
@ -47,7 +45,7 @@ class is_fun {
|
|||||||
template <typename C> static fun_without_args& test(
|
template <typename C> static fun_without_args& test(
|
||||||
really_has<std::string(C::*)() const, &C::operator()>*);
|
really_has<std::string(C::*)() const, &C::operator()>*);
|
||||||
template <typename C> static fun_with_args& test(
|
template <typename C> static fun_with_args& test(
|
||||||
really_has<std::string(C::*)(const std::string&,renderer) const,
|
really_has<std::string(C::*)(const std::string&) const,
|
||||||
&C::operator()>*);
|
&C::operator()>*);
|
||||||
template <typename> static not_fun& test(...);
|
template <typename> static not_fun& test(...);
|
||||||
|
|
||||||
@ -62,7 +60,7 @@ class lambda {
|
|||||||
public:
|
public:
|
||||||
template<class F>
|
template<class F>
|
||||||
lambda(F f, typename std::enable_if<internal::is_fun<F>::no_args>::type* =0):
|
lambda(F f, typename std::enable_if<internal::is_fun<F>::no_args>::type* =0):
|
||||||
fun([f](const std::string&,renderer){return f();})
|
fun([f](const std::string&){return f();})
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,15 +70,12 @@ class lambda {
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string operator()(
|
std::string operator()(const std::string& text = "") const {
|
||||||
const std::string& text = "",
|
return fun(text);
|
||||||
renderer renderer = [](const std::string&){ return "";}) const
|
|
||||||
{
|
|
||||||
return fun(text, renderer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::function<std::string(const std::string&, renderer)> fun;
|
std::function<std::string(const std::string&)> fun;
|
||||||
};
|
};
|
||||||
|
|
||||||
using node = boost::make_recursive_variant<
|
using node = boost::make_recursive_variant<
|
||||||
|
@ -18,9 +18,9 @@ std::string outside_section::render(
|
|||||||
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 visit(render_node(flag::escape_html), ctx.get_node(token.name()));
|
return visit(render_node(ctx, flag::escape_html), ctx.get_node(token.name()));
|
||||||
case token::type::unescaped_variable:
|
case token::type::unescaped_variable:
|
||||||
return visit(render_node(flag::none), ctx.get_node(token.name()));
|
return visit(render_node(ctx, 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:
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <boost/variant/static_visitor.hpp>
|
#include <boost/variant/static_visitor.hpp>
|
||||||
|
|
||||||
|
#include "render_context.hpp"
|
||||||
#include "mstch/mstch.hpp"
|
#include "mstch/mstch.hpp"
|
||||||
#include "utils.hpp"
|
#include "utils.hpp"
|
||||||
|
|
||||||
@ -11,7 +12,9 @@ namespace mstch {
|
|||||||
class render_node: public boost::static_visitor<std::string> {
|
class render_node: public boost::static_visitor<std::string> {
|
||||||
public:
|
public:
|
||||||
enum class flag { none, escape_html };
|
enum class flag { none, escape_html };
|
||||||
render_node(flag p_flag = flag::none): m_flag(p_flag) {
|
render_node(render_context& ctx, flag p_flag = flag::none):
|
||||||
|
ctx(ctx), m_flag(p_flag)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
@ -34,7 +37,8 @@ class render_node: public boost::static_visitor<std::string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string operator()(const lambda& value) const {
|
std::string operator()(const lambda& value) const {
|
||||||
return (m_flag == flag::escape_html) ? html_escape(value()) : value();
|
auto rendered = render_context::push(ctx).render(template_type{value()});
|
||||||
|
return (m_flag == flag::escape_html) ? html_escape(rendered) : rendered;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string operator()(const std::string& value) const {
|
std::string operator()(const std::string& value) const {
|
||||||
@ -42,6 +46,7 @@ class render_node: public boost::static_visitor<std::string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
render_context& ctx;
|
||||||
flag m_flag;
|
flag m_flag;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -29,9 +29,7 @@ class render_section: public boost::static_visitor<std::string> {
|
|||||||
for(auto& token: section)
|
for(auto& token: section)
|
||||||
section_str += token.raw();
|
section_str += token.raw();
|
||||||
|
|
||||||
return fun(section_str, [this](const std::string& str) {
|
return render_context::push(ctx).render(template_type{fun(section_str)});
|
||||||
return render_context::push(ctx).render(template_type{str});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string operator()(const array& array) const {
|
std::string operator()(const array& array) const {
|
||||||
|
@ -19,8 +19,8 @@ class higher_order_sections: public mstch::object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mstch::node bolder() {
|
mstch::node bolder() {
|
||||||
return mstch::lambda{[this](const std::string& text, mstch::renderer render) {
|
return mstch::lambda{[this](const std::string& text) {
|
||||||
return text + " => <b>" + render(text) + "</b> " + m_helper;
|
return "<b>" + text + "</b> " + m_helper;
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1 +1 @@
|
|||||||
Hi {{name}}. => <b>Hi Tater.</b> To tinker?
|
<b>Hi Tater.</b> To tinker?
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const mstch::node nested_higher_order_sections_data = mstch::map{
|
const mstch::node nested_higher_order_sections_data = mstch::map{
|
||||||
{"bold", mstch::lambda{[](const std::string& text, mstch::renderer render) {
|
{"bold", mstch::lambda{[](const std::string& text) {
|
||||||
return std::string{"<b>"} + render(text) + std::string{"</b>"};
|
return std::string{"<b>"} + text + std::string{"</b>"};
|
||||||
}}},
|
}}},
|
||||||
{"person", mstch::map{{"name", std::string{"Jonas"}}}}
|
{"person", mstch::map{{"name", std::string{"Jonas"}}}}
|
||||||
};
|
};
|
@ -1,5 +1,5 @@
|
|||||||
const mstch::node section_functions_in_partials_data = mstch::map{
|
const mstch::node section_functions_in_partials_data = mstch::map{
|
||||||
{"bold", mstch::lambda{[](const std::string& text, mstch::renderer render) {
|
{"bold", mstch::lambda{[](const std::string& text) {
|
||||||
return std::string{"<b>"} + render(text) + std::string{"</b>"};
|
return std::string{"<b>"} + text + std::string{"</b>"};
|
||||||
}}}
|
}}}
|
||||||
};
|
};
|
@ -56,7 +56,9 @@ int main(int argc, char* argv[]) {
|
|||||||
if (vm.count("input-string"))
|
if (vm.count("input-string"))
|
||||||
for (auto& filename: vm["input-string"].as<std::vector<std::string>>()) {
|
for (auto& filename: vm["input-string"].as<std::vector<std::string>>()) {
|
||||||
std::ifstream input(filename, std::ios::in);
|
std::ifstream input(filename, std::ios::in);
|
||||||
wrap_string(input, output, boost::replace_all_copy(filename, ".", "_"));
|
std::string variable_name = boost::replace_all_copy(filename, ".", "_");
|
||||||
|
boost::replace_all(variable_name, "~", "");
|
||||||
|
wrap_string(input, output, variable_name);
|
||||||
input.close();
|
input.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1
test/specs/~lambdas.json
Normal file
1
test/specs/~lambdas.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"__ATTN__":"Do not edit this file; changes belong in the appropriate YAML file.","overview":"Lambdas are a special-cased data type for use in interpolations and\nsections.\n\nWhen used as the data value for an Interpolation tag, the lambda MUST be\ntreatable as an arity 0 function, and invoked as such. The returned value\nMUST be rendered, then interpolated in place of the lambda.\n\nWhen used as the data value for a Section tag, the lambda MUST be treatable\nas an arity 1 function, and invoked as such (passing a String containing the\nunprocessed section contents). The returned value MUST be rendered, then\ninterpolated in place of the section.\n","tests":[{"name":"Interpolation","data":{"lambda":{"php":"return \"world\";","__tag__":"code","perl":"sub { \"world\" }","python":"lambda: \"world\"","ruby":"proc { \"world\" }","js":"function() { return \"world\" }"}},"expected":"Hello, world!","template":"Hello, {{lambda}}!","desc":"A lambda's return value should be interpolated."},{"name":"Interpolation - Expansion","data":{"planet":"world","lambda":{"php":"return \"{{planet}}\";","__tag__":"code","perl":"sub { \"{{planet}}\" }","python":"lambda: \"{{planet}}\"","ruby":"proc { \"{{planet}}\" }","js":"function() { return \"{{planet}}\" }"}},"expected":"Hello, world!","template":"Hello, {{lambda}}!","desc":"A lambda's return value should be parsed."},{"name":"Interpolation - Multiple Calls","data":{"lambda":{"php":"global $calls; return ++$calls;","__tag__":"code","perl":"sub { no strict; $calls += 1 }","python":"lambda: globals().update(calls=globals().get(\"calls\",0)+1) or calls","ruby":"proc { $calls ||= 0; $calls += 1 }","js":"function() { return (g=(function(){return this})()).calls=(g.calls||0)+1 }"}},"expected":"1 == 2 == 3","template":"{{lambda}} == {{{lambda}}} == {{lambda}}","desc":"Interpolated lambdas should not be cached."},{"name":"Escaping","data":{"lambda":{"php":"return \">\";","__tag__":"code","perl":"sub { \">\" }","python":"lambda: \">\"","ruby":"proc { \">\" }","js":"function() { return \">\" }"}},"expected":"<>>","template":"<{{lambda}}{{{lambda}}}","desc":"Lambda results should be appropriately escaped."},{"name":"Section","data":{"x":"Error!","lambda":{"php":"return ($text == \"{{x}}\") ? \"yes\" : \"no\";","__tag__":"code","perl":"sub { $_[0] eq \"{{x}}\" ? \"yes\" : \"no\" }","python":"lambda text: text == \"{{x}}\" and \"yes\" or \"no\"","ruby":"proc { |text| text == \"{{x}}\" ? \"yes\" : \"no\" }","js":"function(txt) { return (txt == \"{{x}}\" ? \"yes\" : \"no\") }"}},"expected":"<yes>","template":"<{{#lambda}}{{x}}{{/lambda}}>","desc":"Lambdas used for sections should receive the raw section string."},{"name":"Section - Expansion","data":{"planet":"Earth","lambda":{"php":"return $text . \"{{planet}}\" . $text;","__tag__":"code","perl":"sub { $_[0] . \"{{planet}}\" . $_[0] }","python":"lambda text: \"%s{{planet}}%s\" % (text, text)","ruby":"proc { |text| \"#{text}{{planet}}#{text}\" }","js":"function(txt) { return txt + \"{{planet}}\" + txt }"}},"expected":"<-Earth->","template":"<{{#lambda}}-{{/lambda}}>","desc":"Lambdas used for sections should have their results parsed."},{"name":"Section - Multiple Calls","data":{"lambda":{"php":"return \"__\" . $text . \"__\";","__tag__":"code","perl":"sub { \"__\" . $_[0] . \"__\" }","python":"lambda text: \"__%s__\" % (text)","ruby":"proc { |text| \"__#{text}__\" }","js":"function(txt) { return \"__\" + txt + \"__\" }"}},"expected":"__FILE__ != __LINE__","template":"{{#lambda}}FILE{{/lambda}} != {{#lambda}}LINE{{/lambda}}","desc":"Lambdas used for sections should not be cached."},{"name":"Inverted Section","data":{"static":"static","lambda":{"php":"return false;","__tag__":"code","perl":"sub { 0 }","python":"lambda text: 0","ruby":"proc { |text| false }","js":"function(txt) { return false }"}},"expected":"<>","template":"<{{^lambda}}{{static}}{{/lambda}}>","desc":"Lambdas used for inverted sections should be considered truthy."}]}
|
32
test/specs_lambdas.hpp
Normal file
32
test/specs_lambdas.hpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
std::map<std::string,std::function<std::string(const std::string&)>> specs_lambdas {
|
||||||
|
{"Interpolation", [](const std::string&) {
|
||||||
|
return "world";
|
||||||
|
}},
|
||||||
|
{"Interpolation - Expansion", [](const std::string&) {
|
||||||
|
return "{{planet}}";
|
||||||
|
}},
|
||||||
|
{"Interpolation - Alternate Delimiters", [](const std::string&) {
|
||||||
|
return "|planet| => {{planet}}";
|
||||||
|
}},
|
||||||
|
{"Interpolation - Multiple Calls", [](const std::string&) {
|
||||||
|
static int calls = 0; return std::to_string(++calls);
|
||||||
|
}},
|
||||||
|
{"Escaping", [](const std::string&) {
|
||||||
|
return ">";
|
||||||
|
}},
|
||||||
|
{"Section", [](const std::string& txt) {
|
||||||
|
return (txt == "{{x}}") ? "yes" : "no";
|
||||||
|
}},
|
||||||
|
{"Section - Expansion", [](const std::string& txt) {
|
||||||
|
return txt + "{{planet}}" + txt;
|
||||||
|
}},
|
||||||
|
{"Section - Alternate Delimiters", [](const std::string& txt) {
|
||||||
|
return txt + "{{planet}} => |planet|" + txt;
|
||||||
|
}},
|
||||||
|
{"Section - Multiple Calls", [](const std::string& txt) {
|
||||||
|
return "__" + txt + "__";
|
||||||
|
}},
|
||||||
|
{"Inverted Section", [](const std::string& txt) {
|
||||||
|
return "";
|
||||||
|
}}
|
||||||
|
};
|
@ -5,6 +5,7 @@
|
|||||||
#include "mstch/mstch.hpp"
|
#include "mstch/mstch.hpp"
|
||||||
#include "test_data.hpp"
|
#include "test_data.hpp"
|
||||||
#include "specs_data.hpp"
|
#include "specs_data.hpp"
|
||||||
|
#include "specs_lambdas.hpp"
|
||||||
|
|
||||||
using namespace mstchtest;
|
using namespace mstchtest;
|
||||||
|
|
||||||
@ -25,6 +26,9 @@ using namespace mstchtest;
|
|||||||
if(test.count("partials")) \
|
if(test.count("partials")) \
|
||||||
for(auto& partial_item: get<mstch::map>(test["partials"])) \
|
for(auto& partial_item: get<mstch::map>(test["partials"])) \
|
||||||
partials.insert(std::make_pair(partial_item.first, get<std::string>(partial_item.second))); \
|
partials.insert(std::make_pair(partial_item.first, get<std::string>(partial_item.second))); \
|
||||||
|
for(auto& data_item: get<mstch::map>(test["data"])) \
|
||||||
|
if(data_item.first == "lambda") \
|
||||||
|
data_item.second = mstch::lambda{specs_lambdas[get<std::string>(test["name"])]}; \
|
||||||
SECTION(get<std::string>(test["name"])) \
|
SECTION(get<std::string>(test["name"])) \
|
||||||
REQUIRE(mstch::render( \
|
REQUIRE(mstch::render( \
|
||||||
get<std::string>(test["template"]), \
|
get<std::string>(test["template"]), \
|
||||||
@ -98,3 +102,4 @@ SPECS_TEST(interpolation)
|
|||||||
SPECS_TEST(inverted)
|
SPECS_TEST(inverted)
|
||||||
SPECS_TEST(partials)
|
SPECS_TEST(partials)
|
||||||
SPECS_TEST(sections)
|
SPECS_TEST(sections)
|
||||||
|
SPECS_TEST(lambdas)
|
||||||
|
Loading…
Reference in New Issue
Block a user