diff --git a/README.md b/README.md index 5f72b13..4b734ba 100644 --- a/README.md +++ b/README.md @@ -7,19 +7,20 @@ 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) - - json data = {{"name", "world"}}; - string result = inja::render("Hello {{ world }}!", data); - // "Hello World!" - +```c++ +json data = {{"name", "world"}}; +string result = inja::render("Hello {{ world }}!", data); +// "Hello World!" +``` ## Integration Inja is headers only. Just one dependency: json by nlohmann. - #include "json.hpp" - #include "inja.hpp" - +```c++ +#include "json.hpp" +#include "inja.hpp" +``` ## Examples diff --git a/src/inja.hpp b/src/inja.hpp index 268d411..f4bb6fe 100644 --- a/src/inja.hpp +++ b/src/inja.hpp @@ -5,6 +5,7 @@ #ifndef PANTOR_INJA_HPP #define PANTOR_INJA_HPP + #include #include #include @@ -15,13 +16,14 @@ static_assert(false, "nlohmann/json not found."); #endif + namespace inja { using json = nlohmann::json; using string = std::string; -string join_strings(std::vector vector, string delimiter) { +inline string join_strings(std::vector vector, string delimiter) { std::stringstream ss; for (size_t i = 0; i < vector.size(); ++i) { @@ -77,7 +79,7 @@ struct SearchClosedMatch { SearchMatch open_match, close_match; }; -SearchMatch search(string input, std::regex regex, size_t position) { +inline SearchMatch search(string input, std::regex regex, size_t position) { auto first = input.cbegin(); auto last = input.cend(); @@ -90,7 +92,7 @@ SearchMatch search(string input, std::regex regex, size_t position) { return SearchMatch(match, position); } -SearchMatch search(string input, std::vector regex_patterns, size_t position) { +inline SearchMatch search(string input, std::vector regex_patterns, size_t position) { auto first = input.cbegin(); auto last = input.cend(); @@ -125,7 +127,7 @@ SearchMatch search(string input, std::vector regex_patterns, size_t posi 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) { +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) { int level = 0; size_t current_position = open_match.end_position; @@ -143,7 +145,7 @@ SearchClosedMatch search_on_level(string input, std::regex regex_statement, std: 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) { +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); } @@ -158,9 +160,13 @@ class Environment { std::regex regex_comment; std::vector regex_pattern_delimiters; + string global_path; + public: - Environment() { + Environment(): Environment("./") { } + + 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_expression = "\\{\\{\\s*(.+?)\\s*\\}\\}"; @@ -418,26 +424,31 @@ public: } string render_template(string filename, json data) { - string text = load_template(filename); + string text = load_file(filename); string path = filename.substr(0, filename.find_last_of("/\\") + 1); // Include / itself return render(text, data, path); } - string load_template(string filename) { - std::ifstream file(filename); + string render_template_with_json_file(string filename, string filename_data) { + json data = load_json(filename_data); + return render_template(filename, data); + } + + string load_file(string filename) { + std::ifstream file(global_path + filename); string text((std::istreambuf_iterator(file)), std::istreambuf_iterator()); return text; } json load_json(string filename) { - std::ifstream file(filename); + std::ifstream file(global_path + filename); json j; file >> j; return j; } }; -string render(string input, json data) { +inline string render(string input, json data) { Environment env = Environment(); return env.render(input, data); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 50b4c2f..ea9fce3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,14 +1,22 @@ -# Prepare "Catch" library for other executables -set(CATCH_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty) +## +## Prepare "Catch" library for other executables +## +set(CATCH_INCLUDE_DIR "thirdparty/catch") add_library(Catch INTERFACE) target_include_directories(Catch INTERFACE ${CATCH_INCLUDE_DIR}) set(UNITTEST_TARGET_NAME "inja_unit") -set(TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/test.cpp) +file(GLOB TEST_SOURCES "src/*.cpp") add_executable(${UNITTEST_TARGET_NAME} ${TEST_SOURCES}) -target_link_libraries(${UNITTEST_TARGET_NAME} Catch) + +target_link_libraries(${UNITTEST_TARGET_NAME} Catch) +target_include_directories(${UNITTEST_TARGET_NAME} PRIVATE "../src" "thirdparty/json") + +## +## Add tests to make +## add_test(NAME "${UNITTEST_TARGET_NAME}_default" COMMAND ${UNITTEST_TARGET_NAME} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/build ) \ No newline at end of file diff --git a/test/data/nested.txt b/test/data/nested.txt deleted file mode 100644 index 5aef97e..0000000 --- a/test/data/nested.txt +++ /dev/null @@ -1,11 +0,0 @@ -^{% for x in x_array %} - {% for y in y_array %} - {{x}}-{{y}} - {% endfor %} -{% endfor %} - -json a; -a["type"] = loop|condition|include -a["command"] = x in x_array -a["innerString"] = "..." -a["children"] = [] diff --git a/test/data/nested/data.json b/test/data/nested/data.json new file mode 100644 index 0000000..f7e4949 --- /dev/null +++ b/test/data/nested/data.json @@ -0,0 +1,4 @@ +{ + "xarray": [0, 1], + "yarray": [0, 1, 2] +} \ No newline at end of file diff --git a/test/data/nested/result.txt b/test/data/nested/result.txt new file mode 100644 index 0000000..d3bee34 --- /dev/null +++ b/test/data/nested/result.txt @@ -0,0 +1,12 @@ + +0-0 + +0-1 + +0-2 + +1-0 + +1-1 + +1-2 diff --git a/test/data/nested/template.txt b/test/data/nested/template.txt new file mode 100644 index 0000000..fd264e9 --- /dev/null +++ b/test/data/nested/template.txt @@ -0,0 +1,3 @@ +(% for x in xarray %)(% for y in yarray %) +{{x}}-{{y}} +(% endfor %)(% endfor %) \ No newline at end of file diff --git a/test/data/simple-file/data.json b/test/data/simple-file/data.json new file mode 100644 index 0000000..c009c75 --- /dev/null +++ b/test/data/simple-file/data.json @@ -0,0 +1,3 @@ +{ + "name": "Jeff" +} \ No newline at end of file diff --git a/test/data/simple-file/result.txt b/test/data/simple-file/result.txt new file mode 100644 index 0000000..d6b3f74 --- /dev/null +++ b/test/data/simple-file/result.txt @@ -0,0 +1 @@ +Hello Jeff. \ No newline at end of file diff --git a/test/data/simple-file/template.txt b/test/data/simple-file/template.txt new file mode 100644 index 0000000..a68f931 --- /dev/null +++ b/test/data/simple-file/template.txt @@ -0,0 +1 @@ +Hello {{ name }}. \ No newline at end of file diff --git a/test/src/test.cpp b/test/src/test.cpp deleted file mode 100644 index 4f6f556..0000000 --- a/test/src/test.cpp +++ /dev/null @@ -1,238 +0,0 @@ -#define CATCH_CONFIG_MAIN -#include "../thirdparty/catch/catch.hpp" - -#include "../thirdparty/json/json.hpp" -#include "../../src/inja.hpp" - - -using Environment = inja::Environment; -using json = nlohmann::json; - - -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 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 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.parse_variable("42", data) == 42 ); - REQUIRE( env.parse_variable("3.1415", data) == 3.1415 ); - REQUIRE( 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." ); - } -} - -TEST_CASE("Render") { - Environment env = Environment(); - json data; - 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, "../") == "" ); - } - - 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" ); - - // 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" ); - } -} - -TEST_CASE("Files") { - Environment env = Environment(); - json data; - data["name"] = "Jeff"; - - SECTION("Files should be loaded") { - REQUIRE( env.load_template("data/simple.txt") == "Hello {{ name }}." ); - } - - SECTION("Files should be rendered") { - REQUIRE( env.render_template("data/simple.txt", data) == "Hello Jeff." ); - } - - SECTION("File includes should be rendered") { - REQUIRE( env.render_template("data/include.txt", data) == "Answer: Hello Jeff." ); - } -} \ No newline at end of file diff --git a/test/src/unit-files.cpp b/test/src/unit-files.cpp new file mode 100644 index 0000000..d55f1be --- /dev/null +++ b/test/src/unit-files.cpp @@ -0,0 +1,36 @@ +#include "catch.hpp" +#include "json.hpp" +#include "inja.hpp" + + +using Environment = inja::Environment; +using json = nlohmann::json; + + +TEST_CASE("files handling") { + Environment env = Environment(); + json data; + data["name"] = "Jeff"; + + SECTION("files should be loaded") { + REQUIRE( 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." ); + } + + SECTION("file includes should be rendered") { + REQUIRE( env.render_template("../test/data/include.txt", data) == "Answer: Hello Jeff." ); + } +} + +TEST_CASE("complete files") { + Environment env = Environment("../test/data/"); + + 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") ); + } + } +} \ No newline at end of file diff --git a/test/src/unit-parser.cpp b/test/src/unit-parser.cpp new file mode 100644 index 0000000..7ac6fde --- /dev/null +++ b/test/src/unit-parser.cpp @@ -0,0 +1,124 @@ +#include "catch.hpp" +#include "json.hpp" +#include "inja.hpp" + + +using Environment = inja::Environment; +using json = nlohmann::json; + + +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.parse_variable("42", data) == 42 ); + REQUIRE( env.parse_variable("3.1415", data) == 3.1415 ); + REQUIRE( 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." ); + } +} \ No newline at end of file diff --git a/test/src/unit-renderer.cpp b/test/src/unit-renderer.cpp new file mode 100644 index 0000000..c38a5f1 --- /dev/null +++ b/test/src/unit-renderer.cpp @@ -0,0 +1,62 @@ +#include "catch.hpp" +#include "json.hpp" +#include "inja.hpp" + + +using Environment = inja::Environment; +using json = nlohmann::json; + + +TEST_CASE("Renderer") { + Environment env = Environment(); + json data; + 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, "../") == "" ); + } + + 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" ); + + // 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" ); + } +} \ No newline at end of file diff --git a/test/src/unit-string-helper.cpp b/test/src/unit-string-helper.cpp new file mode 100644 index 0000000..84137ed --- /dev/null +++ b/test/src/unit-string-helper.cpp @@ -0,0 +1,93 @@ +#include "catch.hpp" +#include "json.hpp" +#include "inja.hpp" + + +using Environment = inja::Environment; +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" ); +} + +TEST_CASE("basic search in string") { + std::string input = "lorem ipsum dolor it"; + std::regex regex("i(.*)m"); + + 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" ); + } + + SECTION("basic search from position") { + inja::SearchMatch match = inja::search(input, regex, 8); + REQUIRE( match.found == false ); + REQUIRE( match.length == 0 ); + } +} + +TEST_CASE("search in string with multiple possible regexes") { + std::string input = "lorem ipsum dolor amit estas tronum."; + + SECTION("basic 1") { + std::vector 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" ); + } + + SECTION("basic 2") { + std::vector 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" ); + } +} + +TEST_CASE("search on level") { + std::string input = "(% up %)(% up %)(% 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") { + inja::SearchMatch open_match = inja::search(input, regex_statement, 0); + + 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 ); + } + + SECTION("basic 1") { + 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" ); + + inja::SearchClosedMatch match = inja::search_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 %)" ); + } +} \ No newline at end of file diff --git a/test/src/unit.cpp b/test/src/unit.cpp new file mode 100644 index 0000000..063e878 --- /dev/null +++ b/test/src/unit.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" \ No newline at end of file