Rewrite argument parsing
This commit is contained in:
parent
4f87cc1181
commit
526cbc5282
|
@ -58,6 +58,7 @@ set(TEST_SRC_FILES
|
|||
tests/conversiontests.cpp
|
||||
tests/iotests.cpp
|
||||
tests/chronotests.cpp
|
||||
tests/argumentparsertests.cpp
|
||||
)
|
||||
|
||||
set(CMAKE_MODULE_FILES
|
||||
|
|
|
@ -48,7 +48,7 @@ make DESTDIR="/temporary/install/location" install
|
|||
Building for Windows with Mingw-w64 cross compiler can be utilized using a small
|
||||
[cmake wrapper from Fedora](https://aur.archlinux.org/cgit/aur.git/tree/mingw-cmake.sh?h=mingw-w64-cmake):
|
||||
```
|
||||
${_arch}-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="/final/install/location" "path/to/projectdirectory"
|
||||
${_arch}-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="/final/install/location" "path/to/source/directory"
|
||||
make DESTDIR="/temporary/install/location" install-mingw-w64-strip
|
||||
```
|
||||
* To create the \*.ico file for the application icon ffmpeg/avconv is required.
|
||||
|
@ -72,5 +72,4 @@ PKGBUILD files to build for Windows using the Mingw-w64 compiler are also includ
|
|||
is currently disabled. Linking against cppunit built using new libstdc++ ABI isn't possible.
|
||||
|
||||
## TODO
|
||||
- rewrite argument parser (the API might change slightly)
|
||||
- remove unused features
|
||||
|
|
|
@ -6,20 +6,23 @@
|
|||
#include "../misc/random.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::placeholders;
|
||||
using namespace ConversionUtilities;
|
||||
|
||||
/*!
|
||||
\namespace ApplicationUtilities
|
||||
\brief Contains currently only ArgumentParser and related classes.
|
||||
*/
|
||||
* \namespace ApplicationUtilities
|
||||
* \brief Contains currently only ArgumentParser and related classes.
|
||||
*/
|
||||
namespace ApplicationUtilities {
|
||||
|
||||
/// \cond
|
||||
|
||||
const char *applicationName = nullptr;
|
||||
const char *applicationAuthor = nullptr;
|
||||
const char *applicationVersion = nullptr;
|
||||
|
@ -30,6 +33,8 @@ inline bool notEmpty(const char *str)
|
|||
return str && *str;
|
||||
}
|
||||
|
||||
/// \endcond
|
||||
|
||||
/*!
|
||||
* \class ApplicationUtilities::Argument
|
||||
* \brief The Argument class is a wrapper for command line argument information.
|
||||
|
@ -46,18 +51,17 @@ inline bool notEmpty(const char *str)
|
|||
* The \a name and the abbreviation mustn't contain any whitespaces.
|
||||
* The \a name mustn't be empty. The \a abbreviation and the \a description might be empty.
|
||||
*/
|
||||
Argument::Argument(const char *name, const char *abbreviation, const char *description, const char *example) :
|
||||
Argument::Argument(const char *name, char abbreviation, const char *description, const char *example) :
|
||||
m_name(name),
|
||||
m_abbreviation(abbreviation),
|
||||
m_description(description),
|
||||
m_example(example),
|
||||
m_required(false),
|
||||
m_minOccurrences(0),
|
||||
m_maxOccurrences(1),
|
||||
m_combinable(false),
|
||||
m_implicit(false),
|
||||
m_denotesOperation(false),
|
||||
m_requiredValueCount(0),
|
||||
m_default(false),
|
||||
m_present(false),
|
||||
m_isMainArg(false)
|
||||
{}
|
||||
|
||||
|
@ -74,42 +78,41 @@ void Argument::printInfo(ostream &os, unsigned char indentionLevel) const
|
|||
{
|
||||
for(unsigned char i = 0; i < indentionLevel; ++i) os << " ";
|
||||
if(notEmpty(name())) {
|
||||
os << "--" << name();
|
||||
os << '-' << '-' << name();
|
||||
}
|
||||
if(notEmpty(name()) && notEmpty(abbreviation())) {
|
||||
os << ", ";
|
||||
if(notEmpty(name()) && abbreviation()) {
|
||||
os << ',' << ' ';
|
||||
}
|
||||
if(notEmpty(abbreviation())) {
|
||||
os << "-" << abbreviation();
|
||||
if(abbreviation()) {
|
||||
os << '-' << abbreviation();
|
||||
}
|
||||
if(requiredValueCount() > 0) {
|
||||
int valueNamesPrint = 0;
|
||||
if(requiredValueCount() != 0) {
|
||||
unsigned int valueNamesPrint = 0;
|
||||
for(auto i = valueNames().cbegin(), end = valueNames().cend(); i != end && valueNamesPrint < requiredValueCount(); ++i) {
|
||||
os << " [" << *i << "]";
|
||||
os << ' ' << '[' << *i << ']';
|
||||
++valueNamesPrint;
|
||||
}
|
||||
for(; valueNamesPrint < requiredValueCount(); ++valueNamesPrint) {
|
||||
os << " [value " << (valueNamesPrint + 1) << "]";
|
||||
}
|
||||
} else if(requiredValueCount() < 0) {
|
||||
for(auto i = valueNames().cbegin(), end = valueNames().cend(); i != end; ++i) {
|
||||
os << " [" << *i << "]";
|
||||
}
|
||||
if(requiredValueCount() == static_cast<size_t>(-1)) {
|
||||
os << " ...";
|
||||
} else {
|
||||
for(; valueNamesPrint < requiredValueCount(); ++valueNamesPrint) {
|
||||
os << " [value " << (valueNamesPrint + 1) << ']';
|
||||
}
|
||||
}
|
||||
}
|
||||
++indentionLevel;
|
||||
if(notEmpty(description())) {
|
||||
os << endl;
|
||||
for(unsigned char i = 0; i < indentionLevel; ++i) os << " ";
|
||||
for(unsigned char i = 0; i < indentionLevel; ++i) os << ' ' << ' ';
|
||||
os << description();
|
||||
}
|
||||
if(isRequired()) {
|
||||
os << endl;
|
||||
for(unsigned char i = 0; i < indentionLevel; ++i) os << " ";
|
||||
for(unsigned char i = 0; i < indentionLevel; ++i) os << ' ' << ' ';
|
||||
os << "This argument is required.";
|
||||
}
|
||||
if(notEmpty(example())) {
|
||||
for(unsigned char i = 0; i < indentionLevel; ++i) os << " ";
|
||||
for(unsigned char i = 0; i < indentionLevel; ++i) os << ' ' << ' ';
|
||||
os << endl << "Usage: " << example();
|
||||
}
|
||||
os << endl;
|
||||
|
@ -182,31 +185,15 @@ void Argument::addSubArgument(Argument *arg)
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the names of the parents in the form "parent1", "parent2, "parent3", ...
|
||||
* Returns an empty string if this Argument has no parents.
|
||||
* \sa parents()
|
||||
*/
|
||||
string Argument::parentNames() const
|
||||
{
|
||||
string res;
|
||||
if(m_parents.size()) {
|
||||
vector<string> names;
|
||||
names.reserve(m_parents.size());
|
||||
for(const Argument *parent : m_parents) {
|
||||
names.push_back(parent->name());
|
||||
}
|
||||
res.assign(ConversionUtilities::joinStrings(names, ", "));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns true if at least one of the parents is present.
|
||||
* Returns false if this argument has no parents or none of its parents is present.
|
||||
* \brief Returns whether at least one parent argument is present.
|
||||
* \remarks Returns always true for main arguments.
|
||||
*/
|
||||
bool Argument::isParentPresent() const
|
||||
{
|
||||
for(Argument *parent : m_parents) {
|
||||
if(isMainArgument()) {
|
||||
return true;
|
||||
}
|
||||
for(const Argument *parent : m_parents) {
|
||||
if(parent->isPresent()) {
|
||||
return true;
|
||||
}
|
||||
|
@ -234,6 +221,15 @@ Argument *Argument::conflictsWithArgument() const
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Resets occurrences and values.
|
||||
*/
|
||||
void Argument::reset()
|
||||
{
|
||||
m_indices.clear();
|
||||
m_values.clear();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \class ApplicationUtilities::ArgumentParser
|
||||
* \brief The ArgumentParser class provides a means for handling command line arguments.
|
||||
|
@ -252,6 +248,7 @@ Argument *Argument::conflictsWithArgument() const
|
|||
*/
|
||||
ArgumentParser::ArgumentParser() :
|
||||
m_actualArgc(0),
|
||||
m_currentDirectory(nullptr),
|
||||
m_ignoreUnknownArgs(false)
|
||||
{}
|
||||
|
||||
|
@ -338,249 +335,216 @@ Argument *ArgumentParser::findArg(const ArgumentVector &arguments, const Argumen
|
|||
return nullptr; // no argument matches
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Parses the specified command line arguments.
|
||||
* \remarks
|
||||
* - The results are stored in the Argument instances assigned as main arguments and sub arguments.
|
||||
* - Calls the assigned callbacks if no constraints are violated.
|
||||
* \throws Throws Failure if the specified arguments violate the constraints defined
|
||||
* by the Argument instances.
|
||||
*/
|
||||
void ArgumentParser::parseArgs(int argc, const char *argv[])
|
||||
{
|
||||
IF_DEBUG_BUILD(verifyArgs(m_mainArgs);)
|
||||
m_actualArgc = 0;
|
||||
if(argc > 0) {
|
||||
m_currentDirectory = *argv;
|
||||
size_t index = 0;
|
||||
++argv;
|
||||
readSpecifiedArgs(m_mainArgs, index, argv, argv + argc - 1);
|
||||
checkConstraints(m_mainArgs);
|
||||
invokeCallbacks(m_mainArgs);
|
||||
} else {
|
||||
m_currentDirectory = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_BUILD
|
||||
/*!
|
||||
* \brief This method is used to verify the setup of the command line parser before parsing.
|
||||
* \brief Verifies the specified \a argument definitions.
|
||||
*
|
||||
* This function will throw std::invalid_argument when a mismatch is detected:
|
||||
* - An argument is used as main argument and as sub argument at the same time.
|
||||
* - An argument abbreviation is used more then once.
|
||||
* - An argument name is used more then once.
|
||||
* If none of these conditions are met, this method will do nothing.
|
||||
* Asserts that
|
||||
* - The same argument has not been added twice to the same parent.
|
||||
* - Only one argument within a parent is default or implicit.
|
||||
* - Only main arguments denote operations.
|
||||
* - Argument abbreviations are unique within one parent.
|
||||
* - Argument names are unique within one parent.
|
||||
*
|
||||
* \remarks Usually you don't need to call this function manually because it is called by
|
||||
* parse() automatically befor the parsing is performed.
|
||||
* \remarks
|
||||
* - Verifies the sub arguments, too.
|
||||
* - For debugging purposes only; hence only available in debug builds.
|
||||
*/
|
||||
void ArgumentParser::verifySetup() const
|
||||
void ApplicationUtilities::ArgumentParser::verifyArgs(const ArgumentVector &args)
|
||||
{
|
||||
vector<Argument *> verifiedArgs;
|
||||
vector<string> abbreviations;
|
||||
vector<const Argument *> verifiedArgs;
|
||||
verifiedArgs.reserve(args.size());
|
||||
vector<char> abbreviations;
|
||||
abbreviations.reserve(args.size());
|
||||
vector<string> names;
|
||||
const Argument *implicitArg = nullptr;
|
||||
function<void (const ArgumentVector &args)> checkArguments;
|
||||
checkArguments = [&verifiedArgs, &abbreviations, &names, &checkArguments, &implicitArg, this] (const ArgumentVector &args) {
|
||||
for(Argument *arg : args) {
|
||||
if(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) != verifiedArgs.cend()) {
|
||||
continue; // do not verify the same argument twice
|
||||
}
|
||||
if(arg->isMainArgument() && arg->parents().size()) {
|
||||
throw invalid_argument("Argument \"" + string(arg->name()) + "\" can not be used as main argument and sub argument at the same time.");
|
||||
}
|
||||
if(notEmpty(arg->abbreviation()) && find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) != abbreviations.cend()) {
|
||||
throw invalid_argument("Abbreviation \"" + string(arg->abbreviation()) + "\" has been used more then once.");
|
||||
}
|
||||
if(find(names.cbegin(), names.cend(), arg->name()) != names.cend()) {
|
||||
throw invalid_argument("Name \"" + string(arg->name()) + "\" has been used more then once.");
|
||||
}
|
||||
if(arg->isDefault() && arg->requiredValueCount() > 0 && arg->defaultValues().size() < static_cast<size_t>(arg->requiredValueCount())) {
|
||||
throw invalid_argument("Default argument \"" + string(arg->name()) + "\" doesn't provide the required number of default values.");
|
||||
}
|
||||
if(arg->isImplicit()) {
|
||||
if(implicitArg) {
|
||||
throw invalid_argument("The arguments \"" + string(implicitArg->name()) + "\" and \"" + string(arg->name()) + "\" can not be both implicit.");
|
||||
} else {
|
||||
implicitArg = arg;
|
||||
}
|
||||
}
|
||||
if(arg->abbreviation()) {
|
||||
abbreviations.push_back(arg->abbreviation());
|
||||
}
|
||||
if(arg->name()) {
|
||||
names.push_back(arg->name());
|
||||
}
|
||||
names.reserve(args.size());
|
||||
bool hasDefault = false;
|
||||
for(const Argument *arg : args) {
|
||||
assert(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) == verifiedArgs.cend());
|
||||
verifiedArgs.push_back(arg);
|
||||
checkArguments(arg->subArguments());
|
||||
assert(arg->isMainArgument() || !arg->denotesOperation());
|
||||
assert(!arg->isDefault() || !hasDefault);
|
||||
hasDefault |= arg->isDefault();
|
||||
assert(!arg->abbreviation() || find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) == abbreviations.cend());
|
||||
abbreviations.push_back(arg->abbreviation());
|
||||
assert(!arg->name() || find(names.cbegin(), names.cend(), arg->name()) == names.cend());
|
||||
assert(arg->requiredValueCount() == 0 || arg->subArguments().size() == 0);
|
||||
names.emplace_back(arg->name());
|
||||
verifyArgs(arg->subArguments());
|
||||
}
|
||||
};
|
||||
checkArguments(m_mainArgs);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* \brief This method invokes verifySetup() before parsing. See its do documentation for more
|
||||
* information about execptions that might be thrown to indicate an invalid setup of the parser.
|
||||
*
|
||||
* If the parser is setup properly this method will parse the given command line arguments using
|
||||
* the previsously set argument definitions.
|
||||
* If one of the previsously defined arguments has been found, its present flag will be set to true
|
||||
* and the parser reads all values tied to this argument.
|
||||
* If an argument has been found which does not match any of the previous definitions it will be
|
||||
* considered as unknown.
|
||||
* If the given command line arguments are not valid according the defined arguments an
|
||||
* Failure will be thrown.
|
||||
* \brief Reads the specified commands line arguments.
|
||||
* \remarks Results are stored in Argument instances added as main arguments and sub arguments.
|
||||
*/
|
||||
void ArgumentParser::parseArgs(int argc, char *argv[])
|
||||
void ArgumentParser::readSpecifiedArgs(ArgumentVector &args, std::size_t &index, const char **&argv, const char **end)
|
||||
{
|
||||
// initiate parser
|
||||
IF_DEBUG_BUILD(verifySetup();)
|
||||
m_actualArgc = 0; // reset actual agument count
|
||||
unsigned int actualArgc = 0;
|
||||
int valuesToRead = 0;
|
||||
// read current directory
|
||||
if(argc >= 1) {
|
||||
m_currentDirectory = string(*argv);
|
||||
enum ArgumentDenotationType : unsigned char {
|
||||
Value = 0, // parameter value
|
||||
Abbreviation = 1, // argument abbreviation
|
||||
FullName = 2 // full argument name
|
||||
};
|
||||
|
||||
bool isTopLevel = index == 0;
|
||||
Argument *lastArg = nullptr;
|
||||
vector<const char *> *values = nullptr;
|
||||
while(argv != end) {
|
||||
if(values && lastArg->requiredValueCount() != static_cast<size_t>(-1) && values->size() < lastArg->requiredValueCount()) {
|
||||
// there are still values to read
|
||||
values->emplace_back(*argv);
|
||||
++index, ++argv;
|
||||
} else {
|
||||
m_currentDirectory.clear();
|
||||
}
|
||||
// function for iterating through all arguments
|
||||
function<void(Argument *, const ArgumentVector &, const function<void (Argument *, Argument *)> &)> foreachArg;
|
||||
foreachArg = [&foreachArg] (Argument *parent, const ArgumentVector &args, const function<void (Argument *, Argument *)> &proc) {
|
||||
// determine denotation type
|
||||
const char *argDenotation = *argv;
|
||||
bool abbreviationFound = false;
|
||||
unsigned char argDenotationType = Value;
|
||||
*argDenotation == '-' && (++argDenotation, ++argDenotationType)
|
||||
&& *argDenotation == '-' && (++argDenotation, ++argDenotationType);
|
||||
|
||||
// try to find matching Argument instance
|
||||
Argument *matchingArg = nullptr;
|
||||
if(argDenotationType != Value) {
|
||||
const char *const equationPos = strchr(argDenotation, '=');
|
||||
for(const auto argDenLen = equationPos ? static_cast<size_t>(equationPos - argDenotation) : strlen(argDenotation); ; matchingArg = nullptr) {
|
||||
// search for arguments by abbreviation or name depending on the denotation type
|
||||
if(argDenotationType == Abbreviation) {
|
||||
for(Argument *arg : args) {
|
||||
proc(parent, arg);
|
||||
foreachArg(arg, arg->subArguments(), proc);
|
||||
}
|
||||
};
|
||||
// parse given arguments
|
||||
if(argc >= 2) {
|
||||
Argument *currentArg = nullptr;
|
||||
// iterate through given arguments
|
||||
for(char **i = argv + 1, **end = argv + argc; i != end; ++i) {
|
||||
string givenArg(*i); // convert argument to string
|
||||
if(!givenArg.empty()) { // skip empty entries
|
||||
if(valuesToRead <= 0 && givenArg.size() > 1 && givenArg.front() == '-') {
|
||||
// we have no values left to read and the given arguments starts with '-'
|
||||
// -> the next "value" is a main argument or a sub argument
|
||||
ArgumentPredicate pred;
|
||||
string givenId;
|
||||
size_t equationPos = givenArg.find('=');
|
||||
if(givenArg.size() > 2 && givenArg[1] == '-') {
|
||||
// the argument starts with '--'
|
||||
// -> the full argument name has been provided
|
||||
givenId = givenArg.substr(2, equationPos - 2);
|
||||
pred = [&givenId, equationPos] (Argument *arg) {
|
||||
return arg->name() && arg->name() == givenId;
|
||||
};
|
||||
} else {
|
||||
// the argument starts with a single '-'
|
||||
// -> the abbreviation has been provided
|
||||
givenId = givenArg.substr(1, equationPos - 1);
|
||||
pred = [&givenId, equationPos] (Argument *arg) {
|
||||
return arg->abbreviation() && arg->abbreviation() == givenId;
|
||||
};
|
||||
}
|
||||
// find the corresponding instance of the Argument class
|
||||
currentArg = findArg(pred);
|
||||
if(currentArg) {
|
||||
// the corresponding instance of Argument class has been found
|
||||
if(currentArg->m_present) {
|
||||
// the argument has been provided more then once
|
||||
throw Failure("The argument \"" + string(currentArg->name()) + "\" has been specified more than one time.");
|
||||
} else {
|
||||
// set present flag of argument
|
||||
currentArg->m_present = true;
|
||||
++actualArgc; // we actually found an argument
|
||||
// now we might need to read values tied to that argument
|
||||
valuesToRead = currentArg->requiredValueCount();
|
||||
if(equationPos != string::npos) {
|
||||
// a value has been specified using the --argument=value syntax
|
||||
string value = givenArg.substr(equationPos + 1);
|
||||
if(valuesToRead != 0) {
|
||||
currentArg->m_values.push_back(value);
|
||||
if(valuesToRead > 0) {
|
||||
--valuesToRead;
|
||||
}
|
||||
} else {
|
||||
throw Failure("Invalid extra information \"" + value + "\" (specified using \"--argument=value\" syntax) for the argument \"" + currentArg->name() + "\" given.");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// the given argument seems to be unknown
|
||||
if(valuesToRead < 0) {
|
||||
// we have a variable number of values to expect -> treat "unknown argument" as value
|
||||
goto readValue;
|
||||
} else {
|
||||
// we have no more values to expect so we need to complain about the unknown argument
|
||||
goto invalidArg;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
readValue:
|
||||
if(!currentArg) {
|
||||
// we have not parsed an argument before
|
||||
// -> check if an argument which denotes the operation is specified
|
||||
if(i == argv + 1) {
|
||||
for(Argument *arg : m_mainArgs) {
|
||||
if(!arg->isPresent() && arg->denotesOperation()
|
||||
&& ((arg->name() && arg->name() == givenArg) || (arg->abbreviation() && arg->abbreviation() == givenArg))) {
|
||||
currentArg = arg;
|
||||
if(arg->abbreviation() && arg->abbreviation() == *argDenotation) {
|
||||
matchingArg = arg;
|
||||
abbreviationFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(currentArg) {
|
||||
currentArg->m_present = true;
|
||||
++actualArgc; // we actually found an argument
|
||||
// now we might need to read values tied to that argument
|
||||
valuesToRead = currentArg->requiredValueCount();
|
||||
} else {
|
||||
for(Argument *arg : args) {
|
||||
if(arg->name() && !strncmp(arg->name(), argDenotation, argDenLen)) {
|
||||
matchingArg = arg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(matchingArg) {
|
||||
// an argument matched the specified denotation
|
||||
matchingArg->m_indices.push_back(index);
|
||||
|
||||
// prepare reading parameter values
|
||||
matchingArg->m_values.emplace_back();
|
||||
values = &matchingArg->m_values.back();
|
||||
if(equationPos) {
|
||||
values->push_back(equationPos + 1);
|
||||
}
|
||||
|
||||
// read sub arguments if no abbreviated argument follows
|
||||
++index, ++m_actualArgc, lastArg = matchingArg;
|
||||
if(argDenotationType != Abbreviation || (!*++argDenotation && argDenotation != equationPos)) {
|
||||
readSpecifiedArgs(matchingArg->m_subArgs, index, ++argv, end);
|
||||
break;
|
||||
} // else: another abbreviated argument follows
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!matchingArg) {
|
||||
if(lastArg && values->size() < lastArg->requiredValueCount()) {
|
||||
// treat unknown argument as parameter of last argument
|
||||
values->emplace_back(abbreviationFound ? argDenotation : *argv);
|
||||
++index, ++argv;
|
||||
continue;
|
||||
} else {
|
||||
// first value might denote "operation"
|
||||
if(isTopLevel) {
|
||||
for(Argument *arg : args) {
|
||||
if(arg->denotesOperation() && arg->name() && !strcmp(arg->name(), *argv)) {
|
||||
(matchingArg = arg)->m_indices.push_back(index);
|
||||
++index, ++argv;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!matchingArg) {
|
||||
// use the first default argument
|
||||
for(Argument *arg : args) {
|
||||
if(arg->isDefault()) {
|
||||
(matchingArg = arg)->m_indices.push_back(index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(matchingArg) {
|
||||
// an argument matched the specified denotation
|
||||
if(lastArg == matchingArg) {
|
||||
break;
|
||||
}
|
||||
|
||||
// prepare reading parameter values
|
||||
matchingArg->m_values.emplace_back();
|
||||
values = &matchingArg->m_values.back();
|
||||
|
||||
// read sub arguments if no abbreviated argument follows
|
||||
++m_actualArgc, lastArg = matchingArg;
|
||||
readSpecifiedArgs(matchingArg->m_subArgs, index, argv, end);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// -> check if there's an implicit argument definition
|
||||
try {
|
||||
foreachArg(nullptr, m_mainArgs, [&actualArgc, &valuesToRead, ¤tArg, this] (Argument *, Argument *arg) {
|
||||
if(!arg->isPresent() && arg->isImplicit()) {
|
||||
throw arg;
|
||||
}
|
||||
});
|
||||
} catch(Argument *arg) {
|
||||
// set present flag of argument
|
||||
arg->m_present = true;
|
||||
++actualArgc; // we actually found an argument
|
||||
// now we might need to read values tied to that argument
|
||||
valuesToRead = arg->requiredValueCount();
|
||||
currentArg = arg;
|
||||
}
|
||||
}
|
||||
if(currentArg) {
|
||||
// check if the given value is tied to the most recently parsed argument
|
||||
if(valuesToRead == 0) {
|
||||
throw Failure("Invalid extra information \"" + givenArg + "\" for the argument \"" + currentArg->name() + "\" given.");
|
||||
} else if(valuesToRead < 0) {
|
||||
currentArg->m_values.emplace_back(givenArg);
|
||||
} else {
|
||||
currentArg->m_values.emplace_back(givenArg);
|
||||
--valuesToRead; // one value less to be read
|
||||
}
|
||||
} else {
|
||||
// there is no implicit argument definition -> the "value" has to be an argument
|
||||
// but does not start with '-' or '--'
|
||||
invalidArg:
|
||||
string msg("The given argument \"" + givenArg + "\" is unknown.");
|
||||
if(isTopLevel) {
|
||||
if(m_ignoreUnknownArgs) {
|
||||
cout << msg << " It will be ignored." << endl;
|
||||
cerr << "The specified argument \"" << *argv << "\" is unknown and will be ignored." << endl;
|
||||
++index, ++argv;
|
||||
} else {
|
||||
throw Failure(msg);
|
||||
throw Failure("The specified argument \"" + string(*argv) + "\" is unknown and will be ignored.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// iterate actually through all arguments using previously defined function to check gathered arguments
|
||||
foreachArg(nullptr, m_mainArgs, [this] (Argument *parent, Argument *arg) {
|
||||
if(!arg->isPresent()) {
|
||||
// the argument might be flagged as present if its a default argument
|
||||
if(arg->isDefault() && (arg->isMainArgument() || (parent && parent->isPresent()))) {
|
||||
arg->m_present = true;
|
||||
if(firstPresentUncombinableArg(arg->isMainArgument() ? m_mainArgs : parent->subArguments(), arg)) {
|
||||
arg->m_present = false;
|
||||
}
|
||||
}
|
||||
// throw an error if mandatory argument is not present
|
||||
if(!arg->isPresent() && (arg->isRequired() && (arg->isMainArgument() || (parent && parent->isPresent())))) {
|
||||
throw Failure("The argument \"" + string(arg->name()) + "\" is required but not given.");
|
||||
}
|
||||
}
|
||||
});
|
||||
foreachArg(nullptr, m_mainArgs, [this] (Argument *, Argument *arg) {
|
||||
if(arg->isPresent()) {
|
||||
if(!arg->isMainArgument() && arg->parents().size() && !arg->isParentPresent()) {
|
||||
if(arg->parents().size() > 1) {
|
||||
throw Failure("The argument \"" + string(arg->name()) + "\" needs to be used together with one the following arguments: " + arg->parentNames());
|
||||
} else {
|
||||
throw Failure("The argument \"" + string(arg->name()) + "\" needs to be used together with the argument \"" + arg->parents().front()->name() + "\".");
|
||||
return; // unknown argument name or abbreviation found -> continue with parent level
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Checks the constrains of the specified \a args.
|
||||
* \remarks Checks the contraints of sub arguments, too.
|
||||
*/
|
||||
void ArgumentParser::checkConstraints(const ArgumentVector &args)
|
||||
{
|
||||
for(const Argument *arg : args) {
|
||||
const auto occurrences = arg->occurrences();
|
||||
if(arg->isParentPresent() && occurrences > arg->maxOccurrences()) {
|
||||
throw Failure("The argument \"" + string(arg->name()) + "\" mustn't be specified more than " + numberToString(arg->maxOccurrences()) + (arg->maxOccurrences() == 1 ? " time." : " times."));
|
||||
}
|
||||
if(arg->isParentPresent() && occurrences < arg->minOccurrences()) {
|
||||
throw Failure("The argument \"" + string(arg->name()) + "\" must be specified at least " + numberToString(arg->minOccurrences()) + (arg->minOccurrences() == 1 ? " time." : " times."));
|
||||
}
|
||||
Argument *conflictingArgument = nullptr;
|
||||
if(arg->isMainArgument()) {
|
||||
if(!arg->isCombinable() && arg->isPresent()) {
|
||||
|
@ -592,39 +556,56 @@ void ArgumentParser::parseArgs(int argc, char *argv[])
|
|||
if(conflictingArgument) {
|
||||
throw Failure("The argument \"" + string(conflictingArgument->name()) + "\" can not be combined with \"" + arg->name() + "\".");
|
||||
}
|
||||
if(!arg->allRequiredValuesPresent()) {
|
||||
for(size_t i = 0; i != occurrences; ++i) {
|
||||
if(!arg->allRequiredValuesPresent(occurrences)) {
|
||||
stringstream ss(stringstream::in | stringstream::out);
|
||||
ss << "Not all required information for the given argument \"" << string(arg->name()) << "\" provided. You have to give the following information:";
|
||||
int valueNamesPrint = 0;
|
||||
ss << "Not all parameter for argument \"" << arg->name() << "\" ";
|
||||
if(i) {
|
||||
ss << " (" << (i + 1) << " occurrence) ";
|
||||
}
|
||||
ss << "provided. You have to provide the following parameter:";
|
||||
size_t valueNamesPrint = 0;
|
||||
for(const auto &name : arg->m_valueNames) {
|
||||
ss << "\n" << name;
|
||||
++valueNamesPrint;
|
||||
}
|
||||
if(arg->m_requiredValueCount != static_cast<size_t>(-1)) {
|
||||
while(valueNamesPrint < arg->m_requiredValueCount) {
|
||||
ss << "\nvalue " << (++valueNamesPrint);
|
||||
}
|
||||
}
|
||||
throw Failure(ss.str());
|
||||
}
|
||||
}
|
||||
});
|
||||
// set actual argument count
|
||||
m_actualArgc = actualArgc;
|
||||
// interate through all arguments again to invoke callback functions
|
||||
foreachArg(nullptr, m_mainArgs, [] (Argument *parent, Argument *arg) {
|
||||
|
||||
// check contraints of sub arguments recursively
|
||||
checkConstraints(arg->m_subArgs);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Invokes the callbacks for the specified \a args.
|
||||
* \remarks
|
||||
* - Checks the callbacks for sub arguments, too.
|
||||
* - Invokes the assigned callback methods for each occurance of
|
||||
* the argument.
|
||||
*/
|
||||
void ArgumentParser::invokeCallbacks(const ArgumentVector &args)
|
||||
{
|
||||
for(const Argument *arg : args) {
|
||||
// invoke the callback for each occurance of the argument
|
||||
if(arg->m_callbackFunction) {
|
||||
if(arg->isMainArgument() || (parent && parent->isPresent())) {
|
||||
// only invoke if its a main argument or the parent is present
|
||||
if(arg->isPresent()) {
|
||||
if(arg->isDefault() && !arg->values().size()) {
|
||||
vector<string> defaultValues(arg->defaultValues().cbegin(), arg->defaultValues().cend());
|
||||
arg->m_callbackFunction(defaultValues);
|
||||
for(const auto &valuesOfOccurance : arg->m_values) {
|
||||
if(arg->isDefault() && valuesOfOccurance.empty()) {
|
||||
arg->m_callbackFunction(arg->defaultValues());
|
||||
} else {
|
||||
arg->m_callbackFunction(arg->values());
|
||||
arg->m_callbackFunction(valuesOfOccurance);
|
||||
}
|
||||
}
|
||||
}
|
||||
// invoke the callbacks for sub arguments recursively
|
||||
invokeCallbacks(arg->m_subArgs);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -637,9 +618,9 @@ void ArgumentParser::parseArgs(int argc, char *argv[])
|
|||
* \brief Constructs a new help argument for the specified parser.
|
||||
*/
|
||||
HelpArgument::HelpArgument(ArgumentParser &parser) :
|
||||
Argument("help", "h", "shows this information")
|
||||
Argument("help", 'h', "shows this information")
|
||||
{
|
||||
setCallback([&parser] (const std::vector<std::string> &) {
|
||||
setCallback([&parser] (const std::vector<const char *> &) {
|
||||
CMD_UTILS_START_CONSOLE;
|
||||
parser.printHelp(cout);
|
||||
});
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
|
||||
#include "./global.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <initializer_list>
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#ifdef DEBUG_BUILD
|
||||
# include <cassert>
|
||||
#endif
|
||||
|
||||
namespace ApplicationUtilities {
|
||||
|
||||
|
@ -37,35 +37,38 @@ class LIB_EXPORT Argument
|
|||
friend class ArgumentParser;
|
||||
|
||||
public:
|
||||
typedef std::function <void (const std::vector<std::string> &)> CallbackFunction;
|
||||
typedef std::function <void (const std::vector<const char *> &)> CallbackFunction;
|
||||
|
||||
Argument(const char *name, const char *abbreviation = nullptr, const char *description = nullptr, const char *example = nullptr);
|
||||
Argument(const char *name, char abbreviation = '\0', const char *description = nullptr, const char *example = nullptr);
|
||||
~Argument();
|
||||
|
||||
const char *name() const;
|
||||
void setName(const char *name);
|
||||
const char *abbreviation() const;
|
||||
void setAbbreviation(const char *abbreviation);
|
||||
char abbreviation() const;
|
||||
void setAbbreviation(char abbreviation);
|
||||
const char *description() const;
|
||||
void setDescription(const char *description);
|
||||
const char *example() const;
|
||||
void setExample(const char *example);
|
||||
const std::vector<std::string> &values() const;
|
||||
const std::string &value(std::size_t index) const;
|
||||
std::size_t valueCount() const;
|
||||
int requiredValueCount() const;
|
||||
void setRequiredValueCount(int requiredValueCount);
|
||||
const std::list<const char *> &valueNames() const;
|
||||
const std::vector<const char *> &values(std::size_t occurrance = 0) const;
|
||||
std::size_t requiredValueCount() const;
|
||||
void setRequiredValueCount(std::size_t requiredValueCount);
|
||||
const std::vector<const char *> &valueNames() const;
|
||||
void setValueNames(std::initializer_list<const char *> valueNames);
|
||||
void appendValueName(const char *valueName);
|
||||
bool allRequiredValuesPresent() const;
|
||||
bool allRequiredValuesPresent(std::size_t occurrance = 0) const;
|
||||
bool isDefault() const;
|
||||
void setDefault(bool value);
|
||||
const std::list<const char *> &defaultValues() const;
|
||||
void setDefaultValues(const std::list<const char *> &defaultValues);
|
||||
void setDefault(bool isDefault);
|
||||
const std::vector<const char *> &defaultValues() const;
|
||||
void setDefaultValues(const std::initializer_list<const char *> &defaultValues);
|
||||
bool isPresent() const;
|
||||
std::size_t occurrences() const;
|
||||
const std::vector<std::size_t> &indices() const;
|
||||
std::size_t minOccurrences() const;
|
||||
std::size_t maxOccurrences() const;
|
||||
void setConstraints(std::size_t minOccurrences, std::size_t maxOccurrences);
|
||||
bool isRequired() const;
|
||||
void setRequired(bool value);
|
||||
void setRequired(bool required);
|
||||
bool isCombinable() const;
|
||||
void setCombinable(bool value);
|
||||
bool isImplicit() const;
|
||||
|
@ -80,25 +83,25 @@ public:
|
|||
bool hasSubArguments() const;
|
||||
const ArgumentVector parents() const;
|
||||
bool isMainArgument() const;
|
||||
std::string parentNames() const;
|
||||
bool isParentPresent() const;
|
||||
Argument *conflictsWithArgument() const;
|
||||
void reset();
|
||||
|
||||
private:
|
||||
const char *m_name;
|
||||
const char *m_abbreviation;
|
||||
char m_abbreviation;
|
||||
const char *m_description;
|
||||
const char *m_example;
|
||||
bool m_required;
|
||||
std::size_t m_minOccurrences;
|
||||
std::size_t m_maxOccurrences;
|
||||
bool m_combinable;
|
||||
bool m_implicit;
|
||||
bool m_denotesOperation;
|
||||
int m_requiredValueCount;
|
||||
std::list<const char *> m_valueNames;
|
||||
std::size_t m_requiredValueCount;
|
||||
std::vector<const char *> m_valueNames;
|
||||
bool m_default;
|
||||
std::list<const char *> m_defaultValues;
|
||||
bool m_present;
|
||||
std::vector<std::string> m_values;
|
||||
std::vector<const char *> m_defaultValues;
|
||||
std::vector<std::size_t> m_indices;
|
||||
std::vector<std::vector<const char *> > m_values;
|
||||
ArgumentVector m_subArgs;
|
||||
CallbackFunction m_callbackFunction;
|
||||
ArgumentVector m_parents;
|
||||
|
@ -129,12 +132,7 @@ inline void Argument::setName(const char *name)
|
|||
#ifdef DEBUG_BUILD
|
||||
if(name && *name) {
|
||||
for(const char *c = name; *c; ++c) {
|
||||
switch(*c) {
|
||||
case ' ': case '=':
|
||||
throw std::invalid_argument("name mustn't contain white spaces or equation chars");
|
||||
default:
|
||||
;
|
||||
}
|
||||
assert(*c != ' ' && *c != '=');
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -147,7 +145,7 @@ inline void Argument::setName(const char *name)
|
|||
* The parser compares the abbreviation with the characters following a "-" prefix to
|
||||
* identify arguments.
|
||||
*/
|
||||
inline const char *Argument::abbreviation() const
|
||||
inline char Argument::abbreviation() const
|
||||
{
|
||||
return m_abbreviation;
|
||||
}
|
||||
|
@ -161,20 +159,9 @@ inline const char *Argument::abbreviation() const
|
|||
* The parser compares the abbreviation with the characters following a "-" prefix to
|
||||
* identify arguments.
|
||||
*/
|
||||
inline void Argument::setAbbreviation(const char *abbreviation)
|
||||
inline void Argument::setAbbreviation(char abbreviation)
|
||||
{
|
||||
#ifdef DEBUG_BUILD
|
||||
if(abbreviation && *abbreviation) {
|
||||
for(const char *c = abbreviation; *c; ++c) {
|
||||
switch(*c) {
|
||||
case ' ': case '=':
|
||||
throw std::invalid_argument("abbreviation mustn't contain white spaces or equation chars");
|
||||
default:
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
IF_DEBUG_BUILD(assert(abbreviation != ' ' && abbreviation != '='));
|
||||
m_abbreviation = abbreviation;
|
||||
}
|
||||
|
||||
|
@ -223,28 +210,9 @@ inline void Argument::setExample(const char *example)
|
|||
*
|
||||
* These values set by the parser when parsing the command line arguments.
|
||||
*/
|
||||
inline const std::vector<std::string> &Argument::values() const
|
||||
inline const std::vector<const char *> &Argument::values(std::size_t occurrance) const
|
||||
{
|
||||
return m_values;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the value with the give \a index.
|
||||
*
|
||||
* These values set by the parser when parsing the command line arguments.
|
||||
*/
|
||||
inline const std::string &Argument::value(std::size_t index) const
|
||||
{
|
||||
return m_values[index];
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns the number of values which could be found when parsing
|
||||
* the command line arguments.
|
||||
*/
|
||||
inline std::size_t Argument::valueCount() const
|
||||
{
|
||||
return m_values.size();
|
||||
return m_values[occurrance];
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -260,7 +228,7 @@ inline std::size_t Argument::valueCount() const
|
|||
* \sa valueNames()
|
||||
* \sa setValueNames()
|
||||
*/
|
||||
inline int Argument::requiredValueCount() const
|
||||
inline std::size_t Argument::requiredValueCount() const
|
||||
{
|
||||
return m_requiredValueCount;
|
||||
}
|
||||
|
@ -276,7 +244,7 @@ inline int Argument::requiredValueCount() const
|
|||
* \sa valueNames()
|
||||
* \sa setValueNames()
|
||||
*/
|
||||
inline void Argument::setRequiredValueCount(int requiredValueCount)
|
||||
inline void Argument::setRequiredValueCount(std::size_t requiredValueCount)
|
||||
{
|
||||
m_requiredValueCount = requiredValueCount;
|
||||
}
|
||||
|
@ -289,7 +257,7 @@ inline void Argument::setRequiredValueCount(int requiredValueCount)
|
|||
* \sa setValueNames()
|
||||
* \sa appendValueNames()
|
||||
*/
|
||||
inline const std::list<const char *> &Argument::valueNames() const
|
||||
inline const std::vector<const char *> &Argument::valueNames() const
|
||||
{
|
||||
return m_valueNames;
|
||||
}
|
||||
|
@ -326,14 +294,11 @@ inline void Argument::appendValueName(const char *valueName)
|
|||
/*!
|
||||
* \brief Returns an indication whether all required values are present.
|
||||
*/
|
||||
inline bool Argument::allRequiredValuesPresent() const
|
||||
inline bool Argument::allRequiredValuesPresent(std::size_t occurrance) const
|
||||
{
|
||||
if(m_requiredValueCount > 0) {
|
||||
return (m_values.size() >= static_cast<size_t>(m_requiredValueCount))
|
||||
|| (m_default && m_defaultValues.size() >= static_cast<size_t>(m_requiredValueCount));
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return m_requiredValueCount == static_cast<std::size_t>(-1)
|
||||
|| (m_values[occurrance].size() >= static_cast<std::size_t>(m_requiredValueCount))
|
||||
|| (m_default && m_defaultValues.size() >= static_cast<std::size_t>(m_requiredValueCount));
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -359,9 +324,9 @@ inline bool Argument::isDefault() const
|
|||
* \brief Sets whether the argument is a default argument.
|
||||
* \sa isDefault()
|
||||
*/
|
||||
inline void Argument::setDefault(bool value)
|
||||
inline void Argument::setDefault(bool isDefault)
|
||||
{
|
||||
m_default = value;
|
||||
m_default = isDefault;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -370,7 +335,7 @@ inline void Argument::setDefault(bool value)
|
|||
* \sa setDefault()
|
||||
* \sa setDefaultValues()
|
||||
*/
|
||||
inline const std::list<const char *> &Argument::defaultValues() const
|
||||
inline const std::vector<const char *> &Argument::defaultValues() const
|
||||
{
|
||||
return m_defaultValues;
|
||||
}
|
||||
|
@ -385,18 +350,64 @@ inline const std::list<const char *> &Argument::defaultValues() const
|
|||
* \sa setDefault()
|
||||
* \sa defaultValues()
|
||||
*/
|
||||
inline void Argument::setDefaultValues(const std::list<const char *> &defaultValues)
|
||||
inline void Argument::setDefaultValues(const std::initializer_list<const char *> &defaultValues)
|
||||
{
|
||||
m_defaultValues = defaultValues;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns an indication whether the argument could be detected
|
||||
* when parsing.
|
||||
* \brief Returns an indication whether the argument could be detected when parsing.
|
||||
*/
|
||||
inline bool Argument::isPresent() const
|
||||
{
|
||||
return m_present;
|
||||
return !m_indices.empty();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns how often the argument could be detected when parsing.
|
||||
*/
|
||||
inline std::size_t Argument::occurrences() const
|
||||
{
|
||||
return m_indices.size();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the indices of the argument's occurences which could be detected when parsing.
|
||||
*/
|
||||
inline const std::vector<std::size_t> &Argument::indices() const
|
||||
{
|
||||
return m_indices;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the minimum number of occurrences.
|
||||
*
|
||||
* If the argument occurs not that many times, the parser will complain.
|
||||
*/
|
||||
inline std::size_t Argument::minOccurrences() const
|
||||
{
|
||||
return m_minOccurrences;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the maximum number of occurrences.
|
||||
*
|
||||
* If the argument occurs more often, the parser will complain.
|
||||
*/
|
||||
inline std::size_t Argument::maxOccurrences() const
|
||||
{
|
||||
return m_maxOccurrences;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Sets the allowed number of occurrences.
|
||||
* \sa minOccurrences()
|
||||
* \sa maxOccurrences()
|
||||
*/
|
||||
inline void Argument::setConstraints(std::size_t minOccurrences, std::size_t maxOccurrences)
|
||||
{
|
||||
m_minOccurrences = minOccurrences;
|
||||
m_maxOccurrences = maxOccurrences;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -410,19 +421,25 @@ inline bool Argument::isPresent() const
|
|||
*/
|
||||
inline bool Argument::isRequired() const
|
||||
{
|
||||
return m_required;
|
||||
return m_minOccurrences;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Sets if this argument is mandatory or not.
|
||||
* \brief Sets whether this argument is mandatory or not.
|
||||
*
|
||||
* The parser will complain if a mandatory argument is not present.
|
||||
*
|
||||
* * \sa isRequired()
|
||||
*/
|
||||
inline void Argument::setRequired(bool value)
|
||||
inline void Argument::setRequired(bool required)
|
||||
{
|
||||
m_required = value;
|
||||
if(required) {
|
||||
if(!m_minOccurrences) {
|
||||
m_minOccurrences = 1;
|
||||
}
|
||||
} else {
|
||||
m_minOccurrences = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -451,30 +468,6 @@ inline void Argument::setCombinable(bool value)
|
|||
m_combinable = value;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Returns an indication whether the argument can be specified implicitely.
|
||||
*
|
||||
* An implicit argument is assumed to be present even if only its value is present.
|
||||
* Only one argument can be implicit.
|
||||
*
|
||||
* \sa setImplicit()
|
||||
*/
|
||||
inline bool Argument::isImplicit() const
|
||||
{
|
||||
return m_implicit;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Sets if this argument can be specified implicitely.
|
||||
*
|
||||
* \sa isImplicit()
|
||||
*/
|
||||
inline void Argument::setImplicit(bool value)
|
||||
{
|
||||
m_implicit = value;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns whether the argument denotes the operation.
|
||||
*
|
||||
|
@ -502,6 +495,7 @@ inline void Argument::setDenotesOperation(bool denotesOperation)
|
|||
/*!
|
||||
* \brief Sets a \a callback function which will be called by the parser if
|
||||
* the argument could be found and no parsing errors occured.
|
||||
* \remarks The \a callback will be called for each occurrance of the argument.
|
||||
*/
|
||||
inline void Argument::setCallback(Argument::CallbackFunction callback)
|
||||
{
|
||||
|
@ -567,19 +561,22 @@ public:
|
|||
void printHelp(std::ostream &os) const;
|
||||
Argument *findArg(const ArgumentPredicate &predicate) const;
|
||||
static Argument *findArg(const ArgumentVector &arguments, const ArgumentPredicate &predicate);
|
||||
#ifdef DEBUG_BUILD
|
||||
void verifySetup() const;
|
||||
#endif
|
||||
void parseArgs(int argc, char *argv[]);
|
||||
void parseArgs(int argc, const char *argv[]);
|
||||
unsigned int actualArgumentCount() const;
|
||||
const std::string ¤tDirectory() const;
|
||||
const char *currentDirectory() const;
|
||||
bool areUnknownArgumentsIgnored() const;
|
||||
void setIgnoreUnknownArguments(bool ignore);
|
||||
|
||||
private:
|
||||
IF_DEBUG_BUILD(void verifyArgs(const ArgumentVector &args);)
|
||||
void readSpecifiedArgs(ArgumentVector &args, std::size_t &index, const char **&argv, const char **end);
|
||||
void checkConstraints(const ArgumentVector &args);
|
||||
void invokeCallbacks(const ArgumentVector &args);
|
||||
|
||||
ArgumentVector m_mainArgs;
|
||||
unsigned int m_actualArgc;
|
||||
std::string m_currentDirectory;
|
||||
const char *m_currentDirectory;
|
||||
bool m_ignoreUnknownArgs;
|
||||
};
|
||||
|
||||
|
@ -592,6 +589,14 @@ inline const ArgumentVector &ArgumentParser::mainArguments() const
|
|||
return m_mainArgs;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Parses the specified command line arguments.
|
||||
*/
|
||||
inline void ArgumentParser::parseArgs(int argc, char *argv[])
|
||||
{
|
||||
parseArgs(argc, const_cast<const char **>(argv));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the actual number of arguments that could be found when parsing.
|
||||
*/
|
||||
|
@ -603,7 +608,7 @@ inline unsigned int ArgumentParser::actualArgumentCount() const
|
|||
/*!
|
||||
* \brief Returns the current directory.
|
||||
*/
|
||||
inline const std::string &ArgumentParser::currentDirectory() const
|
||||
inline const char *ArgumentParser::currentDirectory() const
|
||||
{
|
||||
return m_currentDirectory;
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@ namespace ApplicationUtilities {
|
|||
* \brief Constructs new fake Qt-config arguments.
|
||||
*/
|
||||
FakeQtConfigArguments::FakeQtConfigArguments() :
|
||||
m_qtWidgetsGuiArg("qt-widgets-gui", "g", "shows a Qt widgets based graphical user interface (the application has not been built with Qt widgets support)"),
|
||||
m_qtQuickGuiArg("qt-quick-gui", "q", "shows a Qt quick based graphical user interface (the application has not been built with Qt quick support)")
|
||||
m_qtWidgetsGuiArg("qt-widgets-gui", 'g', "shows a Qt widgets based graphical user interface (the application has not been built with Qt widgets support)"),
|
||||
m_qtQuickGuiArg("qt-quick-gui", 'q', "shows a Qt quick based graphical user interface (the application has not been built with Qt quick support)")
|
||||
{}
|
||||
|
||||
} // namespace ApplicationUtilities
|
||||
|
|
|
@ -178,6 +178,25 @@ template <typename NumberType, typename StringType> LIB_EXPORT NumberType string
|
|||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Converts the given \a string to a numeric value using the specified \a base.
|
||||
* \tparam NumberType The data type used to store the converted value.
|
||||
* \tparam StringType The string type (should be an instantiation of the basic_string class template).
|
||||
* \throws A ConversionException will be thrown if the provided string is not a valid number.
|
||||
* \sa numberToString()
|
||||
*/
|
||||
template <typename NumberType, typename CharType> LIB_EXPORT NumberType stringToNumber(const CharType *string, int base = 10)
|
||||
{
|
||||
std::basic_stringstream<CharType> ss;
|
||||
ss << std::setbase(base) << string;
|
||||
NumberType result;
|
||||
if((ss >> result) && ss.eof()) {
|
||||
return result;
|
||||
} else {
|
||||
throw ConversionException("The specified string is no valid number.");
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Interprets the given \a integer at the specified position as std::string using the specified byte order.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,253 @@
|
|||
#include "./testutils.h"
|
||||
|
||||
#include "../application/argumentparser.h"
|
||||
#include "../application/failure.h"
|
||||
#include "../application/fakeqtconfigarguments.h"
|
||||
|
||||
#include "resources/config.h"
|
||||
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
#include <cppunit/TestFixture.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
using namespace std;
|
||||
using namespace ApplicationUtilities;
|
||||
|
||||
using namespace CPPUNIT_NS;
|
||||
|
||||
/*!
|
||||
* \brief The ArgumentParserTests class tests the ArgumentParser and Argument classes.
|
||||
*/
|
||||
class ArgumentParserTests : public TestFixture
|
||||
{
|
||||
CPPUNIT_TEST_SUITE(ArgumentParserTests);
|
||||
CPPUNIT_TEST(testArgument);
|
||||
CPPUNIT_TEST(testParsing);
|
||||
CPPUNIT_TEST(testCallbacks);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void setUp();
|
||||
void tearDown();
|
||||
|
||||
void testArgument();
|
||||
void testParsing();
|
||||
void testCallbacks();
|
||||
|
||||
private:
|
||||
void callback();
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(ArgumentParserTests);
|
||||
|
||||
void ArgumentParserTests::setUp()
|
||||
{}
|
||||
|
||||
void ArgumentParserTests::tearDown()
|
||||
{}
|
||||
|
||||
/*!
|
||||
* \brief Tests the behaviour of the argument class.
|
||||
*/
|
||||
void ArgumentParserTests::testArgument()
|
||||
{
|
||||
Argument argument("test", 't', "some description");
|
||||
CPPUNIT_ASSERT_EQUAL(argument.isRequired(), false);
|
||||
argument.setConstraints(1, 10);
|
||||
CPPUNIT_ASSERT_EQUAL(argument.isRequired(), true);
|
||||
Argument subArg("sub", 's', "sub arg");
|
||||
argument.addSubArgument(&subArg);
|
||||
CPPUNIT_ASSERT_EQUAL(subArg.parents().at(0), &argument);
|
||||
CPPUNIT_ASSERT(!subArg.conflictsWithArgument());
|
||||
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Tests parsing command line arguments.
|
||||
*/
|
||||
void ArgumentParserTests::testParsing()
|
||||
{
|
||||
ArgumentParser parser;
|
||||
|
||||
// add some test argument definitions
|
||||
SET_APPLICATION_INFO;
|
||||
QT_CONFIG_ARGUMENTS qtConfigArgs;
|
||||
HelpArgument helpArg(parser);
|
||||
Argument verboseArg("verbose", 'v', "be verbose");
|
||||
verboseArg.setCombinable(true);
|
||||
Argument fileArg("file", 'f', "specifies the path of the file to be opened");
|
||||
fileArg.setValueNames({"path"});
|
||||
fileArg.setRequiredValueCount(1);
|
||||
Argument filesArg("files", 'f', "specifies the path of the file(s) to be opened");
|
||||
filesArg.setValueNames({"path 1", "path 2"});
|
||||
filesArg.setRequiredValueCount(-1);
|
||||
Argument outputFileArg("output-file", 'o', "specifies the path of the output file");
|
||||
outputFileArg.setValueNames({"path"});
|
||||
outputFileArg.setRequiredValueCount(1);
|
||||
outputFileArg.setRequired(true);
|
||||
outputFileArg.setCombinable(true);
|
||||
Argument printFieldNamesArg("print-field-names", '\0', "prints available field names");
|
||||
Argument displayFileInfoArg("display-file-info", 'i', "displays general file information");
|
||||
displayFileInfoArg.setDenotesOperation(true);
|
||||
displayFileInfoArg.setSubArguments({&fileArg, &verboseArg});
|
||||
Argument fieldsArg("fields", '\0', "specifies the fields");
|
||||
fieldsArg.setRequiredValueCount(-1);
|
||||
fieldsArg.setValueNames({"title", "album", "artist", "trackpos"});
|
||||
fieldsArg.setDefault(true);
|
||||
Argument displayTagInfoArg("get", 'p', "displays the values of all specified tag fields (displays all fields if none specified)");
|
||||
displayTagInfoArg.setDenotesOperation(true);
|
||||
displayTagInfoArg.setSubArguments({&fieldsArg, &filesArg, &verboseArg});
|
||||
parser.setMainArguments({&qtConfigArgs.qtWidgetsGuiArg(), &printFieldNamesArg, &displayTagInfoArg, &displayFileInfoArg, &helpArg});
|
||||
|
||||
// define some argument values
|
||||
const char *argv[] = {"tageditor", "get", "album", "title", "diskpos", "-f", "somefile"};
|
||||
// try to parse, this should fail
|
||||
try {
|
||||
parser.parseArgs(7, argv);
|
||||
CPPUNIT_FAIL("Exception expected.");
|
||||
} catch(const Failure &e) {
|
||||
CPPUNIT_ASSERT(!strcmp(e.what(), "The argument \"files\" can not be combined with \"fields\"."));
|
||||
}
|
||||
|
||||
// try to parse again, but adjust the configuration for a successful parse
|
||||
displayTagInfoArg.reset(), fieldsArg.reset(), filesArg.reset();
|
||||
filesArg.setCombinable(true);
|
||||
parser.parseArgs(7, argv);
|
||||
// check results
|
||||
CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
|
||||
CPPUNIT_ASSERT(!strcmp(parser.currentDirectory(), "tageditor"));
|
||||
CPPUNIT_ASSERT(!verboseArg.isPresent());
|
||||
CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
|
||||
CPPUNIT_ASSERT(fieldsArg.isPresent());
|
||||
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(0), "album"));
|
||||
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(1), "title"));
|
||||
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(2), "diskpos"));
|
||||
CPPUNIT_ASSERT_THROW(displayTagInfoArg.values().at(3), out_of_range);
|
||||
|
||||
// define the same arguments in a different way
|
||||
const char *argv2[] = {"tageditor", "-p", "album", "title", "diskpos", "--file", "somefile"};
|
||||
// reparse the args
|
||||
displayTagInfoArg.reset(), fieldsArg.reset(), filesArg.reset();
|
||||
parser.parseArgs(7, argv2);
|
||||
// check results again
|
||||
CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
|
||||
CPPUNIT_ASSERT(!verboseArg.isPresent());
|
||||
CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
|
||||
CPPUNIT_ASSERT(fieldsArg.isPresent());
|
||||
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(0), "album"));
|
||||
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(1), "title"));
|
||||
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(2), "diskpos"));
|
||||
CPPUNIT_ASSERT_THROW(displayTagInfoArg.values().at(3), out_of_range);
|
||||
CPPUNIT_ASSERT(filesArg.isPresent());
|
||||
CPPUNIT_ASSERT(!strcmp(filesArg.values().at(0), "somefile"));
|
||||
|
||||
// forget "get"/"-p"
|
||||
const char *argv3[] = {"tageditor", "album", "title", "diskpos", "--file", "somefile"};
|
||||
displayTagInfoArg.reset(), fieldsArg.reset(), filesArg.reset();
|
||||
|
||||
// a parsing error should occur because the argument "album" is not defined
|
||||
try {
|
||||
parser.parseArgs(6, argv3);
|
||||
CPPUNIT_FAIL("Exception expected.");
|
||||
} catch(const Failure &e) {
|
||||
CPPUNIT_ASSERT(!strcmp(e.what(), "The specified argument \"album\" is unknown and will be ignored."));
|
||||
}
|
||||
|
||||
// repeat the test, but this time just ignore the undefined argument
|
||||
displayTagInfoArg.reset(), fieldsArg.reset(), filesArg.reset();
|
||||
parser.setIgnoreUnknownArguments(true);
|
||||
parser.parseArgs(6, argv3);
|
||||
// none of the arguments should be present now
|
||||
CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
|
||||
CPPUNIT_ASSERT(!displayTagInfoArg.isPresent());
|
||||
CPPUNIT_ASSERT(!fieldsArg.isPresent());
|
||||
CPPUNIT_ASSERT(!filesArg.isPresent());
|
||||
|
||||
// test abbreviations like "-vf"
|
||||
const char *argv4[] = {"tageditor", "-i", "-vf", "test"};
|
||||
displayTagInfoArg.reset(), fieldsArg.reset(), filesArg.reset();
|
||||
parser.setIgnoreUnknownArguments(false);
|
||||
parser.parseArgs(4, argv4);
|
||||
CPPUNIT_ASSERT(displayFileInfoArg.isPresent());
|
||||
CPPUNIT_ASSERT(verboseArg.isPresent());
|
||||
CPPUNIT_ASSERT(!displayTagInfoArg.isPresent());
|
||||
CPPUNIT_ASSERT(!filesArg.isPresent());
|
||||
CPPUNIT_ASSERT(fileArg.isPresent());
|
||||
CPPUNIT_ASSERT(!strcmp(fileArg.values().at(0), "test"));
|
||||
CPPUNIT_ASSERT_THROW(fileArg.values().at(1), out_of_range);
|
||||
|
||||
// don't reset verbose argument to test constraint checking
|
||||
displayFileInfoArg.reset(), fileArg.reset();
|
||||
try {
|
||||
parser.parseArgs(4, argv4);
|
||||
CPPUNIT_FAIL("Exception expected.");
|
||||
} catch(const Failure &e) {
|
||||
CPPUNIT_ASSERT(!strcmp(e.what(), "The argument \"verbose\" mustn't be specified more than 1 time."));
|
||||
}
|
||||
|
||||
// relax constraint
|
||||
displayFileInfoArg.reset(), fileArg.reset(), verboseArg.reset();
|
||||
verboseArg.setConstraints(0, -1);
|
||||
parser.parseArgs(4, argv4);
|
||||
|
||||
// make verbose mandatory
|
||||
verboseArg.setRequired(true);
|
||||
displayFileInfoArg.reset(), fileArg.reset(), verboseArg.reset();
|
||||
parser.parseArgs(4, argv4);
|
||||
|
||||
// make it complain about missing argument
|
||||
const char *argv5[] = {"tageditor", "-i", "-f", "test"};
|
||||
displayFileInfoArg.reset(), fileArg.reset(), verboseArg.reset();
|
||||
try {
|
||||
parser.parseArgs(4, argv5);
|
||||
CPPUNIT_FAIL("Exception expected.");
|
||||
} catch(const Failure &e) {
|
||||
CPPUNIT_ASSERT(!strcmp(e.what(), "The argument \"verbose\" must be specified at least 1 time."));
|
||||
}
|
||||
|
||||
// it should not complain if -i is not present
|
||||
const char *argv6[] = {"tageditor", "-g"};
|
||||
displayFileInfoArg.reset(), fileArg.reset(), verboseArg.reset();
|
||||
parser.parseArgs(2, argv6);
|
||||
|
||||
// it should not be possible to specify -f without -i or -p
|
||||
const char *argv7[] = {"tageditor", "-f", "test"};
|
||||
displayFileInfoArg.reset(), fileArg.reset(), verboseArg.reset();
|
||||
try {
|
||||
parser.parseArgs(3, argv7);
|
||||
CPPUNIT_FAIL("Exception expected.");
|
||||
} catch(const Failure &e) {
|
||||
CPPUNIT_ASSERT(!strcmp(e.what(), "The specified argument \"-f\" is unknown and will be ignored."));
|
||||
}
|
||||
}
|
||||
|
||||
void ArgumentParserTests::testCallbacks()
|
||||
{
|
||||
ArgumentParser parser;
|
||||
Argument callbackArg("with-callback", 't', "callback test");
|
||||
callbackArg.setRequiredValueCount(2);
|
||||
callbackArg.setCallback([] (const vector<const char *> &values) {
|
||||
CPPUNIT_ASSERT(values.size() == 2);
|
||||
CPPUNIT_ASSERT(!strcmp(values[0], "val1"));
|
||||
CPPUNIT_ASSERT(!strcmp(values[1], "val2"));
|
||||
throw 42;
|
||||
});
|
||||
Argument noCallbackArg("no-callback", 'l', "callback test");
|
||||
noCallbackArg.setRequiredValueCount(2);
|
||||
parser.setMainArguments({&callbackArg, &noCallbackArg});
|
||||
|
||||
// test whether callback is invoked when argument with callback is specified
|
||||
const char *argv[] = {"test", "-t", "val1", "val2"};
|
||||
try {
|
||||
parser.parseArgs(4, argv);
|
||||
} catch(int i) {
|
||||
CPPUNIT_ASSERT_EQUAL(i, 42);
|
||||
}
|
||||
|
||||
// test whether callback is not invoked when argument with callback is not specified
|
||||
callbackArg.reset();
|
||||
const char *argv2[] = {"test", "-l", "val1", "val2"};
|
||||
parser.parseArgs(4, argv2);
|
||||
|
||||
}
|
|
@ -23,14 +23,13 @@ int main(int argc, char **argv)
|
|||
// run tests
|
||||
TextUi::TestRunner runner;
|
||||
TestFactoryRegistry ®istry = TestFactoryRegistry::getRegistry();
|
||||
const auto &units = testApp.units();
|
||||
if(units.empty()) {
|
||||
if(!testApp.unitsSpecified() || testApp.units().empty()) {
|
||||
// no units specified -> test all
|
||||
runner.addTest(registry.makeTest());
|
||||
} else {
|
||||
// pick specified units from overall test
|
||||
Test *overallTest = registry.makeTest();
|
||||
for(const string &unit : units) {
|
||||
for(const char *unit : testApp.units()) {
|
||||
try {
|
||||
runner.addTest(overallTest->findTest(unit));
|
||||
} catch(const invalid_argument &) {
|
||||
|
|
|
@ -33,9 +33,9 @@ TestApplication *TestApplication::m_instance = nullptr;
|
|||
*/
|
||||
TestApplication::TestApplication(int argc, char **argv) :
|
||||
m_helpArg(m_parser),
|
||||
m_testFilesPathArg("test-files-path", "p", "specifies the path of the directory with test files"),
|
||||
m_workingDirArg("working-dir", "w", "specifies the directory to store working copies of test files"),
|
||||
m_unitsArg("units", "u", "specifies the units to test; omit to test all units")
|
||||
m_testFilesPathArg("test-files-path", 'p', "specifies the path of the directory with test files"),
|
||||
m_workingDirArg("working-dir", 'w', "specifies the directory to store working copies of test files"),
|
||||
m_unitsArg("units", 'u', "specifies the units to test; omit to test all units")
|
||||
{
|
||||
// check whether there is already an instance
|
||||
if(m_instance) {
|
||||
|
@ -69,8 +69,8 @@ TestApplication::TestApplication(int argc, char **argv) :
|
|||
m_parser.parseArgs(argc, argv);
|
||||
cerr << "Directories used to search for testfiles:" << endl;
|
||||
if(m_testFilesPathArg.isPresent()) {
|
||||
if(!m_testFilesPathArg.values().front().empty()) {
|
||||
cerr << (m_testFilesPathArgValue = m_testFilesPathArg.values().front() + '/') << endl;
|
||||
if(*m_testFilesPathArg.values().front()) {
|
||||
cerr << ((m_testFilesPathArgValue = m_testFilesPathArg.values().front()) += '/') << endl;
|
||||
} else {
|
||||
cerr << (m_testFilesPathArgValue = "./") << endl;
|
||||
}
|
||||
|
@ -81,8 +81,8 @@ TestApplication::TestApplication(int argc, char **argv) :
|
|||
cerr << "./testfiles/" << endl << endl;
|
||||
cerr << "Directory used to store working copies:" << endl;
|
||||
if(m_workingDirArg.isPresent()) {
|
||||
if(!m_workingDirArg.values().front().empty()) {
|
||||
m_workingDir = m_workingDirArg.values().front() + '/';
|
||||
if(*m_workingDirArg.values().front()) {
|
||||
(m_workingDir = m_workingDirArg.values().front()) += '/';
|
||||
} else {
|
||||
m_workingDir = "./";
|
||||
}
|
||||
|
|
|
@ -18,7 +18,8 @@ public:
|
|||
#ifdef PLATFORM_UNIX
|
||||
std::string workingCopyPath(const std::string &name) const;
|
||||
#endif
|
||||
const std::vector<std::string> &units() const;
|
||||
bool unitsSpecified() const;
|
||||
const std::vector<const char *> &units() const;
|
||||
static const TestApplication *instance();
|
||||
|
||||
private:
|
||||
|
@ -54,9 +55,18 @@ inline const TestApplication *TestApplication::instance()
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the specified test units.
|
||||
* \brief Returns whether particular units have been specified.
|
||||
*/
|
||||
inline const std::vector<std::string> &TestApplication::units() const
|
||||
inline bool TestApplication::unitsSpecified() const
|
||||
{
|
||||
return m_unitsArg.isPresent();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the specified test units.
|
||||
* \remarks The units argument must be present.
|
||||
*/
|
||||
inline const std::vector<const char *> &TestApplication::units() const
|
||||
{
|
||||
return m_unitsArg.values();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue