passing specs v1.1.2

This commit is contained in:
Daniel Sipka 2015-05-04 14:29:15 +02:00
parent 0afaf7b8a1
commit 415de61de4
9 changed files with 162 additions and 1 deletions

View File

@ -41,9 +41,27 @@ add_custom_command(
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}/test/specs"
"${CMAKE_SOURCE_DIR}/test/specs/*.json")
foreach(specs_file ${specs_files})
list(APPEND specsargs "-S${specs_file}")
string(REGEX REPLACE "\\.json" "" test_name "${specs_file}")
list(APPEND tests "specs_${test_name}")
endforeach(specs_file)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/specs_data.hpp
COMMAND ${headerize_exe} --output ${CMAKE_CURRENT_BINARY_DIR}/specs_data.hpp --namespace mstchtest ${specsargs}
DEPENDS ${headerize_exe}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/test/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)
add_dependencies(mstch_test test_data_hpp specs_data_hpp)
foreach(test ${tests})
add_test(NAME ${test} COMMAND mstch_test ${test})

111
test/json.hpp Normal file
View File

@ -0,0 +1,111 @@
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/fusion/adapted/std_pair.hpp>
#include <map>
namespace json {
namespace spirit = boost::spirit;
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
using node = boost::make_recursive_variant<
std::nullptr_t, std::string, int, double, bool,
std::map<const std::string, boost::recursive_variant_>,
std::vector<boost::recursive_variant_>>::type;
using map = std::map<const std::string, node>;
using array = std::vector<node>;
template<class Iterator, class Node, class Map, class Array>
struct json_grammar: qi::grammar<Iterator, Node(), ascii::space_type> {
struct esc_parser: qi::symbols<char,char> {
esc_parser() {
add("\\\\" , '\\') ("\\\"" , '"' ) ("\\n" , '\n') ("\\r" , '\r')
("\\b" , '\b') ("\\f" , '\f') ("\\t" , '\t');
}
} escaped;
json_grammar(): json_grammar::base_type(value_rule) {
value_rule %= string_rule |
qi::double_ |
object_rule |
array_rule |
qi::lit("true" )[spirit::_val = true ] |
qi::lit("false")[spirit::_val = false] |
"null";
string_rule = spirit::lexeme['"' >> *(escaped | (qi::char_ - '"')) >> '"'];
object_rule = '{' >> -((string_rule >> ':' >> value_rule) % ',') >> '}';
array_rule = '[' >> -(value_rule % ',') >> ']';
}
qi::rule<Iterator, Node(), ascii::space_type> value_rule;
qi::rule<Iterator, Map(), ascii::space_type> object_rule;
qi::rule<Iterator, Array(), ascii::space_type> array_rule;
qi::rule<Iterator, std::string(), ascii::space_type> string_rule;
};
struct json_visitor: boost::static_visitor<std::string> {
std::string escape(const std::string& str) const {
std::string ret{str};
boost::replace_all(ret, "\\", "\\\\");
boost::replace_all(ret, "\"", "\\\"");
return ret;
}
std::string operator()(const std::nullptr_t& value) const {
return "null";
}
std::string operator()(const std::string& value) const {
return "\"" + escape(value) + "\"";
}
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 json::map& value) const {
std::string out;
bool comma = false;
for(auto& item: value)
out += (comma++ ? ", \"" : "\"") + escape(item.first) + "\": " +
boost::apply_visitor(json_visitor(), item.second);
return "{" + out + "}";
}
std::string operator()(const json::array& value) const {
std::string out;
bool comma = false;
for(auto& item: value)
out += (comma++ ? ", " : "") + boost::apply_visitor(json_visitor(), item);
return "[" + out + "]";
}
};
template<class Node = node, class Map = map, class Array = array>
Node parse(const std::string& str) {
Node out;
json_grammar<std::string::const_iterator, Node, Map, Array> json;
auto first = str.begin(), last = str.end();
bool ret = qi::phrase_parse(first, last, json, ascii::space, out);
return (!ret || first != last) ? Node() : out;
}
template<class Node = node>
std::string stringify(const Node& node) {
return boost::apply_visitor(json_visitor(), node);
}
}

1
test/specs/comments.json Normal file
View File

