Repository: nifi-minifi-cpp Updated Branches: refs/heads/master d616effd3 -> dc131d5ac
MINIFICPP-330 Implemented Expression Language substring operations This closes #219. Signed-off-by: Marc Parisi <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/repo Commit: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/commit/dc131d5a Tree: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/tree/dc131d5a Diff: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/diff/dc131d5a Branch: refs/heads/master Commit: dc131d5ac92d0173a2fe891df0a77e7d4eff684d Parents: d616eff Author: Andy I. Christianson <[email protected]> Authored: Tue Dec 12 12:04:46 2017 -0500 Committer: Marc Parisi <[email protected]> Committed: Mon Dec 18 10:48:05 2017 -0500 ---------------------------------------------------------------------- extensions/expression-language/Expression.cpp | 61 +++++++++++++++-- extensions/expression-language/Parser.yy | 15 +++-- .../ExpressionLanguageTests.cpp | 69 ++++++++++++++++++++ 3 files changed, 137 insertions(+), 8 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/dc131d5a/extensions/expression-language/Expression.cpp ---------------------------------------------------------------------- diff --git a/extensions/expression-language/Expression.cpp b/extensions/expression-language/Expression.cpp index 8c03422..806ff6a 100644 --- a/extensions/expression-language/Expression.cpp +++ b/extensions/expression-language/Expression.cpp @@ -64,12 +64,44 @@ std::string expr_toUpper(const std::vector<std::string> &args) { return result; } +std::string expr_substring(const std::vector<std::string> &args) { + if (args.size() < 3) { + return args[0].substr(std::stoul(args[1])); + } else { + return args[0].substr(std::stoul(args[1]), std::stoul(args[2])); + } +} + +std::string expr_substringBefore(const std::vector<std::string> &args) { + return args[0].substr(0, args[0].find(args[1])); +} + +std::string expr_substringBeforeLast(const std::vector<std::string> &args) { + size_t last_pos = 0; + while (args[0].find(args[1], last_pos + 1) != std::string::npos) { + last_pos = args[0].find(args[1], last_pos + 1); + } + return args[0].substr(0, last_pos); +} + +std::string expr_substringAfter(const std::vector<std::string> &args) { + return args[0].substr(args[0].find(args[1]) + args[1].length()); +} + +std::string expr_substringAfterLast(const std::vector<std::string> &args) { + size_t last_pos = 0; + while (args[0].find(args[1], last_pos + 1) != std::string::npos) { + last_pos = args[0].find(args[1], last_pos + 1); + } + return args[0].substr(last_pos + args[1].length()); +} + template<std::string T(const std::vector<std::string> &)> Expression make_dynamic_function_incomplete(const std::string &function_name, const std::vector<Expression> &args, std::size_t num_args) { - if (args.size() == num_args) { - return make_dynamic([=](const Parameters ¶ms) -> std::string { + if (args.size() >= num_args) { + auto result = make_dynamic([=](const Parameters ¶ms) -> std::string { std::vector<std::string> evaluated_args; for (const auto &arg : args) { @@ -78,6 +110,14 @@ Expression make_dynamic_function_incomplete(const std::string &function_name, return T(evaluated_args); }); + + result.complete = [function_name, args](Expression expr) -> Expression { + std::vector<Expression> complete_args = {expr}; + complete_args.insert(complete_args.end(), args.begin(), args.end()); + return make_dynamic_function(function_name, complete_args); + }; + + return result; } else { auto result = make_dynamic([](const Parameters ¶ms) -> std::string { throw std::runtime_error("Attempted to call incomplete function"); @@ -93,11 +133,22 @@ Expression make_dynamic_function_incomplete(const std::string &function_name, } } -Expression make_dynamic_function(const std::string &function_name, const std::vector<Expression> &args) { +Expression make_dynamic_function(const std::string &function_name, + const std::vector<Expression> &args) { if (function_name == "hostname") { return make_dynamic_function_incomplete<expr_hostname>(function_name, args, 0); } else if (function_name == "toUpper") { return make_dynamic_function_incomplete<expr_toUpper>(function_name, args, 1); + } else if (function_name == "substring") { + return make_dynamic_function_incomplete<expr_substring>(function_name, args, 2); + } else if (function_name == "substringBefore") { + return make_dynamic_function_incomplete<expr_substringBefore>(function_name, args, 2); + } else if (function_name == "substringBeforeLast") { + return make_dynamic_function_incomplete<expr_substringBeforeLast>(function_name, args, 2); + } else if (function_name == "substringAfter") { + return make_dynamic_function_incomplete<expr_substringAfter>(function_name, args, 2); + } else if (function_name == "substringAfterLast") { + return make_dynamic_function_incomplete<expr_substringAfterLast>(function_name, args, 2); } else { std::string msg("Unknown expression function: "); msg.append(function_name); @@ -142,10 +193,12 @@ Expression Expression::operator+(const Expression &other_expr) const { result.append(other_val_fn(params)); return result; }); - } else { // !isDynamic() && !other_expr.isDynamic() + } else if (!isDynamic() && !other_expr.isDynamic()) { std::string result(val_); result.append(other_expr.val_); return make_static(result); + } else { + throw std::runtime_error("Invalid function composition"); } } http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/dc131d5a/extensions/expression-language/Parser.yy ---------------------------------------------------------------------- diff --git a/extensions/expression-language/Parser.yy b/extensions/expression-language/Parser.yy index 0ffe536..e9837be 100644 --- a/extensions/expression-language/Parser.yy +++ b/extensions/expression-language/Parser.yy @@ -171,20 +171,27 @@ attr_id: quoted_text exp_whitespaces { std::swap($$, $1); } | IDENTIFIER exp_whitespaces { std::swap($$, $1); } ; -fn_arg: exp_content_val { $$ = $1; } +/*fn_arg: exp_content_val exp_whitespaces { $$ = $1; } + | NUMBER exp_whitespaces { $$ = make_static(std::to_string($1)); } + ;*/ + +fn_arg: quoted_text exp_whitespaces { $$ = make_static($1); } + | NUMBER exp_whitespaces { $$ = make_static(std::to_string($1)); } + | exp exp_whitespaces { $$ = $1; } ; fn_args: %empty {} - | fn_args fn_arg { $$.insert($$.end(), $1.begin(), $1.end()); $$.push_back($2); } + | fn_args COMMA exp_whitespaces fn_arg { $$.insert($$.end(), $1.begin(), $1.end()); $$.push_back($4); } + | fn_arg { $$.push_back($1); } -fn_call: attr_id LPAREN fn_args RPAREN exp_whitespaces { $$ = make_dynamic_function(std::move($1), $3); } +fn_call: attr_id LPAREN exp_whitespaces fn_args RPAREN exp_whitespaces { $$ = make_dynamic_function(std::move($1), $4); } exp_content_val: attr_id { $$ = make_dynamic_attr(std::move($1)); } | fn_call { $$ = $1; } ; exp_content: exp_content_val { $$ = $1; } - | exp_content_val COLON exp_whitespace fn_call { $$ = make_dynamic_function_postfix($1, $4); } + | exp_content_val COLON exp_whitespaces fn_call { $$ = make_dynamic_function_postfix($1, $4); } ; exp: DOLLAR LCURLY exp_whitespaces exp_content RCURLY { $$ = $4; } http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/dc131d5a/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp ---------------------------------------------------------------------- diff --git a/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp b/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp index c0dd7f2..0af816d 100644 --- a/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp +++ b/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp @@ -143,6 +143,13 @@ TEST_CASE("ToUpper function", "[expressionLanguageTestToUpperFunction]") { // N REQUIRE("text_before__FLOW_A_ATTR_VALUE_A__text_after" == expr({flow_file_a})); } +TEST_CASE("ToUpper function w/o whitespace", "[expressionLanguageTestToUpperFunctionWithoutWhitespace]") { // NOLINT + auto expr = expression::compile(R"(text_before${attr_a:toUpper()}text_after)"); + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr_a", "__flow_a_attr_value_a__"); + REQUIRE("text_before__FLOW_A_ATTR_VALUE_A__text_after" == expr({flow_file_a})); +} + TEST_CASE("GetFile PutFile dynamic attribute", "[expressionLanguageTestGetFilePutFileDynamicAttribute]") { // NOLINT TestController testController; @@ -235,3 +242,65 @@ TEST_CASE("GetFile PutFile dynamic attribute", "[expressionLanguageTestGetFilePu REQUIRE("extracted_attr" == output_str.str()); } } + +TEST_CASE("Substring 2 arg", "[expressionLanguageSubstring2]") { // NOLINT + auto expr = expression::compile("text_before${attr:substring(6, 8)}text_after"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "__flow_a_attr_value_a__"); + REQUIRE("text_before_a_attr_text_after" == expr({flow_file_a})); +} + +TEST_CASE("Substring 1 arg", "[expressionLanguageSubstring1]") { // NOLINT + auto expr = expression::compile("text_before${attr:substring(6)}text_after"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "__flow_a_attr_value_a__"); + REQUIRE("text_before_a_attr_value_a__text_after" == expr({flow_file_a})); +} + +TEST_CASE("Substring Before", "[expressionLanguageSubstringBefore]") { // NOLINT + auto expr = expression::compile("${attr:substringBefore('attr_value_a__')}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "__flow_a_attr_value_a__"); + REQUIRE("__flow_a_" == expr({flow_file_a})); +} + +TEST_CASE("Substring Before Last", "[expressionLanguageSubstringBeforeLast]") { // NOLINT + auto expr = expression::compile("${attr:substringBeforeLast('_a')}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "__flow_a_attr_value_a__"); + REQUIRE("__flow_a_attr_value" == expr({flow_file_a})); +} + +TEST_CASE("Substring After", "[expressionLanguageSubstringAfter]") { // NOLINT + auto expr = expression::compile("${attr:substringAfter('__flow_a')}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "__flow_a_attr_value_a__"); + REQUIRE("_attr_value_a__" == expr({flow_file_a})); +} + +TEST_CASE("Substring After Last", "[expressionLanguageSubstringAfterLast]") { // NOLINT + auto expr = expression::compile("${attr:substringAfterLast('_a')}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "__flow_a_attr_value_a__"); + REQUIRE("__" == expr({flow_file_a})); +} + +TEST_CASE("Substring Before No Args", "[expressionLanguageSubstringBeforeNoArgs]") { // NOLINT + auto expr = expression::compile("${attr:substringBefore()}"); + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "__flow_a_attr_value_a__"); + REQUIRE_THROWS_WITH(expr({flow_file_a}), "Attempted to call incomplete function"); +} + +TEST_CASE("Substring After No Args", "[expressionLanguageSubstringAfterNoArgs]") { // NOLINT + auto expr = expression::compile("${attr:substringAfter()}"); + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("attr", "__flow_a_attr_value_a__"); + REQUIRE_THROWS_WITH(expr({flow_file_a}), "Attempted to call incomplete function"); +}
