mirror of
https://github.com/pantor/inja.git
synced 2026-03-12 12:11:24 +00:00
type enum, clode cleaning, readme tables
This commit is contained in:
41
README.md
41
README.md
@@ -69,19 +69,39 @@ Statements can be written with the `(% ... %)` syntax. The most important statem
|
||||
|
||||
#### Loops
|
||||
|
||||
```c++
|
||||
json data;
|
||||
data["guests"] = { "Jeff", "Pierre", "Tom" };
|
||||
|
||||
render("""Guests:
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Template</th>
|
||||
<th>Json</th>
|
||||
<th>Result</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<pre lang="txt">
|
||||
Guests:
|
||||
(% for guest in guests %)- {{ guest }}
|
||||
(% endfor %)""", data);
|
||||
/* Guests:
|
||||
(% endfor %)
|
||||
</pre>
|
||||
</td>
|
||||
<td>
|
||||
<pre lang="json">
|
||||
{
|
||||
"guests: ["Jeff", "Pierre", "Tom"];
|
||||
}
|
||||
</pre>
|
||||
</td>
|
||||
<td>
|
||||
<pre lang="txt">
|
||||
Guests:
|
||||
- Jeff
|
||||
- Pierre
|
||||
- Tom
|
||||
*/
|
||||
```
|
||||
</pre>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
In the loop, some special variables are available:
|
||||
- int index
|
||||
@@ -113,3 +133,6 @@ Currently, the following compilers are tested:
|
||||
## License
|
||||
|
||||
The class is licensed under the [MIT License](https://raw.githubusercontent.com/pantor/inja/master/LICENSE).
|
||||
|
||||
|
||||
|
||||
|
||||
210
src/inja.hpp
210
src/inja.hpp
@@ -1,10 +1,10 @@
|
||||
/*
|
||||
Inja - A Template Engine for Modern C++
|
||||
*/
|
||||
|
||||
#ifndef PANTOR_INJA_HPP
|
||||
#define PANTOR_INJA_HPP
|
||||
|
||||
#ifndef NLOHMANN_JSON_HPP
|
||||
static_assert(false, "nlohmann/json not found.");
|
||||
#endif
|
||||
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
@@ -12,11 +12,6 @@
|
||||
#include <regex>
|
||||
|
||||
|
||||
#ifndef NLOHMANN_JSON_HPP
|
||||
static_assert(false, "nlohmann/json not found.");
|
||||
#endif
|
||||
|
||||
|
||||
namespace inja {
|
||||
|
||||
using json = nlohmann::json;
|
||||
@@ -33,34 +28,43 @@ inline string join_strings(std::vector<string> vector, string delimiter) {
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
SearchMatch(std::smatch match, size_t offset): match(match), offset(offset) {
|
||||
position = offset + match.position();
|
||||
length = match.length();
|
||||
end_position = position + length;
|
||||
found = !match.empty();
|
||||
outer = match[0].str();
|
||||
inner = match[inner_group].str();
|
||||
outer = match.str(0);
|
||||
inner = match.str(1);
|
||||
prefix = match.prefix();
|
||||
suffix = match.suffix();
|
||||
}
|
||||
|
||||
std::smatch match;
|
||||
size_t offset;
|
||||
|
||||
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() { }
|
||||
struct SearchMatchVector: public SearchMatch {
|
||||
SearchMatchVector(): SearchMatch() { }
|
||||
|
||||
SearchClosedMatch(string input, SearchMatch open_match, SearchMatch close_match): open_match(open_match), close_match(close_match) {
|
||||
SearchMatchVector(std::smatch match, size_t offset, int inner_group, int regex_number): SearchMatch(match, offset), regex_number(regex_number) {
|
||||
inner = match.str(inner_group);
|
||||
}
|
||||
|
||||
int regex_number = -1;
|
||||
};
|
||||
|
||||
struct SearchClosedMatch: public SearchMatch {
|
||||
SearchClosedMatch(): SearchMatch() { }
|
||||
|
||||
SearchClosedMatch(string input, SearchMatch open_match, SearchMatch close_match): SearchMatch(), 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;
|
||||
@@ -71,11 +75,6 @@ struct SearchClosedMatch {
|
||||
suffix = close_match.suffix;
|
||||
}
|
||||
|
||||
string outer, inner, prefix, suffix;
|
||||
size_t position, end_position;
|
||||
int length;
|
||||
bool found = false;
|
||||
|
||||
SearchMatch open_match, close_match;
|
||||
};
|
||||
|
||||
@@ -92,17 +91,7 @@ inline SearchMatch search(string input, std::regex regex, size_t position) {
|
||||
return SearchMatch(match, position);
|
||||
}
|
||||
|
||||
inline 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();
|
||||
}
|
||||
|
||||
inline SearchMatchVector search(string input, std::vector<string> regex_patterns, size_t position) {
|
||||
// Vector of id vs groups
|
||||
std::vector<int> regex_mark_counts;
|
||||
for (int i = 0; i < regex_patterns.size(); i++) {
|
||||
@@ -110,7 +99,17 @@ inline SearchMatch search(string input, std::vector<string> regex_patterns, size
|
||||
regex_mark_counts.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
string regex_pattern = "(" + join_strings(regex_patterns, ")|(") + ")";
|
||||
std::regex regex(regex_pattern);
|
||||
|
||||
auto first = input.cbegin();
|
||||
auto last = input.cend();
|
||||
|
||||
if (position >= input.length()) {
|
||||
return SearchMatchVector();
|
||||
}
|
||||
|
||||
std::smatch match;
|
||||
std::regex_search(first + position, last, match, regex);
|
||||
|
||||
@@ -124,10 +123,10 @@ inline SearchMatch search(string input, std::vector<string> regex_patterns, size
|
||||
}
|
||||
}
|
||||
|
||||
return SearchMatch(match, position, number_inner, number_regex);
|
||||
return SearchMatchVector(match, position, number_inner, number_regex);
|
||||
}
|
||||
|
||||
inline 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) {
|
||||
inline SearchClosedMatch search_closed_match_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;
|
||||
@@ -145,13 +144,23 @@ inline SearchClosedMatch search_on_level(string input, std::regex regex_statemen
|
||||
return SearchClosedMatch(input, open_match, statement_match);
|
||||
}
|
||||
|
||||
inline 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);
|
||||
inline SearchClosedMatch search_closed_match(string input, std::regex regex_statement, std::regex regex_open, std::regex regex_close, SearchMatch open_match) {
|
||||
return search_closed_match_on_level(input, regex_statement, regex_open, regex_close, regex_close, open_match);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
namespace Parser {
|
||||
enum Type {
|
||||
String,
|
||||
Loop,
|
||||
Condition,
|
||||
ConditionBranch,
|
||||
Include,
|
||||
Comment,
|
||||
Variable
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
class Environment {
|
||||
std::regex regex_statement;
|
||||
@@ -168,12 +177,12 @@ public:
|
||||
|
||||
Environment(string global_path): global_path(global_path) {
|
||||
const string regex_pattern_statement = "\\(\\%\\s*(.+?)\\s*\\%\\)";
|
||||
// const string regex_pattern_line_statement = "^## (.*)$";
|
||||
const string regex_pattern_line_statement = "^##\\s*(.+)\\s*$";
|
||||
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_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 };
|
||||
@@ -184,11 +193,11 @@ public:
|
||||
json result;
|
||||
|
||||
size_t current_position = 0;
|
||||
SearchMatch statement_match = search(input, regex_pattern_delimiters, current_position);
|
||||
SearchMatchVector 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}});
|
||||
result += {{"type", Parser::Type::String}, {"text", statement_match.prefix}};
|
||||
}
|
||||
|
||||
// Regex matched a statement "(% ... %)"
|
||||
@@ -206,63 +215,63 @@ public:
|
||||
std::smatch inner_statement_match;
|
||||
// Loop
|
||||
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);
|
||||
SearchClosedMatch loop_match = search_closed_match(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}});
|
||||
string loop_command = inner_statement_match.str(0);
|
||||
result += {{"type", Parser::Type::Loop}, {"command", loop_command}, {"inner", loop_match.inner}};
|
||||
}
|
||||
// Include
|
||||
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}});
|
||||
string include_command = inner_statement_match.str(0);
|
||||
string filename = inner_statement_match.str(1);
|
||||
result += {{"type", Parser::Type::Include}, {"filename", filename}};
|
||||
}
|
||||
// Condition
|
||||
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()}};
|
||||
string if_command = inner_statement_match.str(0);
|
||||
json condition_result = {{"type", Parser::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);
|
||||
SearchClosedMatch else_if_match = search_closed_match_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}});
|
||||
condition_result["children"] += {{"type", Parser::Type::ConditionBranch}, {"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);
|
||||
else_if_match = search_closed_match_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);
|
||||
SearchClosedMatch else_match = search_closed_match_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}});
|
||||
condition_result["children"] += {{"type", Parser::Type::ConditionBranch}, {"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);
|
||||
SearchClosedMatch last_if_match = search_closed_match(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}});
|
||||
condition_result["children"] += {{"type", Parser::Type::ConditionBranch}, {"command", last_if_match.open_match.inner}, {"inner", last_if_match.inner}};
|
||||
|
||||
current_position = last_if_match.end_position;
|
||||
result.push_back(condition_result);
|
||||
result += condition_result;
|
||||
}
|
||||
}
|
||||
// Regex matched an expression "{{ ... }}"
|
||||
else if (statement_match.regex_number == 1) {
|
||||
result.push_back({{"type", "variable"}, {"command", statement_match.inner}});
|
||||
result += {{"type", Parser::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}});
|
||||
result += {{"type", Parser::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)}});
|
||||
result += {{"type", Parser::Type::String}, {"text", input.substr(current_position)}};
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -304,6 +313,7 @@ public:
|
||||
}
|
||||
|
||||
bool parse_condition(string condition, json data) {
|
||||
const std::regex regex_condition_not("not (.*)");
|
||||
const std::regex regex_condition_equal("(.*) == (.*)");
|
||||
const std::regex regex_condition_greater("(.*) > (.*)");
|
||||
const std::regex regex_condition_less("(.*) < (.*)");
|
||||
@@ -314,32 +324,41 @@ public:
|
||||
|
||||
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 !parse_condition(match_condition.str(1), data);
|
||||
}
|
||||
else if (std::regex_match(condition, match_condition, regex_condition_equal)) {
|
||||
json comp1 = parse_variable(match_condition.str(1), data);
|
||||
json comp2 = parse_variable(match_condition.str(2), 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);
|
||||
}
|
||||
else if (std::regex_match(condition, match_condition, regex_condition_greater)) {
|
||||
json comp1 = parse_variable(match_condition.str(1), data);
|
||||
json comp2 = parse_variable(match_condition.str(2), 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);
|
||||
}
|
||||
else if (std::regex_match(condition, match_condition, regex_condition_less)) {
|
||||
json comp1 = parse_variable(match_condition.str(1), data);
|
||||
json comp2 = parse_variable(match_condition.str(2), 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);
|
||||
}
|
||||
else if (std::regex_match(condition, match_condition, regex_condition_greater_equal)) {
|
||||
json comp1 = parse_variable(match_condition.str(1), data);
|
||||
json comp2 = parse_variable(match_condition.str(2), 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);
|
||||
}
|
||||
else if (std::regex_match(condition, match_condition, regex_condition_less_equal)) {
|
||||
json comp1 = parse_variable(match_condition.str(1), data);
|
||||
json comp2 = parse_variable(match_condition.str(2), 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);
|
||||
}
|
||||
else if (std::regex_match(condition, match_condition, regex_condition_different)) {
|
||||
json comp1 = parse_variable(match_condition.str(1), data);
|
||||
json comp2 = parse_variable(match_condition.str(2), 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);
|
||||
}
|
||||
else if (std::regex_match(condition, match_condition, regex_condition_in)) {
|
||||
json item = parse_variable(match_condition.str(1), data);
|
||||
json list = parse_variable(match_condition.str(2), data);
|
||||
return (std::find(list.begin(), list.end(), item) != list.end());
|
||||
}
|
||||
|
||||
@@ -364,24 +383,24 @@ public:
|
||||
string render_tree(json input, json data, string path) {
|
||||
string result = "";
|
||||
for (auto element: input) {
|
||||
if (element["type"] == "string") {
|
||||
if (element["type"] == Parser::Type::String) {
|
||||
result += element["text"];
|
||||
}
|
||||
else if (element["type"] == "variable") {
|
||||
else if (element["type"] == Parser::Type::Variable) {
|
||||
json variable = parse_variable(element["command"], data);
|
||||
result += render_json(variable);
|
||||
}
|
||||
else if (element["type"] == "include") {
|
||||
else if (element["type"] == Parser::Type::Include) {
|
||||
result += render_template(path + element["filename"].get<string>(), data);
|
||||
}
|
||||
else if (element["type"] == "loop") {
|
||||
else if (element["type"] == Parser::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();
|
||||
string item_name = match_command.str(1);
|
||||
string list_name = match_command.str(2);
|
||||
|
||||
json list = parse_variable(list_name, data);
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
@@ -394,7 +413,7 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element["type"] == "condition") {
|
||||
else if (element["type"] == Parser::Type::Condition) {
|
||||
const std::regex regex_condition("(if|else if|else) ?(.*)");
|
||||
|
||||
json branches = element["children"];
|
||||
@@ -403,8 +422,8 @@ public:
|
||||
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();
|
||||
string condition_type = match_command.str(1);
|
||||
string condition = match_command.str(2);
|
||||
|
||||
if (parse_condition(condition, data) || condition_type == "else") {
|
||||
result += render_tree(branch["children"], data, path);
|
||||
@@ -453,8 +472,7 @@ public:
|
||||
};
|
||||
|
||||
inline string render(string input, json data) {
|
||||
Environment env = Environment();
|
||||
return env.render(input, data);
|
||||
return Environment().render(input, data);
|
||||
}
|
||||
|
||||
} // namespace inja
|
||||
|
||||
@@ -13,15 +13,15 @@ TEST_CASE("files handling") {
|
||||
data["name"] = "Jeff";
|
||||
|
||||
SECTION("files should be loaded") {
|
||||
REQUIRE( env.load_file("../test/data/simple.txt") == "Hello {{ name }}." );
|
||||
CHECK( env.load_file("../test/data/simple.txt") == "Hello {{ name }}." );
|
||||
}
|
||||
|
||||
SECTION("files should be rendered") {
|
||||
REQUIRE( env.render_template("../test/data/simple.txt", data) == "Hello Jeff." );
|
||||
CHECK( env.render_template("../test/data/simple.txt", data) == "Hello Jeff." );
|
||||
}
|
||||
|
||||
SECTION("file includes should be rendered") {
|
||||
REQUIRE( env.render_template("../test/data/include.txt", data) == "Answer: Hello Jeff." );
|
||||
CHECK( env.render_template("../test/data/include.txt", data) == "Answer: Hello Jeff." );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ TEST_CASE("complete files") {
|
||||
|
||||
for (std::string test_name : {"simple-file", "nested"}) {
|
||||
SECTION(test_name) {
|
||||
REQUIRE( env.render_template_with_json_file(test_name + "/template.txt", test_name + "/data.json") == env.load_file(test_name + "/result.txt") );
|
||||
CHECK( env.render_template_with_json_file(test_name + "/template.txt", test_name + "/data.json") == env.load_file(test_name + "/result.txt") );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
using Environment = inja::Environment;
|
||||
using json = nlohmann::json;
|
||||
using Type = inja::Parser::Type;
|
||||
|
||||
|
||||
TEST_CASE("parser") {
|
||||
@@ -12,89 +13,89 @@ TEST_CASE("parser") {
|
||||
|
||||
SECTION("basic") {
|
||||
std::string test = "asdf";
|
||||
json result = {{{"type", "string"}, {"text", "asdf"}}};
|
||||
json result = {{{"type", Type::String}, {"text", "asdf"}}};
|
||||
|
||||
REQUIRE( env.parse(test) == result );
|
||||
CHECK( env.parse(test) == result );
|
||||
}
|
||||
|
||||
SECTION("variables") {
|
||||
std::string test = "{{ name }}";
|
||||
json result = {{{"type", "variable"}, {"command", "name"}}};
|
||||
REQUIRE( env.parse(test) == result );
|
||||
json result = {{{"type", Type::Variable}, {"command", "name"}}};
|
||||
CHECK( env.parse(test) == result );
|
||||
|
||||
std::string test_combined = "Hello {{ name }}!";
|
||||
json result_combined = {
|
||||
{{"type", "string"}, {"text", "Hello "}},
|
||||
{{"type", "variable"}, {"command", "name"}},
|
||||
{{"type", "string"}, {"text", "!"}}
|
||||
{{"type", Type::String}, {"text", "Hello "}},
|
||||
{{"type", Type::Variable}, {"command", "name"}},
|
||||
{{"type", Type::String}, {"text", "!"}}
|
||||
};
|
||||
REQUIRE( env.parse(test_combined) == result_combined );
|
||||
CHECK( 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", "."}}
|
||||
{{"type", Type::String}, {"text", "Hello "}},
|
||||
{{"type", Type::Variable}, {"command", "name"}},
|
||||
{{"type", Type::String}, {"text", "! I come from "}},
|
||||
{{"type", Type::Variable}, {"command", "city"}},
|
||||
{{"type", Type::String}, {"text", "."}}
|
||||
};
|
||||
REQUIRE( env.parse(test_multiple) == result_multiple );
|
||||
CHECK( 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", Type::String}, {"text", "open "}},
|
||||
{{"type", Type::Loop}, {"command", "for e in list"}, {"children", {
|
||||
{{"type", Type::String}, {"text", "lorem"}}
|
||||
}}},
|
||||
{{"type", "string"}, {"text", " closing"}}
|
||||
{{"type", 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"}}
|
||||
{{"type", Type::Loop}, {"command", "for e in list"}, {"children", {
|
||||
{{"type", Type::Loop}, {"command", "for b in list2"}, {"children", {
|
||||
{{"type", Type::String}, {"text", "lorem"}}
|
||||
}}}
|
||||
}}}
|
||||
};
|
||||
|
||||
REQUIRE( env.parse(test) == result );
|
||||
REQUIRE( env.parse(test_nested) == result_nested );
|
||||
CHECK( env.parse(test) == result );
|
||||
CHECK( 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"}}
|
||||
{{"type", Type::Condition}, {"children", {
|
||||
{{"type", Type::ConditionBranch}, {"command", "if true"}, {"children", {
|
||||
{{"type", 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", Type::String}, {"text", "if: "}},
|
||||
{{"type", Type::Condition}, {"children", {
|
||||
{{"type", Type::ConditionBranch}, {"command", "if maybe"}, {"children", {
|
||||
{{"type", Type::String}, {"text", "first if"}}
|
||||
}}},
|
||||
{{"type", "condition_branch"}, {"command", "else if perhaps"}, {"children", {
|
||||
{{"type", "string"}, {"text", "first else if"}}
|
||||
{{"type", Type::ConditionBranch}, {"command", "else if perhaps"}, {"children", {
|
||||
{{"type", Type::String}, {"text", "first else if"}}
|
||||
}}},
|
||||
{{"type", "condition_branch"}, {"command", "else if sometimes"}, {"children", {
|
||||
{{"type", "string"}, {"text", "second else if"}}
|
||||
{{"type", Type::ConditionBranch}, {"command", "else if sometimes"}, {"children", {
|
||||
{{"type", Type::String}, {"text", "second else if"}}
|
||||
}}},
|
||||
{{"type", "condition_branch"}, {"command", "else"}, {"children", {
|
||||
{{"type", "string"}, {"text", "test else"}}
|
||||
{{"type", Type::ConditionBranch}, {"command", "else"}, {"children", {
|
||||
{{"type", Type::String}, {"text", "test else"}}
|
||||
}}},
|
||||
}}}
|
||||
};
|
||||
|
||||
REQUIRE( env.parse(test) == result );
|
||||
REQUIRE( env.parse(test2) == result2 );
|
||||
CHECK( env.parse(test) == result );
|
||||
CHECK( env.parse(test2) == result2 );
|
||||
}
|
||||
|
||||
|
||||
@@ -108,17 +109,17 @@ TEST_CASE("parser") {
|
||||
data["brother"]["daughter0"] = { { "name", "Maria" } };
|
||||
|
||||
SECTION("variables from values") {
|
||||
REQUIRE( env.parse_variable("42", data) == 42 );
|
||||
REQUIRE( env.parse_variable("3.1415", data) == 3.1415 );
|
||||
REQUIRE( env.parse_variable("\"hello\"", data) == "hello" );
|
||||
CHECK( env.parse_variable("42", data) == 42 );
|
||||
CHECK( env.parse_variable("3.1415", data) == 3.1415 );
|
||||
CHECK( env.parse_variable("\"hello\"", data) == "hello" );
|
||||
}
|
||||
|
||||
SECTION("variables from JSON data") {
|
||||
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." );
|
||||
CHECK( env.parse_variable("name", data) == "Peter" );
|
||||
CHECK( env.parse_variable("age", data) == 29 );
|
||||
CHECK( env.parse_variable("names/1", data) == "Seb" );
|
||||
CHECK( env.parse_variable("brother/name", data) == "Chris" );
|
||||
CHECK( env.parse_variable("brother/daughters/0", data) == "Maria" );
|
||||
CHECK_THROWS_WITH( env.parse_variable("noelement", data), "JSON pointer found no element." );
|
||||
}
|
||||
}
|
||||
@@ -20,43 +20,43 @@ TEST_CASE("Renderer") {
|
||||
data["is_happy"] = true;
|
||||
|
||||
SECTION("Basic") {
|
||||
REQUIRE( env.render("Hello World!", data) == "Hello World!" );
|
||||
REQUIRE( env.render("", data, "../") == "" );
|
||||
CHECK( env.render("Hello World!", data) == "Hello World!" );
|
||||
CHECK( env.render("", data, "../") == "" );
|
||||
}
|
||||
|
||||
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!" );
|
||||
CHECK( env.render("Hello {{ name }}!", data) == "Hello Peter!" );
|
||||
CHECK( env.render("{{ name }}", data) == "Peter" );
|
||||
CHECK( env.render("{{name}}", data) == "Peter" );
|
||||
CHECK( env.render("{{ name }} is {{ age }} years old.", data) == "Peter is 29 years old." );
|
||||
CHECK( env.render("Hello {{ name }}! I come from {{ city }}.", data) == "Hello Peter! I come from Brunswick." );
|
||||
CHECK( env.render("Hello {{ names/1 }}!", data) == "Hello Seb!" );
|
||||
CHECK( env.render("Hello {{ brother/name }}!", data) == "Hello Chris!" );
|
||||
CHECK( 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) == "" );
|
||||
CHECK( env.render("Hello{# This is a comment #}!", data) == "Hello!" );
|
||||
CHECK( 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, !" );
|
||||
CHECK( env.render("Hello (% for name in names %){{ name }} (% endfor %)!", data) == "Hello Jeff Seb !" );
|
||||
CHECK( 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" );
|
||||
CHECK( env.render("(% if is_happy %)Yeah!(% endif %)", data) == "Yeah!" );
|
||||
CHECK( env.render("(% if is_sad %)Yeah!(% endif %)", data) == "" );
|
||||
CHECK( env.render("(% if is_sad %)Yeah!(% else %)Nooo...(% endif %)", data) == "Nooo..." );
|
||||
CHECK( env.render("(% if age == 29 %)Right(% else %)Wrong(% endif %)", data) == "Right" );
|
||||
CHECK( env.render("(% if age > 29 %)Right(% else %)Wrong(% endif %)", data) == "Wrong" );
|
||||
CHECK( env.render("(% if age <= 29 %)Right(% else %)Wrong(% endif %)", data) == "Right" );
|
||||
CHECK( env.render("(% if age != 28 %)Right(% else %)Wrong(% endif %)", data) == "Right" );
|
||||
CHECK( env.render("(% if age >= 30 %)Right(% else %)Wrong(% endif %)", data) == "Wrong" );
|
||||
CHECK( env.render("(% if age in [28, 29, 30] %)True(% endif %)", data) == "True" );
|
||||
|
||||
// Only works with gcc-5
|
||||
// REQUIRE( env.render("(% if name in [\"Simon\", \"Tom\"] %)Test1(% else if name in [\"Peter\"] %)Test2(% else %)Test3(% endif %)", data) == "Test2" );
|
||||
// CHECK( env.render("(% if name in [\"Simon\", \"Tom\"] %)Test1(% else if name in [\"Peter\"] %)Test2(% else %)Test3(% endif %)", data) == "Test2" );
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,10 @@ using json = nlohmann::json;
|
||||
|
||||
|
||||
TEST_CASE("string vector join function") {
|
||||
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" );
|
||||
CHECK( inja::join_strings({"1", "2", "3"}, ",") == "1,2,3" );
|
||||
CHECK( inja::join_strings({"1", "2", "3", "4", "5"}, " ") == "1 2 3 4 5" );
|
||||
CHECK( inja::join_strings({}, " ") == "" );
|
||||
CHECK( inja::join_strings({"single"}, "---") == "single" );
|
||||
}
|
||||
|
||||
TEST_CASE("basic search in string") {
|
||||
@@ -20,18 +20,18 @@ TEST_CASE("basic search in string") {
|
||||
|
||||
SECTION("basic search from start") {
|
||||
inja::SearchMatch match = inja::search(input, regex, 0);
|
||||
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" );
|
||||
CHECK( match.found == true );
|
||||
CHECK( match.position == 6 );
|
||||
CHECK( match.length == 5 );
|
||||
CHECK( match.end_position == 11 );
|
||||
CHECK( match.outer == "ipsum" );
|
||||
CHECK( match.inner == "psu" );
|
||||
}
|
||||
|
||||
SECTION("basic search from position") {
|
||||
inja::SearchMatch match = inja::search(input, regex, 8);
|
||||
REQUIRE( match.found == false );
|
||||
REQUIRE( match.length == 0 );
|
||||
CHECK( match.found == false );
|
||||
CHECK( match.length == 0 );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,54 +40,56 @@ TEST_CASE("search in string with multiple possible regexes") {
|
||||
|
||||
SECTION("basic 1") {
|
||||
std::vector<std::string> regex_patterns = { "tras", "do(\\w*)or", "es(\\w*)as", "ip(\\w*)um" };
|
||||
inja::SearchMatch match = inja::search(input, regex_patterns, 0);
|
||||
REQUIRE( match.regex_number == 3 );
|
||||
REQUIRE( match.outer == "ipsum" );
|
||||
REQUIRE( match.inner == "s" );
|
||||
inja::SearchMatchVector match = inja::search(input, regex_patterns, 0);
|
||||
CHECK( match.regex_number == 3 );
|
||||
CHECK( match.outer == "ipsum" );
|
||||
CHECK( match.inner == "s" );
|
||||
}
|
||||
|
||||
SECTION("basic 2") {
|
||||
std::vector<std::string> regex_patterns = { "tras", "ip(\\w*)um", "do(\\w*)or", "es(\\w*)as" };
|
||||
inja::SearchMatch match = inja::search(input, regex_patterns, 0);
|
||||
REQUIRE( match.regex_number == 1 );
|
||||
REQUIRE( match.outer == "ipsum" );
|
||||
REQUIRE( match.inner == "s" );
|
||||
inja::SearchMatchVector match = inja::search(input, regex_patterns, 0);
|
||||
CHECK( match.regex_number == 1 );
|
||||
CHECK( match.outer == "ipsum" );
|
||||
CHECK( match.inner == "s" );
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("search on level") {
|
||||
std::string input = "(% up %)(% up %)(% N1 %)(% down %)...(% up %)(% N2 %)(% up %)(% N3 %)(% down %)(% N4 %)(% down %)(% N5 %)(% down %)";
|
||||
std::string input = "(% up %)(% up %)Test(% N1 %)(% down %)...(% up %)(% N2 %)(% up %)(% N3 %)(% down %)(% N4 %)(% down %)(% N5 %)(% down %)";
|
||||
|
||||
std::regex regex_statement("\\(\\% (.*?) \\%\\)");
|
||||
std::regex regex_level_up("up");
|
||||
std::regex regex_level_down("down");
|
||||
std::regex regex_search("N(\\d+)");
|
||||
|
||||
SECTION("basic 1") {
|
||||
SECTION("first instance") {
|
||||
inja::SearchMatch open_match = inja::search(input, regex_statement, 0);
|
||||
CHECK( open_match.position == 0 );
|
||||
CHECK( open_match.end_position == 8 );
|
||||
CHECK( open_match.inner == "up" );
|
||||
|
||||
REQUIRE( open_match.position == 0 );
|
||||
REQUIRE( open_match.end_position == 8 );
|
||||
REQUIRE( open_match.inner == "up" );
|
||||
|
||||
inja::SearchClosedMatch match = inja::search_on_level(input, regex_statement, regex_level_up, regex_level_down, regex_search, open_match);
|
||||
|
||||
REQUIRE( match.position == 0 );
|
||||
REQUIRE( match.end_position == 105 );
|
||||
inja::SearchClosedMatch match = inja::search_closed_match_on_level(input, regex_statement, regex_level_up, regex_level_down, regex_search, open_match);
|
||||
CHECK( match.position == 0 );
|
||||
CHECK( match.end_position == 109 );
|
||||
}
|
||||
|
||||
SECTION("basic 1") {
|
||||
SECTION("second instance") {
|
||||
inja::SearchMatch open_match = inja::search(input, regex_statement, 4);
|
||||
|
||||
REQUIRE( open_match.position == 8 );
|
||||
REQUIRE( open_match.end_position == 16 );
|
||||
REQUIRE( open_match.inner == "up" );
|
||||
CHECK( open_match.position == 8 );
|
||||
CHECK( open_match.end_position == 16 );
|
||||
CHECK( open_match.inner == "up" );
|
||||
|
||||
inja::SearchClosedMatch match = inja::search_on_level(input, regex_statement, regex_level_up, regex_level_down, regex_search, open_match);
|
||||
inja::SearchClosedMatch match = inja::search_closed_match_on_level(input, regex_statement, regex_level_up, regex_level_down, regex_search, open_match);
|
||||
|
||||
REQUIRE( match.position == 8 );
|
||||
// REQUIRE( match.end_position == 24 );
|
||||
// REQUIRE( match.outer == "(% up %)(% N1 %)(% down %)" );
|
||||
// REQUIRE( match.inner == "(% N1 %)" );
|
||||
CHECK( match.open_match.position == 8 );
|
||||
CHECK( match.open_match.end_position== 16 );
|
||||
CHECK( match.close_match.position == 20 );
|
||||
CHECK( match.close_match.end_position == 28 );
|
||||
CHECK( match.position == 8 );
|
||||
CHECK( match.end_position == 28 );
|
||||
CHECK( match.outer == "(% up %)Test(% N1 %)" );
|
||||
CHECK( match.inner == "Test" );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user