From 1cb6b15cca8f56ef612009b8c935910bf214fee1 Mon Sep 17 00:00:00 2001 From: Samuel Leweke Date: Mon, 2 Apr 2018 15:54:00 +0200 Subject: [PATCH] Add exists() function that checks existence of key (#38) * Add exists() function that checks existence of key Adds an exists() function that checks whether a given key exists in the data. If only one argument is provided to exists(), the global data is queried for the item. If two arguments are given, the first argument specifies the object to query for the key given as second argument. Also adds corresponding unit tests and updates README for documentation. * Split exists() into exists() and existsIn() Splits the exists() function, which previously took both one or two arguments, into an exists() function accepting one and an existsIn() function accepting two arguments. --- README.md | 6 ++++++ src/inja.hpp | 13 +++++++++++++ test/src/unit-renderer.cpp | 19 +++++++++++++++++++ test/src/unit-string-helper.cpp | 2 ++ 4 files changed, 40 insertions(+) diff --git a/README.md b/README.md index 9d3bd37..e043abe 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,12 @@ render("{{ float(\"1.8\") > 2 }}", data); // false // Set default values if variables are not defined render("Hello {{ default(neighbour, \"my friend\") }}!", data); // "Hello Peter!" render("Hello {{ default(colleague, \"my friend\") }}!", data); // "Hello my friend!" + +// Check if a key exists in an object +render("{{ exists(\"guests\") }}", data); // "true" +render("{{ exists(\"city\") }}", data); // "false" +render("{{ existsIn(time, \"start\") }}", data); // "true" +render("{{ existsIn(time, neighbour) }}", data); // "false" ``` ### Callbacks diff --git a/src/inja.hpp b/src/inja.hpp index 1647eae..b1d4c65 100644 --- a/src/inja.hpp +++ b/src/inja.hpp @@ -279,6 +279,8 @@ struct Parsed { Sort, Upper, ReadJson, + Exists, + ExistsInObject, Default }; @@ -513,6 +515,15 @@ public: Parsed::CallbackSignature signature = std::make_pair(element.command, element.args.size()); return map_callbacks.at(signature)(element.args, data); } + case Parsed::Function::Exists: { + const std::string name = eval_expression(element.args[0], data); + return data.find(name) != data.end(); + } + case Parsed::Function::ExistsInObject: { + const std::string name = eval_expression(element.args[1], data); + const json d = eval_expression(element.args[0], data); + return d.find(name) != d.end(); + } } inja_throw("render_error", "unknown function in renderer: " + element.command); @@ -688,6 +699,8 @@ public: {Parsed::Function::Round, function_regex("round", 2)}, {Parsed::Function::Sort, function_regex("sort", 1)}, {Parsed::Function::Upper, function_regex("upper", 1)}, + {Parsed::Function::Exists, function_regex("exists", 1)}, + {Parsed::Function::ExistsInObject, function_regex("existsIn", 2)}, {Parsed::Function::ReadJson, Regex{"\\s*([^\\(\\)]*\\S)\\s*"}} }; diff --git a/test/src/unit-renderer.cpp b/test/src/unit-renderer.cpp index 41098c4..4acad8c 100644 --- a/test/src/unit-renderer.cpp +++ b/test/src/unit-renderer.cpp @@ -104,6 +104,9 @@ TEST_CASE("functions") { data["city"] = "New York"; data["names"] = {"Jeff", "Seb", "Peter", "Tom"}; data["temperature"] = 25.6789; + data["brother"]["name"] = "Chris"; + data["brother"]["daughters"] = {"Maria", "Helen"}; + data["property"] = "name"; SECTION("upper") { CHECK( env.render("{{ upper(name) }}", data) == "PETER" ); @@ -203,6 +206,22 @@ TEST_CASE("functions") { CHECK( env.render("{{ default(surname, \"nobody\") }}", data) == "nobody" ); CHECK_THROWS_WITH( env.render("{{ default(surname, lastname) }}", data), "[inja.exception.render_error] variable '/lastname' not found" ); } + + SECTION("exists") { + CHECK( env.render("{{ exists(\"name\") }}", data) == "true" ); + CHECK( env.render("{{ exists(\"zipcode\") }}", data) == "false" ); + CHECK( env.render("{{ exists(name) }}", data) == "false" ); + CHECK( env.render("{{ exists(property) }}", data) == "true" ); + } + + SECTION("existsIn") { + CHECK( env.render("{{ existsIn(brother, \"name\") }}", data) == "true" ); + CHECK( env.render("{{ existsIn(brother, \"parents\") }}", data) == "false" ); + CHECK( env.render("{{ existsIn(brother, property) }}", data) == "true" ); + CHECK( env.render("{{ existsIn(brother, name) }}", data) == "false" ); + CHECK_THROWS_WITH( env.render("{{ existsIn(sister, \"lastname\") }}", data), "[inja.exception.render_error] variable '/sister' not found" ); + CHECK_THROWS_WITH( env.render("{{ existsIn(brother, sister) }}", data), "[inja.exception.render_error] variable '/sister' not found" ); + } } TEST_CASE("callbacks") { diff --git a/test/src/unit-string-helper.cpp b/test/src/unit-string-helper.cpp index f6ee964..87f4618 100644 --- a/test/src/unit-string-helper.cpp +++ b/test/src/unit-string-helper.cpp @@ -155,6 +155,8 @@ TEST_CASE("match-functions") { CHECK( inja::match(" upper(lower()) ", map_regex).type() == inja::Parsed::Function::Upper ); CHECK( inja::match("lower(upper(test))", map_regex).type() == inja::Parsed::Function::Lower ); CHECK( inja::match("round(2, 3)", map_regex).type() == inja::Parsed::Function::Round ); + CHECK( inja::match("exists(\"var\")", map_regex).type() == inja::Parsed::Function::Exists ); + CHECK( inja::match("existsIn(var, \"othervar\")", map_regex).type() == inja::Parsed::Function::ExistsInObject ); } TEST_CASE("create-regex-functions") {