argparse.cxx: allow arguments consisting of a single '-'
Such an argument is /a priori/ perfectly valid, and not an option. Typical use in a command line: -o - equivalent to: -o- The former way wasn't accepted before this commit; now it is (also '--output -', equivalent to '--output=-'). If the last option is followed by an argument consisting of a single '-', this argument marks the end of options and is the first non-option argument (contrary to '--' which, given its special status, would mark the end of options but would *not* count as a non-option argument). This commit also adds an std::string length check in ArgumentParser::parseArgs() that was missing before using operator[]() (important difference between operator[]() for std::string and std::map or std::unordered_map!).
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
#include <memory>
|
||||
#include <utility> // std::pair, std::move()
|
||||
#include <cstddef> // std::size_t
|
||||
#include <cstring> // std::strcmp()
|
||||
#include <cassert>
|
||||
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
@@ -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].
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<const char*> 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<string>({"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<string>({"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<const char*> v8({"FoobarProg"});
|
||||
// Test 8: the argument marking the end of options is the empty string
|
||||
const vector<const char*> 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<string>({"", "non option 1", "non option 2", "-", "non option 3"}));
|
||||
|
||||
// Test 9: the argument marking the end of options is a single hyphen
|
||||
const vector<const char*> 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<string>({"-", "non option 1", "non option 2", "-", "non option 3"}));
|
||||
|
||||
// Test 10: no other argument than the program name in argv
|
||||
const vector<const char*> 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();
|
||||
|
||||
Reference in New Issue
Block a user