@ -0,0 +1 @@
{"__ATTN__":"Do not edit this file; changes belong in the appropriate YAML file.","overview":"Comment tags represent content that should never appear in the resulting\noutput.\n\nThe tag's content may contain any substring (including newlines) EXCEPT the\nclosing delimiter.\n\nComment tags SHOULD be treated as standalone when appropriate.\n","tests":[{"name":"Inline","data":{},"expected":"1234567890","template":"12345{{! Comment Block! }}67890","desc":"Comment blocks should be removed from the template."},{"name":"Multiline","data":{},"expected":"1234567890\n","template":"12345{{!\n This is a\n multi-line comment...\n}}67890\n","desc":"Multiline comments should be permitted."},{"name":"Standalone","data":{},"expected":"Begin.\nEnd.\n","template":"Begin.\n{{! Comment Block! }}\nEnd.\n","desc":"All standalone comment lines should be removed."},{"name":"Indented Standalone","data":{},"expected":"Begin.\nEnd.\n","template":"Begin.\n {{! Indented Comment Block! }}\nEnd.\n","desc":"All standalone comment lines should be removed."},{"name":"Standalone Line Endings","data":{},"expected":"|\r\n|","template":"|\r\n{{! Standalone Comment }}\r\n|","desc":"\"\\r\\n\" should be considered a newline for standalone tags."},{"name":"Standalone Without Previous Line","data":{},"expected":"!","template":" {{! I'm Still Standalone }}\n!","desc":"Standalone tags should not require a newline to precede them."},{"name":"Standalone Without Newline","data":{},"expected":"!\n","template":"!\n {{! I'm Still Standalone }}","desc":"Standalone tags should not require a newline to follow them."},{"name":"Multiline Standalone","data":{},"expected":"Begin.\nEnd.\n","template":"Begin.\n{{!\nSomething's going on here...\n}}\nEnd.\n","desc":"All standalone comment lines should be removed."},{"name":"Indented Multiline Standalone","data":{},"expected":"Begin.\nEnd.\n","template":"Begin.\n {{!\n Something's going on here...\n }}\nEnd.\n","desc":"All standalone comment lines should be removed."},{"name":"Indented Inline","data":{},"expected":" 12 \n","template":" 12 {{! 34 }}\n","desc":"Inline comments should not strip whitespace"},{"name":"Surrounding Whitespace","data":{},"expected":"12345 67890","template":"12345 {{! Comment Block! }} 67890","desc":"Comment removal should preserve surrounding whitespace."}]}

View File

@ -0,0 +1 @@
{"__ATTN__":"Do not edit this file; changes belong in the appropriate YAML file.","overview":"Set Delimiter tags are used to change the tag delimiters for all content\nfollowing the tag in the current compilation unit.\n\nThe tag's content MUST be any two non-whitespace sequences (separated by\nwhitespace) EXCEPT an equals sign ('=') followed by the current closing\ndelimiter.\n\nSet Delimiter tags SHOULD be treated as standalone when appropriate.\n","tests":[{"name":"Pair Behavior","data":{"text":"Hey!"},"expected":"(Hey!)","template":"{{=<% %>=}}(<%text%>)","desc":"The equals sign (used on both sides) should permit delimiter changes."},{"name":"Special Characters","data":{"text":"It worked!"},"expected":"(It worked!)","template":"({{=[ ]=}}[text])","desc":"Characters with special meaning regexen should be valid delimiters."},{"name":"Sections","data":{"section":true,"data":"I got interpolated."},"expected":"[\n I got interpolated.\n |data|\n\n {{data}}\n I got interpolated.\n]\n","template":"[\n{{#section}}\n {{data}}\n |data|\n{{/section}}\n\n{{= | | =}}\n|#section|\n {{data}}\n |data|\n|/section|\n]\n","desc":"Delimiters set outside sections should persist."},{"name":"Inverted Sections","data":{"section":false,"data":"I got interpolated."},"expected":"[\n I got interpolated.\n |data|\n\n {{data}}\n I got interpolated.\n]\n","template":"[\n{{^section}}\n {{data}}\n |data|\n{{/section}}\n\n{{= | | =}}\n|^section|\n {{data}}\n |data|\n|/section|\n]\n","desc":"Delimiters set outside inverted sections should persist."},{"name":"Partial Inheritence","data":{"value":"yes"},"expected":"[ .yes. ]\n[ .yes. ]\n","template":"[ {{>include}} ]\n{{= | | =}}\n[ |>include| ]\n","desc":"Delimiters set in a parent template should not affect a partial.","partials":{"include":".{{value}}."}},{"name":"Post-Partial Behavior","data":{"value":"yes"},"expected":"[ .yes. .yes. ]\n[ .yes. .|value|. ]\n","template":"[ {{>include}} ]\n[ .{{value}}. .|value|. ]\n","desc":"Delimiters set in a partial should not affect the parent template.","partials":{"include":".{{value}}. {{= | | =}} .|value|."}},{"name":"Surrounding Whitespace","data":{},"expected":"| |","template":"| {{=@ @=}} |","desc":"Surrounding whitespace should be left untouched."},{"name":"Outlying Whitespace (Inline)","data":{},"expected":" | \n","template":" | {{=@ @=}}\n","desc":"Whitespace should be left untouched."},{"name":"Standalone Tag","data":{},"expected":"Begin.\nEnd.\n","template":"Begin.\n{{=@ @=}}\nEnd.\n","desc":"Standalone lines should be removed from the template."},{"name":"Indented Standalone Tag","data":{},"expected":"Begin.\nEnd.\n","template":"Begin.\n {{=@ @=}}\nEnd.\n","desc":"Indented standalone lines should be removed from the template."},{"name":"Standalone Line Endings","data":{},"expected":"|\r\n|","template":"|\r\n{{= @ @ =}}\r\n|","desc":"\"\\r\\n\" should be considered a newline for standalone tags."},{"name":"Standalone Without Previous Line","data":{},"expected":"=","template":" {{=@ @=}}\n=","desc":"Standalone tags should not require a newline to precede them."},{"name":"Standalone Without Newline","data":{},"expected":"=\n","template":"=\n {{=@ @=}}","desc":"Standalone tags should not require a newline to follow them."},{"name":"Pair with Padding","data":{},"expected":"||","template":"|{{= @ @ =}}|","desc":"Superfluous in-tag whitespace should be ignored."}]}

