correct parser

This commit is contained in:
pantor
2017-08-13 22:51:38 +02:00
parent d0eeb111ea
commit bd46aa2e99
5 changed files with 564 additions and 187 deletions

View File

@@ -1,3 +1,6 @@
# Inja
A Template Engine for Modern C++
A Template Engine for Modern C++
[![Github Issues](https://img.shields.io/github/issues/pantor/inja.svg)](http://github.com/pantor/inja/issues)
[![GitHub License](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/pantor/inja/master/LICENSE.MIT)

View File

@@ -12,151 +12,402 @@ using json = nlohmann::json;
using string = std::string;
template<class BidirIt, class Traits, class CharT, class UnaryFunction>
std::basic_string<CharT> regex_replace(BidirIt first, BidirIt last,
const std::basic_regex<CharT,Traits>& re, UnaryFunction f)
{
std::basic_string<CharT> s;
typename std::match_results<BidirIt>::difference_type positionOfLastMatch = 0;
auto endOfLastMatch = first;
auto callback = [&](const std::match_results<BidirIt>& match)
string join_strings(std::vector<string> vector, string delimiter) {
std::stringstream ss;
for (size_t i = 0; i < vector.size(); ++i)
{
auto positionOfThisMatch = match.position(0);
auto diff = positionOfThisMatch - positionOfLastMatch;
auto startOfThisMatch = endOfLastMatch;
std::advance(startOfThisMatch, diff);
s.append(endOfLastMatch, startOfThisMatch);
s.append(f(match));
auto lengthOfMatch = match.length(0);
positionOfLastMatch = positionOfThisMatch + lengthOfMatch;
endOfLastMatch = startOfThisMatch;
std::advance(endOfLastMatch, lengthOfMatch);
};
std::sregex_iterator begin(first, last, re), end;
std::for_each(begin, end, callback);
s.append(endOfLastMatch, last);
return s;
if (i != 0) ss << delimiter;
ss << vector[i];
}
return ss.str();
}
template<class Traits, class CharT, class UnaryFunction>
std::string regex_replace(const std::string& s,
const std::basic_regex<CharT,Traits>& re, UnaryFunction f)
{
return regex_replace(s.cbegin(), s.cend(), re, f);
struct SearchMatch {
SearchMatch() { }
SearchMatch(std::smatch match, size_t offset): SearchMatch(match, offset, 1, -1) { }
SearchMatch(std::smatch match, size_t offset, int inner_group, int regex_number): inner_group(inner_group), regex_number(regex_number) {
position = offset + match.position();
length = match.length();
end_position = position + length;
found = !match.empty();
outer = match[0].str();
inner = match[inner_group].str();
prefix = match.prefix();
suffix = match.suffix();
}
string outer, inner, prefix, suffix;
size_t position, end_position;
int length;
bool found = false;
int regex_number = -1, inner_group = 1;
};
struct SearchClosedMatch {
SearchClosedMatch() { }
SearchClosedMatch(string input, SearchMatch open_match, SearchMatch close_match): open_match(open_match), close_match(close_match) {
position = open_match.position;
length = close_match.end_position - open_match.position;
end_position = close_match.end_position;
found = open_match.found && close_match.found;
outer = input.substr(position, length);
inner = input.substr(open_match.end_position, close_match.position - open_match.end_position);
prefix = open_match.prefix;
suffix = close_match.suffix;
}
string outer, inner, prefix, suffix;
size_t position, end_position;
int length;
bool found = false;
SearchMatch open_match, close_match;
};
SearchMatch search(string input, std::regex regex, size_t position) {
auto first = input.cbegin();
auto last = input.cend();
if (position >= input.length()) {
return SearchMatch();
}
std::smatch match;
std::regex_search(first + position, last, match, regex);
return SearchMatch(match, position);
}
SearchMatch search(string input, std::vector<string> regex_patterns, size_t position) {
auto first = input.cbegin();
auto last = input.cend();
string regex_pattern = "(" + join_strings(regex_patterns, ")|(") + ")";
std::regex regex(regex_pattern);
if (position >= input.length()) {
return SearchMatch();
}
// Vector of id vs groups
std::vector<int> regex_mark_counts;
for (int i = 0; i < regex_patterns.size(); i++) {
for (int j = 0; j < std::regex(regex_patterns[i]).mark_count() + 1; j++) {
regex_mark_counts.push_back(i);
}
}
std::smatch match;
std::regex_search(first + position, last, match, regex);
int number_regex = -1;
int number_inner = 1;
for (int i = 1; i < match.size(); i++) {
if (match.length(i) > 0) {
number_inner = i + 1;
number_regex = regex_mark_counts[i];
break;
}
}
return SearchMatch(match, position, number_inner, number_regex);
}
SearchClosedMatch search_on_level(string input, std::regex regex_statement, std::regex regex_level_up, std::regex regex_level_down, std::regex regex_search, SearchMatch open_match) {
int level = 0;
size_t current_position = open_match.end_position;
SearchMatch statement_match = search(input, regex_statement, current_position);
while (statement_match.found) {
current_position = statement_match.end_position;
if (level == 0 && std::regex_match(statement_match.inner, regex_search)) break;
if (std::regex_match(statement_match.inner, regex_level_up)) level += 1;
else if (std::regex_match(statement_match.inner, regex_level_down)) level -= 1;
statement_match = search(input, regex_statement, current_position);
}
return SearchClosedMatch(input, open_match, statement_match);
}
SearchClosedMatch search_close(string input, std::regex regex_statement, std::regex regex_open, std::regex regex_close, SearchMatch open_match) {
return search_on_level(input, regex_statement, regex_open, regex_close, regex_close, open_match);
}
class Environment {
std::regex regex_statement;
std::regex regex_line_statement;
std::regex regex_expression;
std::regex regex_comment;
std::vector<string> regex_pattern_delimiters;
public:
Environment() {
const string regex_pattern_statement = "\\(\\%\\s*(.+?)\\s*\\%\\)";
// const string regex_pattern_line_statement = "^## (.*)$";
const string regex_pattern_expression = "\\{\\{\\s*(.+?)\\s*\\}\\}";
const string regex_pattern_comment = "\\{#\\s*(.*?)\\s*#\\}";
regex_statement = std::regex(regex_pattern_statement);
// regex_line_statement = std::regex(regex_pattern_line_statement);
regex_expression = std::regex(regex_pattern_expression);
regex_comment = std::regex(regex_pattern_comment);
regex_pattern_delimiters = { regex_pattern_statement, regex_pattern_expression, regex_pattern_comment };
}
json get_variable_data(string variable_name, json data) {
// Json Raw Data
if ( json::accept(variable_name) ) {
return json::parse(variable_name);
}
// Implement range function
std::regex range_regex("^range\\((\\d+)\\)$");
std::smatch range_match;
if (std::regex_match(variable_name, range_match, range_regex)) {
int counter = std::stoi(range_match[1].str());
std::vector<int> range(counter);
std::iota(range.begin(), range.end(), 0);
return range;
}
if (variable_name[0] != '/') {
variable_name = "/" + variable_name;
}
json parse_level(string input) {
json result;
json::json_pointer ptr(variable_name);
json result = data[ptr];
if (result.is_null()) {
throw std::runtime_error("JSON pointer found no element.");
}
return result;
}
string render(string template_input, json data) {
return render(template_input, data, "./");
}
string render(string template_input, json data, string template_path) {
string result = template_input;
const std::regex include_regex("\\(\\% include \"(.*)\" \\%\\)");
result = inja::regex_replace(result, include_regex,
[this, template_path](const std::smatch& match) {
string filename = template_path + match[1].str();
return load_template(filename);
size_t current_position = 0;
SearchMatch statement_match = search(input, regex_pattern_delimiters, current_position);
while (statement_match.found) {
current_position = statement_match.end_position;
if (!statement_match.prefix.empty()) {
result.push_back({{"type", "string"}, {"text", statement_match.prefix}});
}
);
const std::regex loop_regex("\\(\\% for (\\w+) in (.+) \\%\\)(.*)\\(\\% endfor \\%\\)");
result = inja::regex_replace(result, loop_regex,
[this, data](const std::smatch& match) {
string result = "";
string entry_name = match[1].str();
string list_name = match[2].str();
string inner_string = match[3].str();
json list = get_variable_data(list_name, data);
if (!list.is_array()) throw std::runtime_error("JSON variable is not a list.");
// Regex matched a statement "(% ... %)"
if (statement_match.regex_number == 0) {
const std::regex regex_loop_open("for (.*)");
const std::regex regex_loop_close("endfor");
for (int i = 0; i < list.size(); i++) {
const std::regex entry_regex("\\{\\{.*" + entry_name + ".*\\}\\}");
result += inja::regex_replace(inner_string, entry_regex,
[list_name, i](const std::smatch& match) {
return "{{ " + list_name + "/" + std::to_string(i) + " }}";
}
);
const std::regex regex_include("include \"(.*)\"");
const std::regex regex_condition_open("if (.*)");
const std::regex regex_condition_else_if("else if (.*)");
const std::regex regex_condition_else("else");
const std::regex regex_condition_close("endif");
std::smatch inner_statement_match;
if (std::regex_match(statement_match.inner, inner_statement_match, regex_loop_open)) {
SearchClosedMatch loop_match = search_close(input, regex_statement, regex_loop_open, regex_loop_close, statement_match);
current_position = loop_match.end_position;
string loop_command = inner_statement_match[0].str();
result.push_back({{"type", "loop"}, {"command", loop_command}, {"inner", loop_match.inner}});
}
return result;
}
);
const std::regex condition_regex("\\(\\% if (\\w+) \\%\\)(.*)\\(\\% endif \\%\\)");
result = inja::regex_replace(result, condition_regex,
[this, data](const std::smatch& match) {
string condition_variable_name = match[1].str();
string inner_string = match[2].str();
if (get_variable_data(condition_variable_name, data)) {
return inner_string;
else if (std::regex_match(statement_match.inner, inner_statement_match, regex_include)) {
string include_command = inner_statement_match[0].str();
string filename = inner_statement_match[1].str();
result.push_back({{"type", "include"}, {"filename", filename}});
}
else if (std::regex_match(statement_match.inner, inner_statement_match, regex_condition_open)) {
string if_command = inner_statement_match[0].str();
json condition_result = {{"type", "condition"}, {"children", json::array()}};
SearchMatch condition_match = statement_match;
SearchClosedMatch else_if_match = search_on_level(input, regex_statement, regex_condition_open, regex_condition_close, regex_condition_else_if, condition_match);
while (else_if_match.found) {
condition_match = else_if_match.close_match;
condition_result["children"].push_back({{"type", "condition_branch"}, {"command", else_if_match.open_match.inner}, {"inner", else_if_match.inner}});
else_if_match = search_on_level(input, regex_statement, regex_condition_open, regex_condition_close, regex_condition_else_if, condition_match);
}
SearchClosedMatch else_match = search_on_level(input, regex_statement, regex_condition_open, regex_condition_close, regex_condition_else, condition_match);
if (else_match.found) {
condition_match = else_match.close_match;
condition_result["children"].push_back({{"type", "condition_branch"}, {"command", else_match.open_match.inner}, {"inner", else_match.inner}});
}
SearchClosedMatch last_if_match = search_close(input, regex_statement, regex_condition_open, regex_condition_close, condition_match);
condition_result["children"].push_back({{"type", "condition_branch"}, {"command", last_if_match.open_match.inner}, {"inner", last_if_match.inner}});
current_position = last_if_match.end_position;
result.push_back(condition_result);
}
return string("");
}
);
const std::regex variable_regex("\\{\\{\\s*([^\\}]*[^\\s])\\s*\\}\\}");
result = inja::regex_replace(result, variable_regex,
[this, data](const std::smatch& match) {
string variable_name = match[1].str();
return get_variable_data(variable_name, data);
// Regex matched an expression "{{ ... }}"
else if (statement_match.regex_number == 1) {
result.push_back({{"type", "variable"}, {"command", statement_match.inner}});
}
);
// Regex matched an comment "{# ... #}"
else if (statement_match.regex_number == 2) {
result.push_back({{"type", "comment"}, {"text", statement_match.inner}});
}
statement_match = search(input, regex_pattern_delimiters, current_position);
}
if (current_position < input.length()) {
result.push_back({{"type", "string"}, {"text", input.substr(current_position)}});
}
return result;
}
json parse_tree(json current_element) {
if (current_element.find("inner") != current_element.end()) {
current_element["children"] = parse_level(current_element["inner"]);
current_element.erase("inner");
}
if (current_element.find("children") != current_element.end()) {
for (auto& child: current_element["children"]) {
child = parse_tree(child);
}
}
return current_element;
}
json parse(string input) {
return parse_tree({{"inner", input}})["children"];
}
json parse_variable(string input, json data) {
// Json Raw Data
if ( json::accept(input) ) {
return json::parse(input);
}
// TODO Implement filter and functions
if (input[0] != '/') input.insert(0, "/");
json::json_pointer ptr(input);
json result = data[ptr];
if (result.is_null()) throw std::runtime_error("JSON pointer found no element.");
return result;
}
bool parse_condition(string condition, json data) {
const std::regex regex_condition_equal("(.*) == (.*)");
const std::regex regex_condition_greater("(.*) > (.*)");
const std::regex regex_condition_less("(.*) < (.*)");
const std::regex regex_condition_greater_equal("(.*) >= (.*)");
const std::regex regex_condition_less_equal("(.*) <= (.*)");
const std::regex regex_condition_different("(.*) != (.*)");
const std::regex regex_condition_in("(.*) in (.*)");
std::smatch match_condition;
if (std::regex_match(condition, match_condition, regex_condition_equal)) {
json comp1 = parse_variable(match_condition[1].str(), data);
json comp2 = parse_variable(match_condition[2].str(), data);
return comp1 == comp2;
} else if (std::regex_match(condition, match_condition, regex_condition_greater)) {
json comp1 = parse_variable(match_condition[1].str(), data);
json comp2 = parse_variable(match_condition[2].str(), data);
return comp1 > comp2;
} else if (std::regex_match(condition, match_condition, regex_condition_less)) {
json comp1 = parse_variable(match_condition[1].str(), data);
json comp2 = parse_variable(match_condition[2].str(), data);
return comp1 < comp2;
} else if (std::regex_match(condition, match_condition, regex_condition_greater_equal)) {
json comp1 = parse_variable(match_condition[1].str(), data);
json comp2 = parse_variable(match_condition[2].str(), data);
return comp1 >= comp2;
} else if (std::regex_match(condition, match_condition, regex_condition_less_equal)) {
json comp1 = parse_variable(match_condition[1].str(), data);
json comp2 = parse_variable(match_condition[2].str(), data);
return comp1 <= comp2;
} else if (std::regex_match(condition, match_condition, regex_condition_different)) {
json comp1 = parse_variable(match_condition[1].str(), data);
json comp2 = parse_variable(match_condition[2].str(), data);
return comp1 != comp2;
} else if (std::regex_match(condition, match_condition, regex_condition_in)) {
json item = parse_variable(match_condition[1].str(), data);
json list = parse_variable(match_condition[2].str(), data);
return (std::find(list.begin(), list.end(), item) != list.end());
}
try {
return parse_variable(condition, data);
}
catch (...) {
return false;
}
}
string render_json(json data) {
if (data.is_string()) {
return data;
} else {
std::stringstream ss;
ss << data;
return ss.str();
}
}
string render_tree(json input, json data, string path) {
string result = "";
for (auto element: input) {
if (element["type"] == "string") {
result += element["text"];
}
else if (element["type"] == "variable") {
json variable = parse_variable(element["command"], data);
result += render_json(variable);
}
else if (element["type"] == "include") {
result += render_template(path + element["filename"].get<string>(), data);
}
else if (element["type"] == "loop") {
const std::regex regex_loop_list("for (\\w+) in (.+)");
string command = element["command"].get<string>();
std::smatch match_command;
if (std::regex_match(command, match_command, regex_loop_list)) {
string item_name = match_command[1].str();
string list_name = match_command[2].str();
json list = parse_variable(list_name, data);
for (int i = 0; i < list.size(); i++) {
json data_loop = data;
data_loop[item_name] = list[i];
data_loop["index"] = i;
data_loop["is_first"] = (i == 0);
data_loop["is_last"] = (i == list.size() - 1);
result += render_tree(element["children"], data_loop, path);
}
}
}
else if (element["type"] == "condition") {
const std::regex regex_condition("(if|else if|else) ?(.*)");
json branches = element["children"];
for (auto branch: branches) {
string command = branch["command"].get<string>();
std::smatch match_command;
if (std::regex_match(command, match_command, regex_condition)) {
string condition_type = match_command[1].str();
string condition = match_command[2].str();
if (parse_condition(condition, data) || condition_type == "else") {
result += render_tree(branch["children"], data, path);
break;
}
}
}
}
}
return result;
}
string render(string input, json data) {
return render(input, data, "./");
}
string render(string input, json data, string path) {
json parsed = parse(input);
return render_tree(parsed, data, path);
}
string render_template(string filename, json data) {
string text = load_template(filename);
string path = filename.substr(0, filename.find_last_of("/\\") + 1); // Include / itself

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2013-2017 Niels Lohmann
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Binary file not shown.

View File

@@ -8,71 +8,215 @@ using Environment = inja::Environment;
using json = nlohmann::json;
TEST_CASE("Variables") {
TEST_CASE("String functions") {
SECTION("Vector join") {
REQUIRE( inja::join_strings({"1", "2", "3"}, ",") == "1,2,3" );
REQUIRE( inja::join_strings({"1", "2", "3", "4", "5"}, " ") == "1 2 3 4 5" );
REQUIRE( inja::join_strings({}, " ") == "" );
REQUIRE( inja::join_strings({"single"}, "---") == "single" );
}
SECTION("Basic search") {
inja::SearchMatch match = inja::search("lorem ipsum dolor it", std::regex("i(.*)m"), 0);
inja::SearchMatch match_position = inja::search("lorem ipsum dolor it", std::regex("i(.*)m"), 8);
REQUIRE( match.found == true );
REQUIRE( match.position == 6 );
REQUIRE( match.length == 5 );
REQUIRE( match.end_position == 11 );
REQUIRE( match.outer == "ipsum" );
REQUIRE( match.inner == "psu" );
REQUIRE( match_position.found == false );
}
SECTION("Vector search") {
std::vector<std::string> regex_patterns = { "tras", "do(\\w*)or", "es(\\w*)as", "ip(\\w*)um" };
inja::SearchMatch match = inja::search("lorem ipsum dolor amit estas tronum.", regex_patterns, 0);
std::vector<std::string> regex_patterns_rearranged = { "tras", "ip(\\w*)um", "do(\\w*)or", "es(\\w*)as" };
inja::SearchMatch match_rearranged = inja::search("lorem ipsum dolor amit estas tronum.", regex_patterns_rearranged, 0);
REQUIRE( match.regex_number == 3 );
REQUIRE( match.outer == "ipsum" );
REQUIRE( match.inner == "s" );
REQUIRE( match_rearranged.regex_number == 1 );
REQUIRE( match_rearranged.outer == "ipsum" );
REQUIRE( match_rearranged.inner == "s" );
}
}
TEST_CASE("Parser") {
Environment env = Environment();
SECTION("Basic") {
std::string test = "asdf";
json result = {{{"type", "string"}, {"text", "asdf"}}};
REQUIRE( env.parse(test) == result );
}
SECTION("Variables") {
std::string test = "{{ name }}";
json result = {{{"type", "variable"}, {"command", "name"}}};
REQUIRE( env.parse(test) == result );
std::string test_combined = "Hello {{ name }}!";
json result_combined = {
{{"type", "string"}, {"text", "Hello "}},
{{"type", "variable"}, {"command", "name"}},
{{"type", "string"}, {"text", "!"}}
};
REQUIRE( env.parse(test_combined) == result_combined );
std::string test_multiple = "Hello {{ name }}! I come from {{ city }}.";
json result_multiple = {
{{"type", "string"}, {"text", "Hello "}},
{{"type", "variable"}, {"command", "name"}},
{{"type", "string"}, {"text", "! I come from "}},
{{"type", "variable"}, {"command", "city"}},
{{"type", "string"}, {"text", "."}}
};
REQUIRE( env.parse(test_multiple) == result_multiple );
}
SECTION("Loops") {
std::string test = "open (% for e in list %)lorem(% endfor %) closing";
json result = {
{{"type", "string"}, {"text", "open "}},
{{"type", "loop"}, {"command", "for e in list"}, {"children", {
{{"type", "string"}, {"text", "lorem"}}
}}},
{{"type", "string"}, {"text", " closing"}}
};
std::string test_nested = "(% for e in list %)(% for b in list2 %)lorem(% endfor %)(% endfor %)";
json result_nested = {
{{"type", "loop"}, {"command", "for e in list"}, {"children", {
{{"type", "loop"}, {"command", "for b in list2"}, {"children", {
{{"type", "string"}, {"text", "lorem"}}
}}}
}}}
};
REQUIRE( env.parse(test) == result );
REQUIRE( env.parse(test_nested) == result_nested );
}
SECTION("Conditionals") {
std::string test = "(% if true %)dfgh(% endif %)";
json result = {
{{"type", "condition"}, {"children", {
{{"type", "condition_branch"}, {"command", "if true"}, {"children", {
{{"type", "string"}, {"text", "dfgh"}}
}}}
}}}
};
std::string test2 = "if: (% if maybe %)first if(% else if perhaps %)first else if(% else if sometimes %)second else if(% else %)test else(% endif %)";
json result2 = {
{{"type", "string"}, {"text", "if: "}},
{{"type", "condition"}, {"children", {
{{"type", "condition_branch"}, {"command", "if maybe"}, {"children", {
{{"type", "string"}, {"text", "first if"}}
}}},
{{"type", "condition_branch"}, {"command", "else if perhaps"}, {"children", {
{{"type", "string"}, {"text", "first else if"}}
}}},
{{"type", "condition_branch"}, {"command", "else if sometimes"}, {"children", {
{{"type", "string"}, {"text", "second else if"}}
}}},
{{"type", "condition_branch"}, {"command", "else"}, {"children", {
{{"type", "string"}, {"text", "test else"}}
}}},
}}}
};
REQUIRE( env.parse(test) == result );
REQUIRE( env.parse(test2) == result2 );
}
json data;
data["name"] = "Peter";
data["city"] = "Washington D.C.";
data["age"] = 29;
data["names"] = {"Jeff", "Seb"};
data["brother"]["name"] = "Chris";
data["brother"]["daughters"] = {"Maria", "Helen"};
data["brother"]["daughter0"] = { { "name", "Maria" } };
SECTION("Variables from values") {
REQUIRE( env.get_variable_data("42", data) == 42 );
REQUIRE( env.get_variable_data("3.1415", data) == 3.1415 );
REQUIRE( env.get_variable_data("\"hello\"", data) == "hello" );
REQUIRE( env.parse_variable("42", data) == 42 );
REQUIRE( env.parse_variable("3.1415", data) == 3.1415 );
REQUIRE( env.parse_variable("\"hello\"", data) == "hello" );
}
SECTION("Variables from functions") {
REQUIRE( env.get_variable_data("range(3)", data) == std::vector<int>({0, 1, 2}) );
}
SECTION("Variables from JSON data") {
REQUIRE( env.get_variable_data("name", data) == "Peter" );
REQUIRE( env.get_variable_data("names/1", data) == "Seb" );
REQUIRE( env.get_variable_data("brother/name", data) == "Chris" );
REQUIRE( env.get_variable_data("brother/daughters/0", data) == "Maria" );
REQUIRE_THROWS_WITH( env.get_variable_data("noelement", data), "JSON pointer found no element." );
}
SECTION("Variables should be rendered") {
REQUIRE( env.render("My name is...", data) == "My name is..." );
REQUIRE( env.render("My name is {{ name }}.", data) == "My name is Peter." );
REQUIRE( env.render("My name is {{name}}.", data) == "My name is Peter." );
REQUIRE( env.render("My name is {{ name }}. I come from {{ city }}", data) == "My name is Peter. I come from Washington D.C." );
REQUIRE( env.render("My name is {{ names/1 }}.", data) == "My name is Seb." );
REQUIRE( env.render("My name is {{ brother/name }}.", data) == "My name is Chris." );
REQUIRE( env.render("My name is {{ brother/daughter0/name }}.", data) == "My name is Maria." );
REQUIRE( env.parse_variable("name", data) == "Peter" );
REQUIRE( env.parse_variable("age", data) == 29 );
REQUIRE( env.parse_variable("names/1", data) == "Seb" );
REQUIRE( env.parse_variable("brother/name", data) == "Chris" );
REQUIRE( env.parse_variable("brother/daughters/0", data) == "Maria" );
REQUIRE_THROWS_WITH( env.parse_variable("noelement", data), "JSON pointer found no element." );
}
}
TEST_CASE("Loops should be rendered") {
TEST_CASE("Render") {
Environment env = Environment();
json data;
data["list"] = {"v1", "v2", "v3", "v4"};
data["name"] = "Peter";
data["city"] = "Brunswick";
data["age"] = 29;
data["names"] = {"Jeff", "Seb"};
data["brother"]["name"] = "Chris";
data["brother"]["daughters"] = {"Maria", "Helen"};
data["brother"]["daughter0"] = { { "name", "Maria" } };
data["is_happy"] = true;
SECTION("Basic") {
REQUIRE( env.render("Hello World!", data) == "Hello World!" );
REQUIRE( env.render("", data, "../") == "" );
}
REQUIRE( env.render("List: (% for entry in list %)a(% endfor %)", data) == "List: aaaa" );
REQUIRE( env.render("List: (% for entry in list %){{ entry }}, (% endfor %)", data) == "List: v1, v2, v3, v4, " );
REQUIRE( env.render("List: (% for i in range(4) %)a(% endfor %)", data) == "List: aaaa" );
SECTION("Variables") {
REQUIRE( env.render("Hello {{ name }}!", data) == "Hello Peter!" );
REQUIRE( env.render("{{ name }}", data) == "Peter" );
REQUIRE( env.render("{{name}}", data) == "Peter" );
REQUIRE( env.render("{{ name }} is {{ age }} years old.", data) == "Peter is 29 years old." );
REQUIRE( env.render("Hello {{ name }}! I come from {{ city }}.", data) == "Hello Peter! I come from Brunswick." );
REQUIRE( env.render("Hello {{ names/1 }}!", data) == "Hello Seb!" );
REQUIRE( env.render("Hello {{ brother/name }}!", data) == "Hello Chris!" );
REQUIRE( env.render("Hello {{ brother/daughter0/name }}!", data) == "Hello Maria!" );
}
SECTION("Comments") {
REQUIRE( env.render("Hello{# This is a comment #}!", data) == "Hello!" );
REQUIRE( env.render("{# --- #Todo --- #}", data) == "" );
}
SECTION("Loops") {
REQUIRE( env.render("Hello (% for name in names %){{ name }} (% endfor %)!", data) == "Hello Jeff Seb !" );
REQUIRE( env.render("Hello (% for name in names %){{ index }}: {{ name }}, (% endfor %)!", data) == "Hello 0: Jeff, 1: Seb, !" );
}
SECTION("Conditionals") {
REQUIRE( env.render("(% if is_happy %)Yeah!(% endif %)", data) == "Yeah!" );
REQUIRE( env.render("(% if is_sad %)Yeah!(% endif %)", data) == "" );
REQUIRE( env.render("(% if is_sad %)Yeah!(% else %)Nooo...(% endif %)", data) == "Nooo..." );
REQUIRE( env.render("(% if age == 29 %)Right(% else %)Wrong(% endif %)", data) == "Right" );
REQUIRE( env.render("(% if age > 29 %)Right(% else %)Wrong(% endif %)", data) == "Wrong" );
REQUIRE( env.render("(% if age <= 29 %)Right(% else %)Wrong(% endif %)", data) == "Right" );
REQUIRE( env.render("(% if age != 28 %)Right(% else %)Wrong(% endif %)", data) == "Right" );
REQUIRE( env.render("(% if age >= 30 %)Right(% else %)Wrong(% endif %)", data) == "Wrong" );
REQUIRE( env.render("(% if age in [28, 29, 30] %)True(% endif %)", data) == "True" );
REQUIRE( env.render("(% if name in [\"Simon\", \"Tom\"] %)Test1(% else if name in [\"Peter\"] %)Test2(% else %)Test3(% endif %)", data) == "Test2" );
}
}
TEST_CASE("Conditionals should be rendered") {
Environment env = Environment();
json data;
data["good"] = true;
data["bad"] = false;
data["a"] = 2;
REQUIRE( env.render("(% if good %)a(% endif %)", data) == "a" );
// REQUIRE( env.render("(% if good %)a(% endif %) (% if bad %)b(% endif %)", data) == "a " );
// REQUIRE( env.render("(% if good %)one(% else %)two(% endif %)", data) == "one" );
// REQUIRE( env.render("(% if bad %)one(% else %)two(% endif %)", data) == "two" );
// REQUIRE( env.render("(% if a == 2 %)one(% else %)two(% endif %)", data) == "one" );
// REQUIRE( env.render("(% if "b" in {"a", "b", "c"} %)one(% endif %)", data) == "one" );
}
TEST_CASE("Files should handled") {
TEST_CASE("Files") {
Environment env = Environment();
json data;
data["name"] = "Jeff";