From 93ec40b3fbcf333099870a3a2e5ebd24962819ba Mon Sep 17 00:00:00 2001 From: pantor Date: Thu, 16 Nov 2017 17:39:18 +0100 Subject: [PATCH] add divisibleBy, odd, even functions, add ElementNotation for dot or pointer notation --- README.md | 9 +++++ src/inja.hpp | 66 +++++++++++++++++++++++++++++++++-- test/src/unit-json-helper.cpp | 10 ++++++ test/src/unit-parser.cpp | 38 +++++++++++++++++--- 4 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 test/src/unit-json-helper.cpp diff --git a/README.md b/README.md index a7b2d98..6cca762 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,10 @@ Environment env = Environment("../path/templates/"); // With global path where to save rendered files Environment env = Environment("../path/templates/", "../path/results/"); +// Choose between JSON pointer or dot notation to access elements +env.setElementNotation(inja::ElementNotation::Pointer); // (default) e.g. time/start +env.setElementNotation(inja::ElementNotation::Dot); // e.g. time.start + // With other opening and closing strings (here the defaults, as regex) env.setVariables("\\{\\{", "\\}\\}"); // Variables {{ }} env.setComments("\\{#", "#\\}"); // Comments {# #} @@ -162,6 +166,11 @@ render("I count {{ length(guests) }} guests.", data); // "I count 3 guests." // Round numbers to a given precision render({{ round(3.1415, 0) }}, data) // 3 render({{ round(3.1415, 3) }}, data) // 3.142 + +// Check if a value is odd, even or divisible by a number +render({{ odd(42) }}, data) // false +render({{ even(42) }}, data) // true +render({{ divisibleBy(42, 7) }}, data) // true ``` ### Comments diff --git a/src/inja.hpp b/src/inja.hpp index 7cc2fa5..441a028 100644 --- a/src/inja.hpp +++ b/src/inja.hpp @@ -17,7 +17,9 @@ namespace inja { using json = nlohmann::json; - +/*! +@brief returns the values of a std key-value-map +*/ template inline std::vector get_values(std::map map) { std::vector result; @@ -26,6 +28,25 @@ inline std::vector get_values(std::map map) { } +/*! +@brief dot notation to json pointer notiation +*/ +inline std::string dot_to_json_pointer_notation(std::string dot) { + std::string result = dot; + while (result.find(".") != std::string::npos) { + result.replace(result.find("."), 1, "/"); + } + result.insert(0, "/"); + return result; +} + + +enum class ElementNotation { + Pointer, + Dot +}; + + class Regex: public std::regex { std::string pattern_; @@ -239,7 +260,10 @@ public: Lower, Range, Length, - Round + Round, + DivisibleBy, + Odd, + Even }; const std::map regex_map_functions = { @@ -248,6 +272,9 @@ public: {Function::Range, Regex{"range\\(\\s*(.*?)\\s*\\)"}}, {Function::Length, Regex{"length\\(\\s*(.*?)\\s*\\)"}}, {Function::Round, Regex{"round\\(\\s*(.*?)\\s*,\\s*(.*?)\\s*\\)"}}, + {Function::DivisibleBy, Regex{"divisibleBy\\(\\s*(.*?)\\s*,\\s*(.*?)\\s*\\)"}}, + {Function::Odd, Regex{"odd\\(\\s*(.*?)\\s*\\)"}}, + {Function::Even, Regex{"even\\(\\s*(.*?)\\s*\\)"}} }; Parser() { } @@ -367,6 +394,8 @@ public: class Environment { const std::string global_path; + ElementNotation elementNotation = ElementNotation::Pointer; + Parser parser; public: @@ -389,6 +418,10 @@ public: parser.regex_map_delimiters[Parser::Delimiter::Comment] = Regex{open + "\\s*(.+?)\\s*" + close}; } + void setElementNotation(const ElementNotation elementNotation_) { + elementNotation = elementNotation_; + } + json eval_variable(const std::string& input, json data) { @@ -434,10 +467,37 @@ public: if (not precision.is_number()) { throw std::runtime_error("Argument in round function is not a number."); } return std::round(number.get() * std::pow(10.0, precision.get())) / std::pow(10.0, precision.get()); } + case Parser::Function::DivisibleBy: { + const json number = eval_variable(match_function.str(1), data); + const json divisor = eval_variable(match_function.str(2), data); + if (not number.is_number()) { throw std::runtime_error("Argument in divisibleBy function is not a number."); } + if (not divisor.is_number()) { throw std::runtime_error("Argument in divisibleBy function is not a number."); } + return (number.get() % divisor.get() == 0); + } + case Parser::Function::Odd: { + const json number = eval_variable(match_function.str(1), data); + if (not number.is_number()) { throw std::runtime_error("Argument in odd function is not a number."); } + return (number.get() % 2 != 0); + } + case Parser::Function::Even: { + const json number = eval_variable(match_function.str(1), data); + if (not number.is_number()) { throw std::runtime_error("Argument in even function is not a number."); } + return (number.get() % 2 == 0); + } } std::string input_copy = input; - if (input_copy[0] != '/') { input_copy.insert(0, "/"); } + switch (elementNotation) { + case ElementNotation::Pointer: { + if (input_copy[0] != '/') { input_copy.insert(0, "/"); } + break; + } + case ElementNotation::Dot: { + input_copy = dot_to_json_pointer_notation(input_copy); + break; + } + } + json::json_pointer ptr(input_copy); json result = data[ptr]; diff --git a/test/src/unit-json-helper.cpp b/test/src/unit-json-helper.cpp new file mode 100644 index 0000000..385fe31 --- /dev/null +++ b/test/src/unit-json-helper.cpp @@ -0,0 +1,10 @@ +#include "catch.hpp" +#include "nlohmann/json.hpp" +#include "inja.hpp" + + + +TEST_CASE("Dot to pointer notation") { + CHECK( inja::dot_to_json_pointer_notation("person.names.surname") == "/person/names/surname" ); + CHECK( inja::dot_to_json_pointer_notation("guests.2") == "/guests/2" ); +} diff --git a/test/src/unit-parser.cpp b/test/src/unit-parser.cpp index b6e85b9..05cbfac 100644 --- a/test/src/unit-parser.cpp +++ b/test/src/unit-parser.cpp @@ -132,6 +132,7 @@ lorem ipsum TEST_CASE("Parse variables") { inja::Environment env = inja::Environment(); + json data; data["name"] = "Peter"; data["city"] = "Washington D.C."; @@ -149,16 +150,26 @@ TEST_CASE("Parse variables") { CHECK( env.eval_variable("[5, 6, 8]", data) == std::vector({5, 6, 8}) ); } - SECTION("Variables from JSON data") { + SECTION("Variables from JSON data, dot notation") { + env.setElementNotation(inja::ElementNotation::Dot); + CHECK( env.eval_variable("name", data) == "Peter" ); CHECK( env.eval_variable("age", data) == 29 ); + CHECK( env.eval_variable("names.1", data) == "Seb" ); + CHECK( env.eval_variable("brother.name", data) == "Chris" ); + CHECK( env.eval_variable("brother.daughters.0", data) == "Maria" ); + + CHECK_THROWS_WITH( env.eval_variable("noelement", data), "JSON pointer found no element." ); + CHECK_THROWS_WITH( env.eval_variable("&4s-", data), "JSON pointer found no element." ); + } + + SECTION("Variables from JSON data, pointer notation") { + env.setElementNotation(inja::ElementNotation::Pointer); + CHECK( env.eval_variable("names/1", data) == "Seb" ); CHECK( env.eval_variable("brother/name", data) == "Chris" ); CHECK( env.eval_variable("brother/daughters/0", data) == "Maria" ); CHECK( env.eval_variable("/age", data) == 29 ); - - CHECK_THROWS_WITH( env.eval_variable("noelement", data), "JSON pointer found no element." ); - CHECK_THROWS_WITH( env.eval_variable("&4s-", data), "JSON pointer found no element." ); } } @@ -245,4 +256,23 @@ TEST_CASE("Parse functions") { CHECK( env.eval_variable("round(temperature, 2)", data) == 25.68 ); CHECK_THROWS_WITH( env.eval_variable("round(name, 2)", data), "Argument in round function is not a number." ); } + + SECTION("DivisibleBy") { + CHECK( env.eval_variable("divisibleBy(50, 5)", data) == true ); + CHECK( env.eval_variable("divisibleBy(12, 3)", data) == true ); + CHECK( env.eval_variable("divisibleBy(11, 3)", data) == false ); + CHECK_THROWS_WITH( env.eval_variable("divisibleBy(name, 2)", data), "Argument in divisibleBy function is not a number." ); + } + + SECTION("Odd") { + CHECK( env.eval_variable("odd(11)", data) == true ); + CHECK( env.eval_variable("odd(12)", data) == false ); + CHECK_THROWS_WITH( env.eval_variable("odd(name)", data), "Argument in odd function is not a number." ); + } + + SECTION("Even") { + CHECK( env.eval_variable("even(11)", data) == false ); + CHECK( env.eval_variable("even(12)", data) == true ); + CHECK_THROWS_WITH( env.eval_variable("even(name)", data), "Argument in even function is not a number." ); + } }