diff --git a/simgear/misc/argparse.cxx b/simgear/misc/argparse.cxx index 392f21b4..438af49f 100644 --- a/simgear/misc/argparse.cxx +++ b/simgear/misc/argparse.cxx @@ -24,6 +24,7 @@ #include #include // std::pair, std::move() #include // std::size_t +#include // std::strcmp() #include #include @@ -223,13 +224,14 @@ ArgumentParser::parseArgs(int argc, const char *const *argv) const } if (inOptions) { - if (currentArg[0] == '-') { + if (currentArg.size() >= 2 && currentArg[0] == '-') { if (currentArg[1] == '-') { i += readLongOption(argc, argv, currentArg, i+1, optsWithValues); } else { i += readShortOptions(argc, argv, currentArg, i+1, optsWithValues); } - } else { // the argument doesn't start with a '-' + } else { + // The argument is neither an option, nor a cluster of short options. inOptions = false; nonOptionArgs.push_back(currentArg); } @@ -284,13 +286,15 @@ int ArgumentParser::readLongOption(int argc, const char *const *argv, case OptionArgType::OPTIONAL_ARGUMENT: // pass through case OptionArgType::MANDATORY_ARGUMENT: if (optEnd != string::npos) { - // The optional value is present as in the same command line + // The optional value is present in the same command line // argument as the option name (syntax '--option=value'). optVal.setHasValue(true); optVal.setValue(s.substr(optEnd + 1)); optsWithValues.push_back(std::move(optVal)); return 0; - } else if (nextArgIdx < argc && argv[nextArgIdx][0] != '-') { + } else if (nextArgIdx < argc && + (argv[nextArgIdx][0] != '-' || + !std::strcmp(argv[nextArgIdx], "-"))) { // The optional value is present as a separate command line argument // (syntax '--option value'). optVal.setHasValue(true); @@ -358,7 +362,9 @@ int ArgumentParser::readShortOptions(int argc, const char *const *argv, optsWithValues.emplace_back(optDesc, string("-") + s[i], s.substr(i+1), true /* hasValue */); return 0; - } else if (nextArgIdx < argc && argv[nextArgIdx][0] != '-') { + } else if (nextArgIdx < argc && + (argv[nextArgIdx][0] != '-' || + !std::strcmp(argv[nextArgIdx], "-"))) { assert(i + 1 == s.size()); // The option is at the end of 'currentArg' and has a value: // argv[nextArgIdx]. diff --git a/simgear/misc/argparse.hxx b/simgear/misc/argparse.hxx index aee2d901..4b10f487 100644 --- a/simgear/misc/argparse.hxx +++ b/simgear/misc/argparse.hxx @@ -212,8 +212,9 @@ public: // hyphens, can be used to cause all subsequent arguments to be treated as // non-option arguments, regardless of whether they start with a hyphen or // not. In the absence of this special argument, the first argument that is - // not the value of an option and does not start with a hyphen marks the end - // of options. All subsequent arguments are read as non-option arguments. + // not the value of an option and is either a single hyphen, or doesn't + // start with a hyphen, marks the end of options. This and all subsequent + // arguments are read as non-option arguments. // // Return a pair containing: // - the list of supplied options (with their respective values, when diff --git a/simgear/misc/argparse_test.cxx b/simgear/misc/argparse_test.cxx index bd7b10df..f6af9228 100644 --- a/simgear/misc/argparse_test.cxx +++ b/simgear/misc/argparse_test.cxx @@ -202,6 +202,107 @@ void test_mixOfShortAndLongOptions() "<-- came too late, treated as an arg"})); } +void test_whenOptionValueIsASingleHyphen() +{ + cout << "Testing cases where a single hyphen is used as an option value" << + endl; + + using namespace simgear::argparse; + ArgumentParser parser; + + parser.addOption("option -T", OptionArgType::NO_ARGUMENT, "-T", "--test"); + parser.addOption("option -o", OptionArgType::OPTIONAL_ARGUMENT, + "-o", "--with-opt-arg"); + parser.addOption("option -m", OptionArgType::MANDATORY_ARGUMENT, "-m", + "--with-mandatory-arg"); + + const vector v({ + "FoobarProg", "-To", "-", "-o-", "-oT", "-o", "-T", "-o", "-", + "--with-opt-arg=-", "--with-opt-arg", "-", "--with-opt-arg", + "-m-", "--with-mandatory-arg=-", "--with-mandatory-arg", "-", "-m", "-", + "non option 1", "non option 2", "non option 3"}); + const auto res = parser.parseArgs(v.size(), &v[0]); + const auto& opts = res.first; + const auto& otherArgs = res.second; + + SG_CHECK_EQUAL(opts.size(), 14); // number of passed options + SG_CHECK_EQUAL(otherArgs.size(), 3); // number of non-option arguments + + SG_CHECK_EQUAL(opts[0].passedAs(), "-T"); + SG_CHECK_EQUAL(opts[0].value(), ""); + SG_CHECK_EQUAL(opts[0].hasValue(), false); + SG_CHECK_EQUAL(opts[0].id(), "option -T"); + + SG_CHECK_EQUAL(opts[1].passedAs(), "-o"); + SG_CHECK_EQUAL(opts[1].value(), "-"); + SG_CHECK_EQUAL(opts[1].hasValue(), true); + SG_CHECK_EQUAL(opts[1].id(), "option -o"); + + SG_CHECK_EQUAL(opts[2].passedAs(), "-o"); + SG_CHECK_EQUAL(opts[2].value(), "-"); + SG_CHECK_EQUAL(opts[2].hasValue(), true); + SG_CHECK_EQUAL(opts[2].id(), "option -o"); + + SG_CHECK_EQUAL(opts[3].passedAs(), "-o"); + SG_CHECK_EQUAL(opts[3].value(), "T"); + SG_CHECK_EQUAL(opts[3].hasValue(), true); + SG_CHECK_EQUAL(opts[3].id(), "option -o"); + + SG_CHECK_EQUAL(opts[4].passedAs(), "-o"); + SG_CHECK_EQUAL(opts[4].value(), ""); + SG_CHECK_EQUAL(opts[4].hasValue(), false); + SG_CHECK_EQUAL(opts[4].id(), "option -o"); + + SG_CHECK_EQUAL(opts[5].passedAs(), "-T"); + SG_CHECK_EQUAL(opts[5].value(), ""); + SG_CHECK_EQUAL(opts[5].hasValue(), false); + SG_CHECK_EQUAL(opts[5].id(), "option -T"); + + SG_CHECK_EQUAL(opts[6].passedAs(), "-o"); + SG_CHECK_EQUAL(opts[6].value(), "-"); + SG_CHECK_EQUAL(opts[6].hasValue(), true); + SG_CHECK_EQUAL(opts[6].id(), "option -o"); + + SG_CHECK_EQUAL(opts[7].passedAs(), "--with-opt-arg"); + SG_CHECK_EQUAL(opts[7].value(), "-"); + SG_CHECK_EQUAL(opts[7].hasValue(), true); + SG_CHECK_EQUAL(opts[7].id(), "option -o"); + + SG_CHECK_EQUAL(opts[8].passedAs(), "--with-opt-arg"); + SG_CHECK_EQUAL(opts[8].value(), "-"); + SG_CHECK_EQUAL(opts[8].hasValue(), true); + SG_CHECK_EQUAL(opts[8].id(), "option -o"); + + SG_CHECK_EQUAL(opts[9].passedAs(), "--with-opt-arg"); + SG_CHECK_EQUAL(opts[9].value(), ""); + SG_CHECK_EQUAL(opts[9].hasValue(), false); + SG_CHECK_EQUAL(opts[9].id(), "option -o"); + + SG_CHECK_EQUAL(opts[10].passedAs(), "-m"); + SG_CHECK_EQUAL(opts[10].value(), "-"); + SG_CHECK_EQUAL(opts[10].hasValue(), true); + SG_CHECK_EQUAL(opts[10].id(), "option -m"); + + SG_CHECK_EQUAL(opts[11].passedAs(), "--with-mandatory-arg"); + SG_CHECK_EQUAL(opts[11].value(), "-"); + SG_CHECK_EQUAL(opts[11].hasValue(), true); + SG_CHECK_EQUAL(opts[11].id(), "option -m"); + + SG_CHECK_EQUAL(opts[12].passedAs(), "--with-mandatory-arg"); + SG_CHECK_EQUAL(opts[12].value(), "-"); + SG_CHECK_EQUAL(opts[12].hasValue(), true); + SG_CHECK_EQUAL(opts[12].id(), "option -m"); + + SG_CHECK_EQUAL(opts[13].passedAs(), "-m"); + SG_CHECK_EQUAL(opts[13].value(), "-"); + SG_CHECK_EQUAL(opts[13].hasValue(), true); + SG_CHECK_EQUAL(opts[13].id(), "option -m"); + + SG_CHECK_EQUAL_NOSTREAM( + otherArgs, + vector({"non option 1", "non option 2", "non option 3"})); +} + void test_frontierBetweenOptionsAndNonOptions() { cout << "Testing around the frontier between options and non-options" << endl; @@ -321,14 +422,44 @@ void test_frontierBetweenOptionsAndNonOptions() vector({"doesn't look like an option", "non option 1", "non option 2", "--", "non option 3"})); - // Test 8: no other argument than the program name in argv - const vector v8({"FoobarProg"}); + // Test 8: the argument marking the end of options is the empty string + const vector v8({ + "FoobarProg", "--long-option-without-arg", "-aval", + "", "non option 1", "non option 2", "-", "non option 3"}); const auto res8 = parser.parseArgs(v8.size(), &v8[0]); const auto& opts8 = res8.first; const auto& otherArgs8 = res8.second; - SG_VERIFY(opts8.empty()); - SG_VERIFY(otherArgs8.empty()); + SG_CHECK_EQUAL(opts8.size(), 2); + SG_CHECK_EQUAL(otherArgs8.size(), 5); + + SG_CHECK_EQUAL_NOSTREAM( + otherArgs8, + vector({"", "non option 1", "non option 2", "-", "non option 3"})); + + // Test 9: the argument marking the end of options is a single hyphen + const vector v9({ + "FoobarProg", "--long-option-without-arg", "-aval", + "-", "non option 1", "non option 2", "-", "non option 3"}); + const auto res9 = parser.parseArgs(v9.size(), &v9[0]); + const auto& opts9 = res9.first; + const auto& otherArgs9 = res9.second; + + SG_CHECK_EQUAL(opts9.size(), 2); + SG_CHECK_EQUAL(otherArgs9.size(), 5); + + SG_CHECK_EQUAL_NOSTREAM( + otherArgs9, + vector({"-", "non option 1", "non option 2", "-", "non option 3"})); + + // Test 10: no other argument than the program name in argv + const vector v10({"FoobarProg"}); + const auto res10 = parser.parseArgs(v10.size(), &v10[0]); + const auto& opts10 = res10.first; + const auto& otherArgs10 = res10.second; + + SG_VERIFY(opts10.empty()); + SG_VERIFY(otherArgs10.empty()); } void test_optionsWithMultipleAliases() @@ -480,6 +611,7 @@ void test_invalidOptionOrMissingArgument() int main(int argc, const char *const *argv) { test_mixOfShortAndLongOptions(); + test_whenOptionValueIsASingleHyphen(); test_frontierBetweenOptionsAndNonOptions(); test_optionsWithMultipleAliases(); test_invalidOptionOrMissingArgument();