Skip to content

Commit

Permalink
CmdArgs improved to support options like --key=value-with-equals
Browse files Browse the repository at this point in the history
  • Loading branch information
pmaciel committed Jun 18, 2023
1 parent ba0adda commit a4e2127
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 29 deletions.
32 changes: 12 additions & 20 deletions src/eckit/option/CmdArgs.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,14 @@
/// @date March 2016


#include "eckit/option/CmdArgs.h"

#include <iostream>
#include <map>

#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 {

Expand Down Expand Up @@ -62,33 +61,26 @@ void CmdArgs::init(std::function<void(const std::string&)> 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<std::string> 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<std::string, const option::Option*>::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<std::string>::const_iterator b = v.begin();
++b;
std::vector<std::string>::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;
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/option/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
67 changes: 59 additions & 8 deletions tests/option/eckit_test_option_cmdargs.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ namespace eckit::test {

namespace {

/// A self-cleaning container for Option
struct options_t : public std::vector<Option*> {
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
Expand All @@ -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<Option*>& 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<char**>(global_args));
CmdArgs(&usage, options, args_count, 0, true);
}
Expand Down Expand Up @@ -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<Option*> options;
options_t options;
options.push_back(new SimpleOption<std::string>("arg1", ""));

// Argument parser will succeed when passed exactly one unnamed argument.
Expand Down Expand Up @@ -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<Option*> options;
options_t options;
options.push_back(new SimpleOption<std::string>("arg1", ""));
options.push_back(new SimpleOption<long>("arg2", ""));

Expand All @@ -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<Option*> options;
options_t options;
options.push_back(new SimpleOption<std::string>("arg1", ""));
options.push_back(new SimpleOption<long>("arg2", ""));

Expand All @@ -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<Option*> options;
options_t options;
options.push_back(new SimpleOption<std::string>("arg1", ""));
options.push_back(new SimpleOption<long>("arg2", ""));

Expand All @@ -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<Option*> options;
options_t options;
options.push_back(new VectorOption<long>("arg", "", 3));

const char* input[] = {"exe", "--arg=-12345/678/-123"};
Expand All @@ -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<Option*> options;
options_t options;
options.push_back(new VectorOption<double>("arg", "", 4));

const char* input[] = {"exe", "--arg=-123.45/67.8/90/-123.0"};
Expand All @@ -226,7 +237,7 @@ CASE("test_eckit_option_cmdargs_double_vector") {

#if TESTCASE == 12
CASE("test_eckit_option_cmdargs_vector_size_check") {
std::vector<Option*> options;
options_t options;
options.push_back(new VectorOption<long>("arg", "", 4));

const char* input[] = {"exe", "--arg=1/2/3"};
Expand All @@ -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>(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<char**>(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) {
Expand Down

0 comments on commit a4e2127

Please sign in to comment.