File diff suppressed because one or more lines are too long

1
test/specs/inverted.json Normal file

File diff suppressed because one or more lines are too long

1
test/specs/partials.json Normal file
View File

@ -0,0 +1 @@
{"__ATTN__":"Do not edit this file; changes belong in the appropriate YAML file.","overview":"Partial tags are used to expand an external template into the current\ntemplate.\n\nThe tag's content MUST be a non-whitespace character sequence NOT containing\nthe current closing delimiter.\n\nThis tag's content names the partial to inject. Set Delimiter tags MUST NOT\naffect the parsing of a partial. The partial MUST be rendered against the\ncontext stack local to the tag.\n\nPartial tags SHOULD be treated as standalone when appropriate. If this tag\nis used standalone, any whitespace preceding the tag should treated as\nindentation, and prepended to each line of the partial before rendering.\n","tests":[{"name":"Basic Behavior","data":{},"expected":"\"from partial\"","template":"\"{{>text}}\"","desc":"The greater-than operator should expand to the named partial.","partials":{"text":"from partial"}},{"name":"Context","data":{"text":"content"},"expected":"\"*content*\"","template":"\"{{>partial}}\"","desc":"The greater-than operator should operate within the current context.","partials":{"partial":"*{{text}}*"}},{"name":"Recursion","data":{"content":"X","nodes":[{"content":"Y","nodes":[]}]},"expected":"X<Y<>>","template":"{{>node}}","desc":"The greater-than operator should properly recurse.","partials":{"node":"{{content}}<{{#nodes}}{{>node}}{{/nodes}}>"}},{"name":"Surrounding Whitespace","data":{},"expected":"| \t|\t |","template":"| {{>partial}} |","desc":"The greater-than operator should not alter surrounding whitespace.","partials":{"partial":"\t|\t"}},{"name":"Inline Indentation","data":{"data":"|"},"expected":" | >\n>\n","template":" {{data}} {{> partial}}\n","desc":"Whitespace should be left untouched.","partials":{"partial":">\n>"}},{"name":"Standalone Line Endings","data":{},"expected":"|\r\n>|","template":"|\r\n{{>partial}}\r\n|","desc":"\"\\r\\n\" should be considered a newline for standalone tags.","partials":{"partial":">"}},{"name":"Standalone Without Previous Line","data":{},"expected":" >\n >>","template":" {{>partial}}\n>","desc":"Standalone tags should not require a newline to precede them.","partials":{"partial":">\n>"}},{"name":"Standalone Without Newline","data":{},"expected":">\n >\n >","template":">\n {{>partial}}","desc":"Standalone tags should not require a newline to follow them.","partials":{"partial":">\n>"}},{"name":"Standalone Indentation","data":{"content":"<\n->"},"expected":"\\\n |\n <\n->\n |\n/\n","template":"\\\n {{>partial}}\n/\n","desc":"Each line of the partial should be indented before rendering.","partials":{"partial":"|\n{{{content}}}\n|\n"}},{"name":"Padding Whitespace","data":{"boolean":true},"expected":"|[]|","template":"|{{> partial }}|","desc":"Superfluous in-tag whitespace should be ignored.","partials":{"partial":"[]"}}]}

1
test/specs/sections.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,10 @@
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
#include "json.hpp"
#include "mstch/mstch.hpp"
#include "test_data.hpp"
#include "specs_data.hpp"
using namespace mstchtest;
@ -14,6 +16,23 @@ using namespace mstchtest;
REQUIRE(x ## _txt == mstch::render(x ## _mustache, x ## _data)); \
}
#define SPECS_TEST(x) TEST_CASE("specs_" #x) { \
using boost::get; \
auto data = json::parse<mstch::node,mstch::map,mstch::array>(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))); \
SECTION(get<std::string>(test["name"])) \
REQUIRE(mstch::render( \
get<std::string>(test["template"]), \
test["data"], partials) == \
get<std::string>(test["expected"])); \
} \
}
MSTCH_TEST(ampersand_escape)
MSTCH_TEST(apostrophe)
MSTCH_TEST(array_of_strings)
@ -72,3 +91,10 @@ 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)