From a4e2127fef05c24dd6323ae94b7bacd02a349153 Mon Sep 17 00:00:00 2001 From: Pedro Maciel Date: Fri, 16 Jun 2023 17:09:30 +0100 Subject: [PATCH] CmdArgs improved to support options like --key=value-with-equals --- src/eckit/option/CmdArgs.cc | 32 ++++------- tests/option/CMakeLists.txt | 2 +- tests/option/eckit_test_option_cmdargs.cc | 67 ++++++++++++++++++++--- 3 files changed, 72 insertions(+), 29 deletions(-) diff --git a/src/eckit/option/CmdArgs.cc b/src/eckit/option/CmdArgs.cc index b332ac427..31034ad2c 100644 --- a/src/eckit/option/CmdArgs.cc +++ b/src/eckit/option/CmdArgs.cc @@ -14,15 +14,14 @@ /// @date March 2016 +#include "eckit/option/CmdArgs.h" + #include #include #include "eckit/exception/Exceptions.h" -#include "eckit/option/CmdArgs.h" #include "eckit/option/Option.h" #include "eckit/runtime/Main.h" -#include "eckit/utils/StringTools.h" -#include "eckit/utils/Tokenizer.h" namespace eckit::option { @@ -62,33 +61,26 @@ void CmdArgs::init(std::function usage, int args_count } } - Tokenizer parse("="); for (int i = 1; i < argc; ++i) { - std::string a = ctx.argv(i); + const auto a = ctx.argv(i); if (a.size() > 2 && a[0] == '-' && a[1] == '-') { - std::vector v; - parse(a.substr(2), v); + auto b = a.begin() + 2; + auto e = std::find(b, a.end(), '='); + + const std::string key(b, e); - std::map::const_iterator j = opts.find(v[0]); - if (j != opts.end()) { + if (auto j = opts.find(key); j != opts.end()) { try { - if (v.size() == 1) { - (*j).second->set(*this); - } - else { - std::vector::const_iterator b = v.begin(); - ++b; - std::vector::const_iterator e = v.end(); - (*j).second->set(StringTools::join("=", b, e), *this); - } + const std::string value(e == a.end() ? "" : std::string{++e, a.end()}); + value.empty() ? j->second->set(*this) : j->second->set(value, *this); } catch (UserError&) { - Log::info() << "Invalid value for option --" << v[0] << std::endl; + Log::info() << "Invalid value for option --" << key << std::endl; error = true; } } else { - Log::info() << "Invalid option --" << v[0] << std::endl; + Log::info() << "Invalid option --" << key << std::endl; error = true; } } diff --git a/tests/option/CMakeLists.txt b/tests/option/CMakeLists.txt index 9e5c91176..2bf8fd6dc 100644 --- a/tests/option/CMakeLists.txt +++ b/tests/option/CMakeLists.txt @@ -1,4 +1,4 @@ -foreach( TESTCASE RANGE 1 12 ) +foreach( TESTCASE RANGE 1 13 ) ecbuild_add_test( TARGET eckit_test_option_cmdargs_${TESTCASE} SOURCES eckit_test_option_cmdargs.cc diff --git a/tests/option/eckit_test_option_cmdargs.cc b/tests/option/eckit_test_option_cmdargs.cc index d219103b0..18661ee33 100644 --- a/tests/option/eckit_test_option_cmdargs.cc +++ b/tests/option/eckit_test_option_cmdargs.cc @@ -41,6 +41,17 @@ namespace eckit::test { namespace { +/// A self-cleaning container for Option +struct options_t : public std::vector { + using vector::push_back; + + ~options_t() { + for (auto* option : *this) { + delete option; + } + } +}; + /// A local function to satisfy the CmdArg details void usage(const std::string&) { // Empty @@ -51,7 +62,7 @@ void init(int nargs, const char* global_args[]) { CmdArgs(&usage, 1, 0, true); } -void init(int nargs, const char* global_args[], std::vector& options, int args_count = 0) { +void init(int nargs, const char* global_args[], options_t& options, int args_count = 0) { Main::initialise(nargs, const_cast(global_args)); CmdArgs(&usage, options, args_count, 0, true); } @@ -85,7 +96,7 @@ CASE("test_eckit_option_cmdargs_numbered_args_required") { #if TESTCASE >= 4 and TESTCASE <= 6 CASE("test_eckit_option_cmdargs_numbered_args_required_with_options") { - std::vector options; + options_t options; options.push_back(new SimpleOption("arg1", "")); // Argument parser will succeed when passed exactly one unnamed argument. @@ -114,7 +125,7 @@ CASE("test_eckit_option_cmdargs_numbered_args_required_with_options") { CASE("test_eckit_option_cmdargs_simple_argument_string") { // Set up he parser to accept two named arguments, one integer and one string // n.b. Option* are deleted inside CmdArgs. - std::vector options; + options_t options; options.push_back(new SimpleOption("arg1", "")); options.push_back(new SimpleOption("arg2", "")); @@ -138,7 +149,7 @@ CASE("test_eckit_option_cmdargs_simple_argument_string") { CASE("test_eckit_option_cmdargs_simple_argument_integer") { // Set up the parser to accept two named arguments, one integer and one string // n.b. Option* are deleted inside CmdArgs. - std::vector options; + options_t options; options.push_back(new SimpleOption("arg1", "")); options.push_back(new SimpleOption("arg2", "")); @@ -160,7 +171,7 @@ CASE("test_eckit_option_cmdargs_simple_argument_integer") { #if TESTCASE == 9 CASE("test_eckit_option_cmdargs_simple_argument_missing") { - std::vector options; + options_t options; options.push_back(new SimpleOption("arg1", "")); options.push_back(new SimpleOption("arg2", "")); @@ -175,7 +186,7 @@ CASE("test_eckit_option_cmdargs_simple_argument_missing") { CASE("test_eckit_option_cmdargs_integer_vector") { // Set up the parser to accept two named arguments, one integer and one string // n.b. Option* are deleted inside CmdArgs. - std::vector options; + options_t options; options.push_back(new VectorOption("arg", "", 3)); const char* input[] = {"exe", "--arg=-12345/678/-123"}; @@ -201,7 +212,7 @@ CASE("test_eckit_option_cmdargs_integer_vector") { CASE("test_eckit_option_cmdargs_double_vector") { // Set up the parser to accept two named arguments, one integer and one string // n.b. Option* are deleted inside CmdArgs. - std::vector options; + options_t options; options.push_back(new VectorOption("arg", "", 4)); const char* input[] = {"exe", "--arg=-123.45/67.8/90/-123.0"}; @@ -226,7 +237,7 @@ CASE("test_eckit_option_cmdargs_double_vector") { #if TESTCASE == 12 CASE("test_eckit_option_cmdargs_vector_size_check") { - std::vector options; + options_t options; options.push_back(new VectorOption("arg", "", 4)); const char* input[] = {"exe", "--arg=1/2/3"}; @@ -236,6 +247,46 @@ CASE("test_eckit_option_cmdargs_vector_size_check") { //---------------------------------------------------------------------------------------------------------------------- +#if TESTCASE == 13 +CASE("test_eckit_option_cmdargs_value_with_equals") { + options_t options; + for (auto c : std::string{"abcdefghijk"}) { + options.push_back(new SimpleOption(std::string{c}, "")); + } + + const char* input[] = {"exe", + "--a=a", + "--b==b", + "--c=c=", + "--d===d", + "--e==e=", + "--f=f==", + "--g=g=g", + "--h==h=h", + "--i==ii=", + "--j=j==j", + "--k=k=k="}; + + Main::initialise(12, const_cast(input)); + + CmdArgs args(&usage, options, 0, 0, true); + + EXPECT(args.getString("a") == "a"); + EXPECT(args.getString("b") == "=b"); + EXPECT(args.getString("c") == "c="); + EXPECT(args.getString("d") == "==d"); + EXPECT(args.getString("e") == "=e="); + EXPECT(args.getString("f") == "f=="); + EXPECT(args.getString("g") == "g=g"); + EXPECT(args.getString("h") == "=h=h"); + EXPECT(args.getString("i") == "=ii="); + EXPECT(args.getString("j") == "j==j"); + EXPECT(args.getString("k") == "k=k="); +} +#endif + +//---------------------------------------------------------------------------------------------------------------------- + } // namespace eckit::test int main(int argc, char** argv) {