diff --git a/.travis.yml b/.travis.yml index 165e946..b035ebd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,9 +60,9 @@ matrix: - compiler: clang addons: apt: - sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.7'] - packages: clang-3.7 - env: COMPILER=clang++-3.7 + sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.8'] + packages: clang-3.8 + env: COMPILER=clang++-3.8 script: diff --git a/README.md b/README.md index a38fb68..a0f42f3 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,10 @@ Inja is headers only. Just one dependency: json by nlohmann. ```c++ #include "json.hpp" #include "inja.hpp" + +// For convenience +using namespace inja; +using json = nlohmann::json; ``` @@ -34,10 +38,10 @@ Inja is headers only. Just one dependency: json by nlohmann. json data; data["name"] = "world"; -inja::render("Hello {{ name }}!", data); // "Hello World!" +render("Hello {{ name }}!", data); // "Hello World!" // For more advanced usage, an environment is recommended -inja::Environment env = inja::Environment(); +Environment env = Environment(); // Render a string with json data std::string result = env.render("Hello {{ name }}!", data); @@ -52,16 +56,32 @@ std::string result_template_2 = env.render_temlate_with_json_file("template.txt" The environment class can be configured. ```c++ // With default settings -inja::Environment env_default = inja::Environment(); +Environment env_default = Environment(); // With global path to template files -inja::Environment env_default = inja::Environment("../path/templates/"); +Environment env_default = Environment("../path/templates/"); ``` ### Variables Variables can be rendered with the `{{ ... }}` syntax. +```c++ +json data; +data["name"] = "world"; +data["guests"] = {"Jeff", "Pierre", "Tom"}; +data["time"]["start"]["hour"] = 16; +data["time"]["end"]["hour"] = 21; + +string template = """ + {{ guests/0 }} + + {{ time/start/hour }} to {{ time/end/hour }} or {{ 24 }} +"""; +``` + +Valid Json -> Printed. Json Pointer. + ### Statements @@ -106,12 +126,17 @@ Guests: In the loop, some special variables are available: -- int index -- bool is_first -- bool is_last +- `int index, index1` +- `bool is_first` +- `bool is_last` #### Conditions +If, else if, else. Nested. conditions: +- `not` +- `==`, `>`, `<`, `>=`, `<=`, `!=` +- `in` + #### Includes Include other files like `(% include "footer.html" %)`. Relative from file. @@ -121,7 +146,7 @@ Include other files like `(% include "footer.html" %)`. Relative from file. Comments can be rendered with the `{# ... #}` syntax. ```c++ -inja::render("Hello{# Todo #}!", data); // "Hello!" +render("Hello{# Todo #}!", data); // "Hello!" ``` ## Supported compilers @@ -129,7 +154,7 @@ inja::render("Hello{# Todo #}!", data); // "Hello!" Currently, the following compilers are tested: - GCC 4.9 - 7.1 (and possibly later) -- Clang 3.6 - 3.7 (and possibly later) +- Clang 3.6 - 3.8 (and possibly later) ## License diff --git a/src/inja.hpp b/src/inja.hpp index c2d5d7f..56da247 100644 --- a/src/inja.hpp +++ b/src/inja.hpp @@ -100,6 +100,7 @@ inline SearchMatchVector search(string input, std::vector regex_patterns } } + // TODO use search(...) string regex_pattern = "(" + join_strings(regex_patterns, ")|(") + ")"; std::regex regex(regex_pattern); @@ -383,56 +384,67 @@ public: string render_tree(json input, json data, string path) { string result = ""; for (auto element: input) { - if (element["type"] == Parser::Type::String) { - result += element["text"]; - } - else if (element["type"] == Parser::Type::Variable) { - json variable = parse_variable(element["command"], data); - result += render_json(variable); - } - else if (element["type"] == Parser::Type::Include) { - result += render_template(path + element["filename"].get(), data); - } - else if (element["type"] == Parser::Type::Loop) { - const std::regex regex_loop_list("for (\\w+) in (.+)"); - - string command = element["command"].get(); - std::smatch match_command; - if (std::regex_match(command, match_command, regex_loop_list)) { - 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++) { - json data_loop = data; - data_loop[item_name] = list[i]; - data_loop["index"] = i; - data_loop["index1"] = i + 1; - data_loop["is_first"] = (i == 0); - data_loop["is_last"] = (i == list.size() - 1); - result += render_tree(element["children"], data_loop, path); - } + switch ( (Parser::Type) element["type"] ) { + case Parser::Type::String: { + result += element["text"]; + break; } - } - else if (element["type"] == Parser::Type::Condition) { - const std::regex regex_condition("(if|else if|else) ?(.*)"); - - json branches = element["children"]; - for (auto branch: branches) { + case Parser::Type::Variable: { + json variable = parse_variable(element["command"], data); + result += render_json(variable); + break; + } + case Parser::Type::Include: { + result += render_template(path + element["filename"].get(), data); + break; + } + case Parser::Type::Loop: { + const std::regex regex_loop_list("for (\\w+) in (.+)"); - string command = branch["command"].get(); + string command = element["command"].get(); std::smatch match_command; - if (std::regex_match(command, match_command, regex_condition)) { - 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); - break; + if (std::regex_match(command, match_command, regex_loop_list)) { + 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++) { + json data_loop = data; + data_loop[item_name] = list[i]; + data_loop["index"] = i; + data_loop["index1"] = i + 1; + data_loop["is_first"] = (i == 0); + data_loop["is_last"] = (i == list.size() - 1); + result += render_tree(element["children"], data_loop, path); } } + break; } - } + case Parser::Type::Condition: { + const std::regex regex_condition("(if|else if|else) ?(.*)"); + + for (auto branch: element["children"]) { + string command = branch["command"].get(); + std::smatch match_command; + if (std::regex_match(command, match_command, regex_condition)) { + 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); + break; + } + } + } + break; + } + case Parser::Type::Comment: { + break; + } + default: { + throw std::runtime_error("Unknown type in renderer."); + } + } } return result; } diff --git a/test/src/unit-parser.cpp b/test/src/unit-parser.cpp index 932230f..f503ea9 100644 --- a/test/src/unit-parser.cpp +++ b/test/src/unit-parser.cpp @@ -8,7 +8,7 @@ using json = nlohmann::json; using Type = inja::Parser::Type; -TEST_CASE("parser") { +TEST_CASE("parse structure") { Environment env = Environment(); SECTION("basic") { @@ -97,7 +97,10 @@ TEST_CASE("parser") { CHECK( env.parse(test) == result ); CHECK( env.parse(test2) == result2 ); } +} +TEST_CASE("parse json") { + Environment env = Environment(); json data; data["name"] = "Peter"; @@ -122,4 +125,34 @@ TEST_CASE("parser") { CHECK( env.parse_variable("brother/daughters/0", data) == "Maria" ); CHECK_THROWS_WITH( env.parse_variable("noelement", data), "JSON pointer found no element." ); } +} + +TEST_CASE("parse conditions") { + Environment env = Environment(); + + json data; + data["age"] = 29; + data["brother"] = "Peter"; + data["father"] = "Peter"; + + SECTION("elements") { + // CHECK( env.parse_condition("age", data) ); + CHECK_FALSE( env.parse_condition("size", data) ); + } + + SECTION("numbers") { + CHECK( env.parse_condition("age == 29", data) ); + CHECK( env.parse_condition("age >= 29", data) ); + CHECK( env.parse_condition("age <= 29", data) ); + CHECK( env.parse_condition("age < 100", data) ); + CHECK_FALSE( env.parse_condition("age > 29", data) ); + CHECK_FALSE( env.parse_condition("age != 29", data) ); + CHECK_FALSE( env.parse_condition("age < 28", data) ); + CHECK_FALSE( env.parse_condition("age < -100.0", data) ); + } + + SECTION("strings") { + CHECK( env.parse_condition("brother == father", data) ); + CHECK( env.parse_condition("brother == \"Peter\"", data) ); + } } \ No newline at end of file