add divisibleBy, odd, even functions, add ElementNotation for dot or pointer notation

This commit is contained in:
pantor
2017-11-16 17:39:18 +01:00
parent 776bd00e4a
commit 93ec40b3fb
4 changed files with 116 additions and 7 deletions

View File

@@ -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

View File

@@ -17,7 +17,9 @@ namespace inja {
using json = nlohmann::json;
/*!
@brief returns the values of a std key-value-map
*/
template<typename S, typename T>
inline std::vector<T> get_values(std::map<S, T> map) {
std::vector<T> result;
@@ -26,6 +28,25 @@ inline std::vector<T> get_values(std::map<S, T> 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<Function, Regex> 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<double>() * std::pow(10.0, precision.get<int>())) / std::pow(10.0, precision.get<int>());
}
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<int>() % divisor.get<int>() == 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<int>() % 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<int>() % 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];

View File

@@ -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" );
}

View File

@@ -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<int>({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." );
}
}