diff --git a/libdevcore/Whiskers.cpp b/libdevcore/Whiskers.cpp index a6db35c8e..8a20f2cd7 100644 --- a/libdevcore/Whiskers.cpp +++ b/libdevcore/Whiskers.cpp @@ -30,64 +30,73 @@ using namespace std; using namespace dev; -Whiskers::Whiskers(string const& _template): -m_template(_template) +Whiskers::Whiskers(string _template): + m_template(move(_template)) { } -Whiskers& Whiskers::operator ()(string const& _parameter, string const& _value) +Whiskers& Whiskers::operator()(string _parameter, string _value) { - assertThrow( - m_parameters.count(_parameter) == 0, - WhiskersError, - _parameter + " already set." - ); - assertThrow( - m_listParameters.count(_parameter) == 0, - WhiskersError, - _parameter + " already set as list parameter." - ); - m_parameters[_parameter] = _value; - + checkParameterUnknown(_parameter); + m_parameters[move(_parameter)] = move(_value); return *this; } -Whiskers& Whiskers::operator ()( - string const& _listParameter, - vector> const& _values +Whiskers& Whiskers::operator()(string _parameter, bool _value) +{ + checkParameterUnknown(_parameter); + m_conditions[move(_parameter)] = _value; + return *this; +} + +Whiskers& Whiskers::operator()( + string _listParameter, + vector> _values ) { - assertThrow( - m_listParameters.count(_listParameter) == 0, - WhiskersError, - _listParameter + " already set." - ); - assertThrow( - m_parameters.count(_listParameter) == 0, - WhiskersError, - _listParameter + " already set as value parameter." - ); - m_listParameters[_listParameter] = _values; - + checkParameterUnknown(_listParameter); + m_listParameters[move(_listParameter)] = move(_values); return *this; } string Whiskers::render() const { - return replace(m_template, m_parameters, m_listParameters); + return replace(m_template, m_parameters, m_conditions, m_listParameters); +} + +void Whiskers::checkParameterUnknown(string const& _parameter) +{ + assertThrow( + !m_parameters.count(_parameter), + WhiskersError, + _parameter + " already set as value parameter." + ); + assertThrow( + !m_conditions.count(_parameter), + WhiskersError, + _parameter + " already set as condition parameter." + ); + assertThrow( + !m_listParameters.count(_parameter), + WhiskersError, + _parameter + " already set as list parameter." + ); } string Whiskers::replace( string const& _template, StringMap const& _parameters, + map const& _conditions, map> const& _listParameters ) { using namespace boost; - static regex listOrTag("<([^#/>]+)>|<#([^>]+)>(.*?)"); + static regex listOrTag("<([^#/?!>]+)>|<#([^>]+)>(.*?)|<\\?([^>]+)>(.*?)((.*?))?"); return regex_replace(_template, listOrTag, [&](match_results _match) -> string { string tagName(_match[1]); + string listName(_match[2]); + string conditionName(_match[4]); if (!tagName.empty()) { assertThrow( @@ -99,20 +108,32 @@ string Whiskers::replace( ); return _parameters.at(tagName); } - else + else if (!listName.empty()) { - string listName(_match[2]); string templ(_match[3]); - assertThrow(!listName.empty(), WhiskersError, ""); assertThrow( _listParameters.count(listName), WhiskersError, "List parameter " + listName + " not set." ); string replacement; for (auto const& parameters: _listParameters.at(listName)) - replacement += replace(templ, joinMaps(_parameters, parameters)); + replacement += replace(templ, joinMaps(_parameters, parameters), _conditions); return replacement; } + else + { + assertThrow(!conditionName.empty(), WhiskersError, ""); + assertThrow( + _conditions.count(conditionName), + WhiskersError, "Condition parameter " + conditionName + " not set." + ); + return replace( + _conditions.at(conditionName) ? _match[5] : _match[7], + _parameters, + _conditions, + _listParameters + ); + } }); } diff --git a/libdevcore/Whiskers.h b/libdevcore/Whiskers.h index 21d46af4c..7944358a0 100644 --- a/libdevcore/Whiskers.h +++ b/libdevcore/Whiskers.h @@ -34,45 +34,63 @@ namespace dev DEV_SIMPLE_EXCEPTION(WhiskersError); -/// -/// Moustache-like templates. -/// -/// Usage: -/// std::vector> listValues(2); -/// listValues[0]["k"] = "key1"; -/// listValues[0]["v"] = "value1"; -/// listValues[1]["k"] = "key2"; -/// listValues[1]["v"] = "value2"; -/// auto s = Whiskers("\n<#list> -> \n") -/// ("p1", "HEAD") -/// ("list", listValues) -/// .render(); -/// -/// results in s == "HEAD\nkey1 -> value1\nkey2 -> value2\n" -/// -/// Note that lists cannot themselves contain lists - this would be a future feature. +/** + * Moustache-like templates. + * + * Usage: + * std::vector> listValues(2); + * listValues[0]["k"] = "key1"; + * listValues[0]["v"] = "value1"; + * listValues[1]["k"] = "key2"; + * listValues[1]["v"] = "value2"; + * auto s = Whiskers("y\n<#list> -> \n") + * ("p1", "HEAD") + * ("c", true) + * ("list", listValues) + * .render(); + * + * results in s == "HEAD\nkey1 -> value1\nkey2 -> value2\n" + * + * Note that lists cannot themselves contain lists - this would be a future feature. + * + * The elements are: + * - Regular parameter: + * just replaced + * - Condition parameter: ......, where "" is optional + * replaced (and recursively expanded) by the first part if the condition is true + * and by the second (or empty string if missing) if the condition is false + * - List parameter: <#list>... + * The part between the tags is repeated as often as values are provided + * in the mapping. Each list element can have its own parameter -> value mapping. + */ class Whiskers { public: using StringMap = std::map; using StringListMap = std::map>; - explicit Whiskers(std::string const& _template); + explicit Whiskers(std::string _template); - /// Sets a single parameter, . - Whiskers& operator()(std::string const& _parameter, std::string const& _value); + /// Sets a single regular parameter, . + Whiskers& operator()(std::string _parameter, std::string _value); + Whiskers& operator()(std::string _parameter, char const* _value) { return (*this)(_parameter, std::string{_value}); } + /// Sets a condition parameter, ...... + Whiskers& operator()(std::string _parameter, bool _value); /// Sets a list parameter, <#listName> . Whiskers& operator()( - std::string const& _listParameter, - std::vector const& _values + std::string _listParameter, + std::vector _values ); std::string render() const; private: + void checkParameterUnknown(std::string const& _parameter); + static std::string replace( std::string const& _template, StringMap const& _parameters, + std::map const& _conditions, StringListMap const& _listParameters = StringListMap() ); @@ -81,6 +99,7 @@ private: std::string m_template; StringMap m_parameters; + std::map m_conditions; StringListMap m_listParameters; }; diff --git a/test/libdevcore/Whiskers.cpp b/test/libdevcore/Whiskers.cpp index b12acdd70..8a785774e 100644 --- a/test/libdevcore/Whiskers.cpp +++ b/test/libdevcore/Whiskers.cpp @@ -55,6 +55,65 @@ BOOST_AUTO_TEST_CASE(tag_unavailable) BOOST_CHECK_THROW(m.render(), WhiskersError); } +BOOST_AUTO_TEST_CASE(list_unavailable) +{ + string templ = "<#b>"; + Whiskers m(templ); + BOOST_CHECK_THROW(m.render(), WhiskersError); +} + +BOOST_AUTO_TEST_CASE(name_type_collision) +{ + string templ = "<#b>"; + Whiskers m(templ); + m("b", "x"); + BOOST_CHECK_THROW(m("b", vector>{}), WhiskersError); +} + +BOOST_AUTO_TEST_CASE(conditional) +{ + string templ = "X"; + BOOST_CHECK_EQUAL(Whiskers(templ)("b", true).render(), "X"); + BOOST_CHECK_EQUAL(Whiskers(templ)("b", false).render(), ""); +} + +BOOST_AUTO_TEST_CASE(conditional_with_else) +{ + string templ = "XY"; + BOOST_CHECK_EQUAL(Whiskers(templ)("b", true).render(), "X"); + BOOST_CHECK_EQUAL(Whiskers(templ)("b", false).render(), "Y"); +} + +BOOST_AUTO_TEST_CASE(conditional_plus_params) +{ + string templ = " - _^ - "; + Whiskers m1(templ); + m1("b", true); + m1("r", "R"); + m1("t", "T"); + BOOST_CHECK_EQUAL(m1.render(), " - _R - "); + + Whiskers m2(templ); + m2("b", false); + m2("r", "R"); + m2("t", "T"); + BOOST_CHECK_EQUAL(m2.render(), " - ^T - "); +} + +BOOST_AUTO_TEST_CASE(conditional_plus_list) +{ + string templ = " - _<#l><#l> - "; + Whiskers m(templ); + m("b", false); + vector> list(2); + list[0]["x"] = "1"; + list[0]["y"] = "a"; + list[1]["x"] = "2"; + list[1]["y"] = "b"; + m("l", list); + BOOST_CHECK_EQUAL(m.render(), " - ab - "); +} + BOOST_AUTO_TEST_CASE(complicated_replacement) { string templ = "a x \n >.";