Add classes to ease writing command line parsers with GNU-style options handling
See simgear/misc/argparse.hxx for API and documentation (simgear/misc/argparse_test.cxx also has examples, although argparse.hxx features a very simple one at the top). These classes were also presented in <https://sourceforge.net/p/flightgear/mailman/message/35785019/>.
This commit is contained in:
@@ -7,6 +7,7 @@ set(HEADERS
|
||||
ResourceManager.hxx
|
||||
SimpleMarkdown.hxx
|
||||
SVGpreserveAspectRatio.hxx
|
||||
argparse.hxx
|
||||
interpolator.hxx
|
||||
make_new.hxx
|
||||
sg_dir.hxx
|
||||
@@ -25,6 +26,7 @@ set(SOURCES
|
||||
ResourceManager.cxx
|
||||
SimpleMarkdown.cxx
|
||||
SVGpreserveAspectRatio.cxx
|
||||
argparse.cxx
|
||||
interpolator.cxx
|
||||
sg_dir.cxx
|
||||
sg_path.cxx
|
||||
@@ -46,6 +48,10 @@ simgear_component(misc misc "${SOURCES}" "${HEADERS}")
|
||||
|
||||
if(ENABLE_TESTS)
|
||||
|
||||
add_executable(test_argparse argparse_test.cxx)
|
||||
target_link_libraries(test_argparse ${TEST_LIBS})
|
||||
add_test(argparse ${EXECUTABLE_OUTPUT_PATH}/test_argparse)
|
||||
|
||||
add_executable(test_CSSBorder CSSBorder_test.cxx)
|
||||
add_test(CSSBorder ${EXECUTABLE_OUTPUT_PATH}/test_CSSBorder)
|
||||
target_link_libraries(test_CSSBorder ${TEST_LIBS})
|
||||
|
||||
384
simgear/misc/argparse.cxx
Normal file
384
simgear/misc/argparse.cxx
Normal file
@@ -0,0 +1,384 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// argparse.cxx --- Simple, generic parser for command-line arguments
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <utility> // std::pair, std::move()
|
||||
#include <cstddef> // std::size_t
|
||||
#include <cassert>
|
||||
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include "argparse.hxx"
|
||||
|
||||
using std::string;
|
||||
using std::shared_ptr;
|
||||
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
namespace argparse
|
||||
{
|
||||
|
||||
// ***************************************************************************
|
||||
// * Base class for custom exceptions *
|
||||
// ***************************************************************************
|
||||
Error::Error(const string& message, const std::string& origin)
|
||||
: sg_exception("Argument parser error: " + message, origin)
|
||||
{ }
|
||||
|
||||
Error::Error(const char* message, const char* origin)
|
||||
: Error(string(message), string(origin))
|
||||
{ }
|
||||
|
||||
// ***************************************************************************
|
||||
// * OptionDesc class *
|
||||
// ***************************************************************************
|
||||
OptionDesc::OptionDesc(
|
||||
const string& optionId, std::vector<char> shortAliases,
|
||||
std::vector<string> longAliases, OptionArgType argumentType)
|
||||
: _id(optionId),
|
||||
_shortAliases(shortAliases),
|
||||
_longAliases(longAliases),
|
||||
_argumentType(argumentType)
|
||||
{ }
|
||||
|
||||
const std::string& OptionDesc::id() const
|
||||
{ return _id; }
|
||||
|
||||
const std::vector<char>& OptionDesc::shortAliases() const
|
||||
{ return _shortAliases; }
|
||||
|
||||
const std::vector<std::string>& OptionDesc::longAliases() const
|
||||
{ return _longAliases; }
|
||||
|
||||
OptionArgType OptionDesc::argumentType() const
|
||||
{ return _argumentType; }
|
||||
|
||||
|
||||
// ***************************************************************************
|
||||
// * OptionValue class *
|
||||
// ***************************************************************************
|
||||
OptionValue::OptionValue(shared_ptr<const OptionDesc> optionDesc,
|
||||
const string& passedAs, const string& value,
|
||||
bool hasValue)
|
||||
: _optionDesc(std::move(optionDesc)),
|
||||
_passedAs(passedAs),
|
||||
_value(value),
|
||||
_hasValue(hasValue)
|
||||
{ }
|
||||
|
||||
shared_ptr<const OptionDesc> OptionValue::optionDesc() const
|
||||
{ return _optionDesc; } // return a copy of the shared_ptr
|
||||
|
||||
void OptionValue::setOptionDesc(shared_ptr<const OptionDesc> descPtr)
|
||||
{ _optionDesc = std::move(descPtr); }
|
||||
|
||||
string OptionValue::passedAs() const
|
||||
{ return _passedAs; }
|
||||
|
||||
void OptionValue::setPassedAs(const string& passedAs)
|
||||
{ _passedAs = passedAs; }
|
||||
|
||||
string OptionValue::value() const
|
||||
{ return _value; }
|
||||
|
||||
void OptionValue::setValue(const string& value)
|
||||
{ _value = value; }
|
||||
|
||||
bool OptionValue::hasValue() const
|
||||
{ return _hasValue; }
|
||||
|
||||
void OptionValue::setHasValue(bool hasValue)
|
||||
{ _hasValue = hasValue; }
|
||||
|
||||
const string OptionValue::id() const
|
||||
{
|
||||
const auto desc = optionDesc();
|
||||
return (desc) ? desc->id() : string();
|
||||
}
|
||||
|
||||
|
||||
// ***************************************************************************
|
||||
// * ArgumentParser class *
|
||||
// ***************************************************************************
|
||||
|
||||
// Static utility method.
|
||||
std::vector<char>
|
||||
ArgumentParser::removeHyphens(const std::vector<string>& shortAliases,
|
||||
std::vector<string>& longAliases)
|
||||
{
|
||||
std::vector<char> shortAliasesCharVec;
|
||||
shortAliasesCharVec.reserve(shortAliases.size());
|
||||
|
||||
for (const string& opt: shortAliases) {
|
||||
if (opt.size() != 2 || opt[0] != '-' || opt[1] == '-' || opt[1] > 127) {
|
||||
throw Error("unexpected form for a short option: '" + opt + "' (expecting "
|
||||
"a string of size 2 whose first character is a hyphen and "
|
||||
"second character an ASCII char that is not a hyphen)");
|
||||
}
|
||||
|
||||
shortAliasesCharVec.emplace_back(opt[1]); // emplace the char after hyphen
|
||||
}
|
||||
|
||||
for (string& longOpt: longAliases) {
|
||||
if (longOpt.size() < 3 ||
|
||||
!simgear::strutils::starts_with(longOpt, string("--"))) {
|
||||
throw Error("unexpected form for a long option: '" + longOpt + "' "
|
||||
"(expecting a string of size 3 or more that starts with "
|
||||
"two hyphens)");
|
||||
}
|
||||
|
||||
longOpt.erase(0, 2); // remove the two leading hyphens
|
||||
}
|
||||
|
||||
return shortAliasesCharVec;
|
||||
}
|
||||
|
||||
void
|
||||
ArgumentParser::addOption(const string& optionId,
|
||||
OptionArgType argType,
|
||||
std::vector<string> shortAliases,
|
||||
std::vector<string> longAliases)
|
||||
{
|
||||
// Remove the leading dashes and do a sanity check for these arguments
|
||||
std::vector<char> shortAliasesCharVec = removeHyphens(shortAliases,
|
||||
longAliases);
|
||||
|
||||
const auto desc_p = std::make_shared<const OptionDesc>(
|
||||
optionId, std::move(shortAliasesCharVec), std::move(longAliases), argType);
|
||||
|
||||
for (const char c: desc_p->shortAliases()) {
|
||||
if (!_shortOptionMap.emplace(c, desc_p).second) {
|
||||
throw Error(
|
||||
"trying to add option '-" + string(1, c) + "', however it is already "
|
||||
"in the short option map");
|
||||
}
|
||||
}
|
||||
|
||||
for (const string& longOpt: desc_p->longAliases()) {
|
||||
if (!_longOptionMap.emplace(longOpt, desc_p).second) {
|
||||
throw Error(
|
||||
"trying to add option '--" + longOpt + "', however it is already in "
|
||||
"the long option map");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ArgumentParser::addOption(const string& optionId, OptionArgType argumentType,
|
||||
string shortOpt, string longOpt)
|
||||
{
|
||||
std::vector<string> shortOptList;
|
||||
std::vector<string> longOptList;
|
||||
|
||||
if (!shortOpt.empty()) {
|
||||
shortOptList.push_back(std::move(shortOpt));
|
||||
}
|
||||
|
||||
if (!longOpt.empty()) {
|
||||
longOptList.push_back(std::move(longOpt));
|
||||
}
|
||||
|
||||
addOption(optionId, argumentType, std::move(shortOptList),
|
||||
std::move(longOptList));
|
||||
}
|
||||
|
||||
std::pair< std::vector<OptionValue>, std::vector<string> >
|
||||
ArgumentParser::parseArgs(int argc, const char *const *argv) const
|
||||
{
|
||||
std::pair< std::vector<OptionValue>, std::vector<string> > res;
|
||||
std::vector<OptionValue>& optsWithValues = res.first;
|
||||
std::vector<string>& nonOptionArgs = res.second;
|
||||
bool inOptions = true;
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
// Decode from command line encoding
|
||||
const string currentArg = cmdEncToUtf8(argv[i]);
|
||||
|
||||
if ((inOptions) && (currentArg == "--")) {
|
||||
// We found the end-of-options delimiter
|
||||
inOptions = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inOptions) {
|
||||
if (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 '-'
|
||||
inOptions = false;
|
||||
nonOptionArgs.push_back(currentArg);
|
||||
}
|
||||
} else {
|
||||
nonOptionArgs.push_back(currentArg);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Static method
|
||||
string ArgumentParser::cmdEncToUtf8(const string& s)
|
||||
{
|
||||
#if defined(SG_WINDOWS)
|
||||
// Untested code path. Comments and/or testing by Windows people welcome.
|
||||
return simgear::strutils::convertWindowsLocal8BitToUtf8(s);
|
||||
#else
|
||||
// XXX This assumes UTF-8 encoding for command line arguments on non-Windows
|
||||
// platforms. Unfortunately, the current (April 2017) standard C++ API for
|
||||
// encoding conversions has big problems (cf.
|
||||
// <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0618r0.html>).
|
||||
// Should be fixed when we have a good way to do such conversions.
|
||||
return s;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Return the number of arguments used by the option value, if any (i.e., how
|
||||
// much the caller should shift to resume arguments processing).
|
||||
int ArgumentParser::readLongOption(int argc, const char *const *argv,
|
||||
const string& currentArg, int nextArgIdx,
|
||||
std::vector<OptionValue>& optsWithValues)
|
||||
const
|
||||
{
|
||||
const string s = currentArg.substr(2); // skip the two initial dashes
|
||||
|
||||
// UTF-8 guarantees that ASCII bytes (here, '=') cannot be part of the
|
||||
// encoding of a non-ASCII character.
|
||||
std::size_t optEnd = s.find('=');
|
||||
string opt = s.substr(0, optEnd);
|
||||
|
||||
const auto mapElt = _longOptionMap.find(opt);
|
||||
if (mapElt != _longOptionMap.end()) {
|
||||
const shared_ptr<const OptionDesc>& optDesc = mapElt->second;
|
||||
OptionValue optVal(optDesc, string("--") + opt);
|
||||
|
||||
switch (optDesc->argumentType()) {
|
||||
case OptionArgType::NO_ARGUMENT:
|
||||
optVal.setHasValue(false);
|
||||
optsWithValues.push_back(std::move(optVal));
|
||||
return 0;
|
||||
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
|
||||
// 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] != '-') {
|
||||
// The optional value is present as a separate command line argument
|
||||
// (syntax '--option value').
|
||||
optVal.setHasValue(true);
|
||||
optVal.setValue(cmdEncToUtf8(argv[nextArgIdx]));
|
||||
optsWithValues.push_back(std::move(optVal));
|
||||
return 1;
|
||||
} else if (optDesc->argumentType() ==
|
||||
OptionArgType::OPTIONAL_ARGUMENT) {
|
||||
// No argument (value) can be found for the option
|
||||
optVal.setHasValue(false);
|
||||
optsWithValues.push_back(std::move(optVal));
|
||||
return 0;
|
||||
} else {
|
||||
assert(optDesc->argumentType() == OptionArgType::MANDATORY_ARGUMENT);
|
||||
throw InvalidUserInput("option '" + optVal.passedAs() + "' requires an "
|
||||
"argument, but none was provided");
|
||||
}
|
||||
default:
|
||||
throw sg_error("This piece of code should be unreachable.");
|
||||
}
|
||||
} else {
|
||||
throw InvalidUserInput("invalid option: '--" + opt + "'");
|
||||
}
|
||||
}
|
||||
|
||||
int ArgumentParser::readShortOptions(int argc, const char *const *argv,
|
||||
const string& currentArg, int nextArgIdx,
|
||||
std::vector<OptionValue>& optsWithValues)
|
||||
const
|
||||
{
|
||||
shared_ptr<const OptionDesc> optDesc;
|
||||
const string s = currentArg.substr(1); // skip the initial dash
|
||||
std::size_t i = 0; // index inside s
|
||||
|
||||
// Read all options taking no argument in 'currentArg'; stop at the first
|
||||
// taking an optional or mandatory argument.
|
||||
for (/* empty */; i < s.size(); i++) {
|
||||
const auto mapElt = _shortOptionMap.find(s[i]);
|
||||
if (mapElt != _shortOptionMap.end()) {
|
||||
optDesc = mapElt->second;
|
||||
|
||||
if (optDesc->argumentType() == OptionArgType::NO_ARGUMENT) {
|
||||
optsWithValues.emplace_back(optDesc, string("-") + s[i], string(),
|
||||
false /* no value */);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
throw InvalidUserInput(string("invalid option: '-") + s[i] + "'");
|
||||
}
|
||||
}
|
||||
|
||||
if (i == s.size()) {
|
||||
// The command line argument in 'currentArg' was fully read and only
|
||||
// contains options that take no argument.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We've already “eaten” all options taking no argument in 'currentArg'.
|
||||
assert(optDesc->argumentType() == OptionArgType::OPTIONAL_ARGUMENT ||
|
||||
optDesc->argumentType() == OptionArgType::MANDATORY_ARGUMENT);
|
||||
|
||||
if (i + 1 < s.size()) {
|
||||
// The option has a value at the end of 'currentArg': s.substr(i+1)
|
||||
optsWithValues.emplace_back(optDesc, string("-") + s[i], s.substr(i+1),
|
||||
true /* hasValue */);
|
||||
return 0;
|
||||
} else if (nextArgIdx < argc && argv[nextArgIdx][0] != '-') {
|
||||
assert(i + 1 == s.size());
|
||||
// The option is at the end of 'currentArg' and has a value:
|
||||
// argv[nextArgIdx].
|
||||
optsWithValues.emplace_back(optDesc, string("-") + s[i],
|
||||
cmdEncToUtf8(argv[nextArgIdx]),
|
||||
true /* hasValue */);
|
||||
return 1;
|
||||
} else if (optDesc->argumentType() ==
|
||||
OptionArgType::OPTIONAL_ARGUMENT) {
|
||||
// No argument (value) can be found for the option
|
||||
optsWithValues.emplace_back(optDesc, string("-") + s[i], string(),
|
||||
false /* no value */);
|
||||
return 0;
|
||||
} else {
|
||||
assert(optDesc->argumentType() == OptionArgType::MANDATORY_ARGUMENT);
|
||||
throw InvalidUserInput(string("option '-") + s[i] + "' requires an "
|
||||
"argument, but none was provided");
|
||||
}
|
||||
}
|
||||
|
||||
} // of namespace argparse
|
||||
|
||||
} // of namespace simgear
|
||||
279
simgear/misc/argparse.hxx
Normal file
279
simgear/misc/argparse.hxx
Normal file
@@ -0,0 +1,279 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// argparse.hxx --- Simple, generic parser for command-line arguments
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#ifndef _SIMGEAR_ARGPARSE_HXX_
|
||||
#define _SIMGEAR_ARGPARSE_HXX_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <utility> // std::pair
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
||||
// Usage example:
|
||||
//
|
||||
// using simgear::argparse::OptionArgType;
|
||||
//
|
||||
// simgear::argparse::ArgumentParser parser;
|
||||
// parser.addOption("root option", OptionArgType::MANDATORY_ARGUMENT,
|
||||
// "", "--root");
|
||||
// parser.addOption("test option", OptionArgType::NO_ARGUMENT, "-t", "--test");
|
||||
//
|
||||
// const auto res = parser.parseArgs(argc, argv);
|
||||
//
|
||||
// for (const auto& opt: res.first) {
|
||||
// std::cerr << "Got option '" << opt.id() << "' as '" << opt.passedAs() <<
|
||||
// "'" << ((opt.hasValue()) ? " with value '" + opt.value() + "'" : "") <<
|
||||
// "\n";
|
||||
// }
|
||||
//
|
||||
// for (const auto& arg: res.second) {
|
||||
// std::cerr << "Got non-option argument '" << arg << "'\n";
|
||||
// }
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
namespace argparse
|
||||
{
|
||||
|
||||
// Custom exception classes
|
||||
class Error : public sg_exception
|
||||
{
|
||||
public:
|
||||
explicit Error(const std::string& message,
|
||||
const std::string& origin = std::string());
|
||||
explicit Error(const char* message, const char* origin = nullptr);
|
||||
};
|
||||
|
||||
class InvalidUserInput : public Error
|
||||
{
|
||||
using Error::Error; // inherit all constructors
|
||||
};
|
||||
|
||||
|
||||
enum class OptionArgType {
|
||||
NO_ARGUMENT = 0,
|
||||
OPTIONAL_ARGUMENT,
|
||||
MANDATORY_ARGUMENT
|
||||
};
|
||||
|
||||
// All strings inside this class are encoded in UTF-8.
|
||||
class OptionDesc
|
||||
{
|
||||
public:
|
||||
explicit OptionDesc(const std::string& optionId,
|
||||
std::vector<char> shortAliases,
|
||||
std::vector<std::string> longAliases,
|
||||
OptionArgType argumentType);
|
||||
|
||||
// Simple getters for the private members
|
||||
const std::string& id() const;
|
||||
const std::vector<char>& shortAliases() const;
|
||||
const std::vector<std::string>& longAliases() const;
|
||||
OptionArgType argumentType() const;
|
||||
|
||||
private:
|
||||
// Option identifier, invisible to the end user. Used to easily refer to the
|
||||
// option despite the various forms it may take (short and/or long aliases).
|
||||
std::string _id;
|
||||
// Each element of _shortAliases must be an ASCII character. For instance,
|
||||
// 'o' for an option called '-o'.
|
||||
std::vector<char> _shortAliases;
|
||||
// Each element of _longAliases should be the name of a long option, with
|
||||
// the two leading dashes removed. For instance, 'generate-foobar' for an
|
||||
// option named '--generate-foobar'.
|
||||
std::vector<std::string> _longAliases;
|
||||
OptionArgType _argumentType;
|
||||
};
|
||||
|
||||
// All strings inside this class are encoded in UTF-8.
|
||||
class OptionValue
|
||||
{
|
||||
public:
|
||||
explicit OptionValue(std::shared_ptr<const OptionDesc> optionDesc,
|
||||
const std::string& passedAs,
|
||||
const std::string& value = std::string(),
|
||||
bool hasValue = false);
|
||||
|
||||
// Simple getters/accessors for the private members
|
||||
std::shared_ptr<const OptionDesc> optionDesc() const;
|
||||
std::string passedAs() const;
|
||||
std::string value() const;
|
||||
bool hasValue() const;
|
||||
|
||||
// The corresponding setters
|
||||
void setOptionDesc(std::shared_ptr<const OptionDesc>);
|
||||
void setPassedAs(const std::string&);
|
||||
void setValue(const std::string&);
|
||||
void setHasValue(bool);
|
||||
|
||||
// For convenience: get the option ID from the result of optionDesc()
|
||||
const std::string id() const;
|
||||
|
||||
private:
|
||||
// Pointer to the option descriptor.
|
||||
std::shared_ptr<const OptionDesc> _optionDesc;
|
||||
// Exact option passed (e.g., -f or --foobar).
|
||||
std::string _passedAs;
|
||||
// Value given for the option, if any (otherwise, the empty string).
|
||||
std::string _value;
|
||||
// Tells whether the option has been given a value. This is of course mainly
|
||||
// useful for options taking an *optional* argument. The value in question
|
||||
// can be the empty string, if given on a separate command line argument
|
||||
// from the option.
|
||||
bool _hasValue;
|
||||
};
|
||||
|
||||
|
||||
// Main class for command line processing. Every string coming out of it is
|
||||
// encoded in UTF-8.
|
||||
class ArgumentParser
|
||||
{
|
||||
public:
|
||||
// Register an option, with zero or more short aliases (e.g., -a, -u, - F)
|
||||
// and zero or more long aliases (e.g., --foobar, --barnum, --bleh). The
|
||||
// option may take no argument, or one optional argument, or one mandatory
|
||||
// argument. The 'optionId' is used to refer to the option in a clear and
|
||||
// simple way, even in the presence of several short or long aliases. It is
|
||||
// thus visible to the programmer using this API, but not to users of the
|
||||
// command line interface being implemented.
|
||||
//
|
||||
// Note: this method and all its overloads take options in the form "-o" or
|
||||
// "--foobar" (as std::string instances). While it would be possible
|
||||
// to only require a char for each short option and to take long
|
||||
// option declarations without the two leading dashes, the API chosen
|
||||
// here should lead to more readable and searchable user code.
|
||||
//
|
||||
// shortAliases: each element should consist of two characters: an ASCII
|
||||
// hyphen (-) followed by an ASCII character.
|
||||
// longAliases: each element should be a string in UTF-8 encoding, starting
|
||||
// with two ASCII/UTF-8 hyphens (U+002D).
|
||||
//
|
||||
// This API could be extended to automatically generate --help output from
|
||||
// strings passed to addOption().
|
||||
void addOption(const std::string& optionId,
|
||||
OptionArgType argumentType,
|
||||
std::vector<std::string> shortAliases,
|
||||
std::vector<std::string> longAliases);
|
||||
// Convenience overload that should be enough for most cases. To register
|
||||
// only a short option or only a long option, simply pass the empty string
|
||||
// for the corresponding parameter.
|
||||
void addOption(const std::string& optionId,
|
||||
OptionArgType argumentType,
|
||||
std::string shortOpt = std::string(),
|
||||
std::string longOpt = std::string());
|
||||
|
||||
// Parse arguments from an argc/argv pair of variables. 'argc' should be the
|
||||
// number of elements in 'argv', the first of which is ignored for the sake
|
||||
// of options and arguments extraction (since it normally holds the program
|
||||
// name).
|
||||
//
|
||||
// Note: this “number of elements” doesn't count the usual---and completely
|
||||
// unneeded here---final null pointer.
|
||||
//
|
||||
// Short options may be grouped in the usual way. For instance, if '-x',
|
||||
// '-z' and '-f' are three short options, the first two taking no argument
|
||||
// and '-f' taking one mandatory argument, then both '-xzf bar' and
|
||||
// '-xzfbar' are equivalent to '-x -z -f bar' as well as to '-x -z -fbar'
|
||||
// ('bar' being the value taken by option '-f').
|
||||
//
|
||||
// Long options are handled in the usual way too:
|
||||
//
|
||||
// '--foobar' for an option taking no argument
|
||||
//
|
||||
// '--foobar=value' for an option taking an optional or mandatory
|
||||
// or '--foobar value' argument (two separate command line arguments in the
|
||||
// second case)
|
||||
//
|
||||
// Long option names may contain spaces, though this is extremely uncommon
|
||||
// and inconvenient for users. Any option argument (be it for a long or a
|
||||
// short option) may contain spaces, as expected.
|
||||
//
|
||||
// As usual too, the special '--' argument consisting of two ASCII/UTF-8
|
||||
// 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.
|
||||
//
|
||||
// Return a pair containing:
|
||||
// - the list of supplied options (with their respective values, when
|
||||
// applicable);
|
||||
// - the list of non-option arguments that were given after the options.
|
||||
//
|
||||
// Both of these lists (vectors) may be empty and preserve the order used in
|
||||
// 'argv'.
|
||||
std::pair< std::vector<OptionValue>, std::vector<std::string> >
|
||||
parseArgs(int argc, const char *const *argv) const;
|
||||
|
||||
private:
|
||||
// Convert from the encoding used for argv (command line arguments) to
|
||||
// UTF-8.
|
||||
//
|
||||
// This method is currently not very satisfactory (cf. comments in the
|
||||
// implementation). The Windows code path is untested; the non-Windows code
|
||||
// path assumes command line arguments are encoded in UTF-8 (in other words,
|
||||
// it's a no-op).
|
||||
static std::string cmdEncToUtf8(const std::string& stringInCmdLineEncoding);
|
||||
|
||||
// Remove leading dashes and do sanity checks. 'longAliases' is modified
|
||||
// in-place. 'shortAliases' is not, because we build an std::vector<char>
|
||||
// from an std::vector<std::string>.
|
||||
static std::vector<char> removeHyphens(
|
||||
const std::vector<std::string>& shortAliases,
|
||||
std::vector<std::string>& longAliases);
|
||||
|
||||
// Read a long option and its value, if any (in total: one or two command
|
||||
// line arguments).
|
||||
//
|
||||
// Return the number of arguments consumed by this process after
|
||||
// 'currentArg' (i.e., 0 or 1 depending on whether the last option in
|
||||
// 'currentArg' has been given a value).
|
||||
//
|
||||
// 'currentArg' comes from argv[nextArgIdx-1], after decoding by
|
||||
// cmdEncToUtf8(). Thus, argv[nextArgIdx] is the command-line argument
|
||||
// coming after 'currentArg'.
|
||||
int readLongOption(
|
||||
int argc, const char *const *argv, const std::string& currentArg,
|
||||
int nextArgIdx, std::vector<OptionValue>& optsWithValues) const;
|
||||
// Read all short options in a command line argument, plus the option value
|
||||
// of the last one of these, if any (even if the option value is in the next
|
||||
// command line argument).
|
||||
//
|
||||
// See readLongOption() for the return value and meaning of parameters.
|
||||
int readShortOptions(
|
||||
int argc, const char *const *argv, const std::string& currentArg,
|
||||
int nextArgIdx, std::vector<OptionValue>& optsWithValues) const;
|
||||
|
||||
// Keys are short option names without the leading dash
|
||||
std::unordered_map< char,
|
||||
std::shared_ptr<const OptionDesc> > _shortOptionMap;
|
||||
// Keys are long option names without the two leading dashes
|
||||
std::unordered_map< std::string,
|
||||
std::shared_ptr<const OptionDesc> > _longOptionMap;
|
||||
};
|
||||
|
||||
} // of namespace argparse
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // _SIMGEAR_ARGPARSE_HXX_
|
||||
488
simgear/misc/argparse_test.cxx
Normal file
488
simgear/misc/argparse_test.cxx
Normal file
@@ -0,0 +1,488 @@
|
||||
// -*- coding: utf-8 -*-
|
||||
//
|
||||
// argparse_test.cxx --- Automated tests for argparse.cxx / argparse.hxx
|
||||
//
|
||||
// Copyright (C) 2017 Florent Rougon
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along
|
||||
// with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
#include <simgear_config.h>
|
||||
|
||||
#include <iostream> // std::cout
|
||||
#include <vector>
|
||||
#include <cstdlib> // EXIT_SUCCESS
|
||||
|
||||
#include <simgear/misc/test_macros.hxx>
|
||||
#include "argparse.hxx"
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
|
||||
void test_mixOfShortAndLongOptions()
|
||||
{
|
||||
cout << "Testing a mix of short and long options, plus non-option arguments"
|
||||
<< endl;
|
||||
|
||||
using namespace simgear::argparse;
|
||||
ArgumentParser parser;
|
||||
|
||||
parser.addOption("test option", OptionArgType::NO_ARGUMENT, "-t");
|
||||
parser.addOption("long opt w/o arg", OptionArgType::NO_ARGUMENT,
|
||||
"", "--long-option-without-arg");
|
||||
parser.addOption("other test option", OptionArgType::NO_ARGUMENT, "-O");
|
||||
parser.addOption("yet another test option",
|
||||
OptionArgType::OPTIONAL_ARGUMENT, "-y", "--yes-we-can");
|
||||
parser.addOption("and again", OptionArgType::MANDATORY_ARGUMENT, "-a",
|
||||
"--all-you-need-is-love");
|
||||
parser.addOption("long option with opt arg",
|
||||
OptionArgType::OPTIONAL_ARGUMENT, "", // no short alias
|
||||
"--long-option-with-opt-arg");
|
||||
|
||||
// Using an std::vector to avoid the need to count the elements ourselves
|
||||
const vector<const char*> v({
|
||||
"FoobarProg", "-Oy", "-aarg for -a", "-OtOty", "arg for -y",
|
||||
"-tyOther arg for -y", "--long-option-without-arg", "-a", "Arg for -a",
|
||||
"--long-option-with-opt-arg", "--long-option-with-opt-arg", "value 1",
|
||||
"--long-option-with-opt-arg=value 2", "-t",
|
||||
"--all-you-need-is-love", "oh this is true", "-ypouet",
|
||||
"--all-you-need-is-love=right, I'll shut up ;-)", "non option",
|
||||
" other non option ", "--", "<-- came too late, treated as an arg"});
|
||||
// v.size() corresponds to argc, &v[0] corresponds to argv.
|
||||
const auto res = parser.parseArgs(v.size(), &v[0]);
|
||||
const auto& opts = res.first;
|
||||
const auto& otherArgs = res.second;
|
||||
|
||||
SG_CHECK_EQUAL(opts.size(), 19); // number of passed options
|
||||
SG_CHECK_EQUAL(otherArgs.size(), 4); // number of non-option arguments
|
||||
|
||||
// Check all passed options and their values
|
||||
SG_CHECK_EQUAL(opts[0].passedAs(), "-O");
|
||||
SG_CHECK_EQUAL(opts[0].value(), "");
|
||||
SG_CHECK_EQUAL(opts[0].hasValue(), false);
|
||||
|
||||
SG_CHECK_EQUAL(opts[0].id(), "other test option");
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[0].optionDesc()->argumentType(),
|
||||
OptionArgType::NO_ARGUMENT);
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[0].optionDesc()->shortAliases(),
|
||||
vector<char>(1, 'O'));
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[0].optionDesc()->longAliases(), vector<string>());
|
||||
|
||||
SG_CHECK_EQUAL(opts[1].passedAs(), "-y");
|
||||
SG_CHECK_EQUAL(opts[1].value(), "");
|
||||
SG_CHECK_EQUAL(opts[1].hasValue(), false);
|
||||
|
||||
SG_CHECK_EQUAL(opts[1].id(), "yet another test option");
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[1].optionDesc()->argumentType(),
|
||||
OptionArgType::OPTIONAL_ARGUMENT);
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[1].optionDesc()->shortAliases(),
|
||||
vector<char>(1, 'y'));
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[1].optionDesc()->longAliases(),
|
||||
vector<string>(1, "yes-we-can"));
|
||||
|
||||
SG_CHECK_EQUAL(opts[2].passedAs(), "-a");
|
||||
SG_CHECK_EQUAL(opts[2].value(), "arg for -a");
|
||||
SG_CHECK_EQUAL(opts[2].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[2].id(), "and again");
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[2].optionDesc()->argumentType(),
|
||||
OptionArgType::MANDATORY_ARGUMENT);
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[2].optionDesc()->shortAliases(),
|
||||
vector<char>(1, 'a'));
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[2].optionDesc()->longAliases(),
|
||||
vector<string>(1, "all-you-need-is-love"));
|
||||
|
||||
SG_CHECK_EQUAL(opts[3].passedAs(), "-O");
|
||||
SG_CHECK_EQUAL(opts[3].value(), "");
|
||||
SG_CHECK_EQUAL(opts[3].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[3].id(), "other test option");
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[3].optionDesc()->argumentType(),
|
||||
OptionArgType::NO_ARGUMENT);
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[3].optionDesc()->shortAliases(),
|
||||
vector<char>(1, 'O'));
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[3].optionDesc()->longAliases(), vector<string>());
|
||||
|
||||
SG_CHECK_EQUAL(opts[4].passedAs(), "-t");
|
||||
SG_CHECK_EQUAL(opts[4].value(), "");
|
||||
SG_CHECK_EQUAL(opts[4].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[4].id(), "test option");
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[4].optionDesc()->argumentType(),
|
||||
OptionArgType::NO_ARGUMENT);
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[4].optionDesc()->shortAliases(),
|
||||
vector<char>(1, 't'));
|
||||
SG_CHECK_EQUAL_NOSTREAM(opts[4].optionDesc()->longAliases(), vector<string>());
|
||||
|
||||
SG_CHECK_EQUAL(opts[5].passedAs(), "-O");
|
||||
SG_CHECK_EQUAL(opts[5].value(), "");
|
||||
SG_CHECK_EQUAL(opts[5].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[5].id(), "other test option");
|
||||
|
||||
SG_CHECK_EQUAL(opts[6].passedAs(), "-t");
|
||||
SG_CHECK_EQUAL(opts[6].value(), "");
|
||||
SG_CHECK_EQUAL(opts[6].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[6].id(), "test option");
|
||||
|
||||
SG_CHECK_EQUAL(opts[7].passedAs(), "-y");
|
||||
SG_CHECK_EQUAL(opts[7].value(), "arg for -y");
|
||||
SG_CHECK_EQUAL(opts[7].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[7].id(), "yet another test option");
|
||||
|
||||
SG_CHECK_EQUAL(opts[8].passedAs(), "-t");
|
||||
SG_CHECK_EQUAL(opts[8].value(), "");
|
||||
SG_CHECK_EQUAL(opts[8].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[8].id(), "test option");
|
||||
|
||||
SG_CHECK_EQUAL(opts[9].passedAs(), "-y");
|
||||
SG_CHECK_EQUAL(opts[9].value(), "Other arg for -y");
|
||||
SG_CHECK_EQUAL(opts[9].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[9].id(), "yet another test option");
|
||||
|
||||
SG_CHECK_EQUAL(opts[10].passedAs(), "--long-option-without-arg");
|
||||
SG_CHECK_EQUAL(opts[10].value(), "");
|
||||
SG_CHECK_EQUAL(opts[10].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[10].id(), "long opt w/o arg");
|
||||
|
||||
SG_CHECK_EQUAL(opts[11].passedAs(), "-a");
|
||||
SG_CHECK_EQUAL(opts[11].value(), "Arg for -a");
|
||||
SG_CHECK_EQUAL(opts[11].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[11].id(), "and again");
|
||||
|
||||
SG_CHECK_EQUAL(opts[12].passedAs(), "--long-option-with-opt-arg");
|
||||
SG_CHECK_EQUAL(opts[12].value(), "");
|
||||
SG_CHECK_EQUAL(opts[12].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[12].id(), "long option with opt arg");
|
||||
|
||||
SG_CHECK_EQUAL(opts[13].passedAs(), "--long-option-with-opt-arg");
|
||||
SG_CHECK_EQUAL(opts[13].value(), "value 1");
|
||||
SG_CHECK_EQUAL(opts[13].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[13].id(), "long option with opt arg");
|
||||
|
||||
SG_CHECK_EQUAL(opts[14].passedAs(), "--long-option-with-opt-arg");
|
||||
SG_CHECK_EQUAL(opts[14].value(), "value 2");
|
||||
SG_CHECK_EQUAL(opts[14].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[14].id(), "long option with opt arg");
|
||||
|
||||
SG_CHECK_EQUAL(opts[15].passedAs(), "-t");
|
||||
SG_CHECK_EQUAL(opts[15].value(), "");
|
||||
SG_CHECK_EQUAL(opts[15].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[15].id(), "test option");
|
||||
|
||||
SG_CHECK_EQUAL(opts[16].passedAs(), "--all-you-need-is-love");
|
||||
SG_CHECK_EQUAL(opts[16].value(), "oh this is true");
|
||||
SG_CHECK_EQUAL(opts[16].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[16].id(), "and again");
|
||||
|
||||
SG_CHECK_EQUAL(opts[17].passedAs(), "-y");
|
||||
SG_CHECK_EQUAL(opts[17].value(), "pouet");
|
||||
SG_CHECK_EQUAL(opts[17].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[17].id(), "yet another test option");
|
||||
|
||||
SG_CHECK_EQUAL(opts[18].passedAs(), "--all-you-need-is-love");
|
||||
SG_CHECK_EQUAL(opts[18].value(), "right, I'll shut up ;-)");
|
||||
SG_CHECK_EQUAL(opts[18].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[18].id(), "and again");
|
||||
|
||||
// Check all non-option arguments that were passed to parser.parseArgs()
|
||||
SG_CHECK_EQUAL_NOSTREAM(
|
||||
otherArgs,
|
||||
vector<string>({"non option", " other non option ", "--",
|
||||
"<-- came too late, treated as an arg"}));
|
||||
}
|
||||
|
||||
void test_frontierBetweenOptionsAndNonOptions()
|
||||
{
|
||||
cout << "Testing around the frontier between options and non-options" << endl;
|
||||
|
||||
using namespace simgear::argparse;
|
||||
ArgumentParser parser;
|
||||
|
||||
parser.addOption("option -T", OptionArgType::NO_ARGUMENT, "-T");
|
||||
parser.addOption("long opt w/o arg", OptionArgType::NO_ARGUMENT,
|
||||
"", "--long-option-without-arg");
|
||||
parser.addOption("option -a", OptionArgType::MANDATORY_ARGUMENT, "-a",
|
||||
"--this-is-option-a");
|
||||
|
||||
// Test 1: both options and non-options; '--' used as a normal non-option
|
||||
// argument (i.e., after other non-option arguments).
|
||||
const vector<const char*> v1({
|
||||
"FoobarProg", "--long-option-without-arg", "-aval", "non option 1",
|
||||
"non option 2", "--", "non option 3"});
|
||||
// v1.size() corresponds to argc, &v1[0] corresponds to argv.
|
||||
const auto res1 = parser.parseArgs(v1.size(), &v1[0]);
|
||||
const auto& opts1 = res1.first;
|
||||
const auto& otherArgs1 = res1.second;
|
||||
|
||||
SG_CHECK_EQUAL(opts1.size(), 2); // number of passed options
|
||||
SG_CHECK_EQUAL(otherArgs1.size(), 4); // number of non-option arguments
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(
|
||||
otherArgs1,
|
||||
vector<string>({"non option 1", "non option 2", "--", "non option 3"}));
|
||||
|
||||
// Test 2: some options but no non-options arguments
|
||||
const vector<const char*> v2({
|
||||
"FoobarProg", "--long-option-without-arg", "-aval"});
|
||||
const auto res2 = parser.parseArgs(v2.size(), &v2[0]);
|
||||
const auto& opts2 = res2.first;
|
||||
const auto& otherArgs2 = res2.second;
|
||||
|
||||
SG_CHECK_EQUAL(opts2.size(), 2);
|
||||
SG_VERIFY(otherArgs2.empty());
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(otherArgs2, vector<string>());
|
||||
|
||||
// Test 3: same as test 2, but with useless end-of-options delimiter
|
||||
const vector<const char*> v3({
|
||||
"FoobarProg", "--long-option-without-arg", "-aval", "--"});
|
||||
const auto res3 = parser.parseArgs(v3.size(), &v3[0]);
|
||||
const auto& opts3 = res3.first;
|
||||
const auto& otherArgs3 = res3.second;
|
||||
|
||||
SG_CHECK_EQUAL(opts3.size(), 2);
|
||||
SG_VERIFY(otherArgs3.empty());
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(otherArgs3, vector<string>());
|
||||
|
||||
// Test 4: only non-option arguments
|
||||
const vector<const char*> v4({
|
||||
"FoobarProg", "non option 1",
|
||||
"non option 2", "--", "non option 3"});
|
||||
const auto res4 = parser.parseArgs(v4.size(), &v4[0]);
|
||||
const auto& opts4 = res4.first;
|
||||
const auto& otherArgs4 = res4.second;
|
||||
|
||||
SG_VERIFY(opts4.empty());
|
||||
SG_CHECK_EQUAL(otherArgs4.size(), 4);
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(
|
||||
otherArgs4,
|
||||
vector<string>({"non option 1", "non option 2", "--", "non option 3"}));
|
||||
|
||||
// Test 5: only non-options arguments, but starting with --
|
||||
const vector<const char*> v5({
|
||||
"FoobarProg", "--", "non option 1",
|
||||
"non option 2", "--", "non option 3"});
|
||||
const auto res5 = parser.parseArgs(v5.size(), &v5[0]);
|
||||
const auto& opts5 = res5.first;
|
||||
const auto& otherArgs5 = res5.second;
|
||||
|
||||
SG_VERIFY(opts5.empty());
|
||||
SG_CHECK_EQUAL(otherArgs5.size(), 4);
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(
|
||||
otherArgs5,
|
||||
vector<string>({"non option 1", "non option 2", "--", "non option 3"}));
|
||||
|
||||
// Test 6: use the '--' delimiter before what would otherwise be considered
|
||||
// an option
|
||||
const vector<const char*> v6({
|
||||
"FoobarProg", "--long-option-without-arg", "-aval", "--", "-T",
|
||||
"non option 1", "non option 2", "--", "non option 3"});
|
||||
const auto res6 = parser.parseArgs(v6.size(), &v6[0]);
|
||||
const auto& opts6 = res6.first;
|
||||
const auto& otherArgs6 = res6.second;
|
||||
|
||||
SG_CHECK_EQUAL(opts6.size(), 2);
|
||||
SG_CHECK_EQUAL(otherArgs6.size(), 5);
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(
|
||||
otherArgs6,
|
||||
vector<string>({"-T", "non option 1", "non option 2", "--",
|
||||
"non option 3"}));
|
||||
|
||||
// Test 7: use the '--' delimiter before an argument that doesn't look like
|
||||
// an option
|
||||
const vector<const char*> v7({
|
||||
"FoobarProg", "--long-option-without-arg", "-aval", "--",
|
||||
"doesn't look like an option", "non option 1", "non option 2", "--",
|
||||
"non option 3"});
|
||||
const auto res7 = parser.parseArgs(v7.size(), &v7[0]);
|
||||
const auto& opts7 = res7.first;
|
||||
const auto& otherArgs7 = res7.second;
|
||||
|
||||
SG_CHECK_EQUAL(opts7.size(), 2);
|
||||
SG_CHECK_EQUAL(otherArgs7.size(), 5);
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(
|
||||
otherArgs7,
|
||||
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"});
|
||||
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());
|
||||
}
|
||||
|
||||
void test_optionsWithMultipleAliases()
|
||||
{
|
||||
cout << "Testing options with multiple aliases" << endl;
|
||||
|
||||
using namespace simgear::argparse;
|
||||
ArgumentParser parser;
|
||||
|
||||
parser.addOption("option -o", OptionArgType::OPTIONAL_ARGUMENT,
|
||||
vector<string>({"-o", "-O", "-0"}),
|
||||
vector<string>({"--o-alias-1", "--o-alias-2"}));
|
||||
parser.addOption("option -a", OptionArgType::MANDATORY_ARGUMENT,
|
||||
vector<string>({"-a", "-r"}),
|
||||
vector<string>({"--a-alias-1", "--a-alias-2",
|
||||
"--a-alias-3"}));
|
||||
parser.addOption("option -N", OptionArgType::NO_ARGUMENT,
|
||||
vector<string>({"-N", "-p"}),
|
||||
vector<string>({"--N-alias-1", "--N-alias-2"}));
|
||||
|
||||
const vector<const char*> v({
|
||||
"FoobarProg", "--o-alias-1", "-aarg for -a", "-pO", "arg for -O",
|
||||
"--a-alias-2=value 1", "--o-alias-2", "value 2", "-Novalue 3",
|
||||
"--N-alias-2", "--a-alias-3=value 4", "-0value 5", "--N-alias-1",
|
||||
"non option 1", "non option 2", "non option 3"});
|
||||
// v.size() corresponds to argc, &v[0] corresponds to argv.
|
||||
const auto res = parser.parseArgs(v.size(), &v[0]);
|
||||
const auto& opts = res.first;
|
||||
const auto& otherArgs = res.second;
|
||||
|
||||
SG_CHECK_EQUAL(opts.size(), 12); // number of passed options
|
||||
SG_CHECK_EQUAL(otherArgs.size(), 3); // number of non-option arguments
|
||||
|
||||
SG_CHECK_EQUAL(opts[0].passedAs(), "--o-alias-1");
|
||||
SG_CHECK_EQUAL(opts[0].value(), "");
|
||||
SG_CHECK_EQUAL(opts[0].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[0].id(), "option -o");
|
||||
|
||||
SG_CHECK_EQUAL(opts[1].passedAs(), "-a");
|
||||
SG_CHECK_EQUAL(opts[1].value(), "arg for -a");
|
||||
SG_CHECK_EQUAL(opts[1].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[1].id(), "option -a");
|
||||
|
||||
SG_CHECK_EQUAL(opts[2].passedAs(), "-p");
|
||||
SG_CHECK_EQUAL(opts[2].value(), "");
|
||||
SG_CHECK_EQUAL(opts[2].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[2].id(), "option -N");
|
||||
|
||||
SG_CHECK_EQUAL(opts[3].passedAs(), "-O");
|
||||
SG_CHECK_EQUAL(opts[3].value(), "arg for -O");
|
||||
SG_CHECK_EQUAL(opts[3].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[3].id(), "option -o");
|
||||
|
||||
SG_CHECK_EQUAL(opts[4].passedAs(), "--a-alias-2");
|
||||
SG_CHECK_EQUAL(opts[4].value(), "value 1");
|
||||
SG_CHECK_EQUAL(opts[4].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[4].id(), "option -a");
|
||||
|
||||
SG_CHECK_EQUAL(opts[5].passedAs(), "--o-alias-2");
|
||||
SG_CHECK_EQUAL(opts[5].value(), "value 2");
|
||||
SG_CHECK_EQUAL(opts[5].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[5].id(), "option -o");
|
||||
|
||||
SG_CHECK_EQUAL(opts[6].passedAs(), "-N");
|
||||
SG_CHECK_EQUAL(opts[6].value(), "");
|
||||
SG_CHECK_EQUAL(opts[6].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[6].id(), "option -N");
|
||||
|
||||
SG_CHECK_EQUAL(opts[7].passedAs(), "-o");
|
||||
SG_CHECK_EQUAL(opts[7].value(), "value 3");
|
||||
SG_CHECK_EQUAL(opts[7].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[7].id(), "option -o");
|
||||
|
||||
SG_CHECK_EQUAL(opts[8].passedAs(), "--N-alias-2");
|
||||
SG_CHECK_EQUAL(opts[8].value(), "");
|
||||
SG_CHECK_EQUAL(opts[8].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[8].id(), "option -N");
|
||||
|
||||
SG_CHECK_EQUAL(opts[9].passedAs(), "--a-alias-3");
|
||||
SG_CHECK_EQUAL(opts[9].value(), "value 4");
|
||||
SG_CHECK_EQUAL(opts[9].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[9].id(), "option -a");
|
||||
|
||||
SG_CHECK_EQUAL(opts[10].passedAs(), "-0");
|
||||
SG_CHECK_EQUAL(opts[10].value(), "value 5");
|
||||
SG_CHECK_EQUAL(opts[10].hasValue(), true);
|
||||
SG_CHECK_EQUAL(opts[10].id(), "option -o");
|
||||
|
||||
SG_CHECK_EQUAL(opts[11].passedAs(), "--N-alias-1");
|
||||
SG_CHECK_EQUAL(opts[11].value(), "");
|
||||
SG_CHECK_EQUAL(opts[11].hasValue(), false);
|
||||
SG_CHECK_EQUAL(opts[11].id(), "option -N");
|
||||
|
||||
SG_CHECK_EQUAL_NOSTREAM(
|
||||
otherArgs,
|
||||
vector<string>({"non option 1", "non option 2", "non option 3"}));
|
||||
}
|
||||
|
||||
// Auxiliary function used by test_invalidOptionOrArgumentMissing()
|
||||
void aux_invalidOptionOrMissingArgument_checkRaiseExcecption(
|
||||
const simgear::argparse::ArgumentParser& parser,
|
||||
const vector<const char*>& v)
|
||||
{
|
||||
bool gotException = false;
|
||||
|
||||
try {
|
||||
parser.parseArgs(v.size(), &v[0]);
|
||||
} catch (const simgear::argparse::Error&) {
|
||||
gotException = true;
|
||||
}
|
||||
|
||||
SG_VERIFY(gotException);
|
||||
}
|
||||
|
||||
void test_invalidOptionOrMissingArgument()
|
||||
{
|
||||
cout << "Testing passing invalid options and other syntax errors" << endl;
|
||||
|
||||
using simgear::argparse::OptionArgType;
|
||||
|
||||
simgear::argparse::ArgumentParser parser;
|
||||
parser.addOption("option -o", OptionArgType::OPTIONAL_ARGUMENT, "-o");
|
||||
parser.addOption("option -m", OptionArgType::MANDATORY_ARGUMENT,
|
||||
"-m", "--mandatory-arg");
|
||||
parser.addOption("option -n", OptionArgType::NO_ARGUMENT, "-n", "--no-arg");
|
||||
|
||||
const vector<vector<const char*> > listOfArgvs({
|
||||
{"FoobarProg", "-ovalue", "-n", "-X",
|
||||
"non option 1", "non option 2", "non option 3"},
|
||||
{"FoobarProg", "-ovalue", "-nXn",
|
||||
"non option 1", "non option 2", "non option 3"},
|
||||
{"FoobarProg", "-ovalue", "-n", "--non-existent-option",
|
||||
"non option 1", "non option 2", "non option 3"},
|
||||
{"FoobarProg", "-ovalue", "-n", "--non-existent-option=value",
|
||||
"non option 1", "non option 2", "non option 3"},
|
||||
{"FoobarProg", "-ovalue", "-n", "-m", "--",
|
||||
"non option 1", "non option 2", "non option 3"},
|
||||
{"FoobarProg", "-ovalue", "-n", "-X", "-m"},
|
||||
{"FoobarProg", "-ovalue", "-n", "--mandatory-arg", "--",
|
||||
"non option 1", "non option 2", "non option 3"},
|
||||
{"FoobarProg", "-ovalue", "-n", "--mandatory-arg"}
|
||||
});
|
||||
|
||||
for (const auto& argv: listOfArgvs) {
|
||||
aux_invalidOptionOrMissingArgument_checkRaiseExcecption(parser, argv);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, const char *const *argv)
|
||||
{
|
||||
test_mixOfShortAndLongOptions();
|
||||
test_frontierBetweenOptionsAndNonOptions();
|
||||
test_optionsWithMultipleAliases();
|
||||
test_invalidOptionOrMissingArgument();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user