Rewrite argument parsing
This commit is contained in:
parent
4f87cc1181
commit
526cbc5282
|
@ -58,6 +58,7 @@ set(TEST_SRC_FILES
|
||||||
tests/conversiontests.cpp
|
tests/conversiontests.cpp
|
||||||
tests/iotests.cpp
|
tests/iotests.cpp
|
||||||
tests/chronotests.cpp
|
tests/chronotests.cpp
|
||||||
|
tests/argumentparsertests.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(CMAKE_MODULE_FILES
|
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
|
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):
|
[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
|
make DESTDIR="/temporary/install/location" install-mingw-w64-strip
|
||||||
```
|
```
|
||||||
* To create the \*.ico file for the application icon ffmpeg/avconv is required.
|
* 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.
|
is currently disabled. Linking against cppunit built using new libstdc++ ABI isn't possible.
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
- rewrite argument parser (the API might change slightly)
|
|
||||||
- remove unused features
|
- remove unused features
|
||||||
|
|
|
@ -6,20 +6,23 @@
|
||||||
#include "../misc/random.h"
|
#include "../misc/random.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <vector>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <stdexcept>
|
#include <cstring>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace std::placeholders;
|
using namespace std::placeholders;
|
||||||
|
using namespace ConversionUtilities;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\namespace ApplicationUtilities
|
* \namespace ApplicationUtilities
|
||||||
\brief Contains currently only ArgumentParser and related classes.
|
* \brief Contains currently only ArgumentParser and related classes.
|
||||||
*/
|
*/
|
||||||
namespace ApplicationUtilities {
|
namespace ApplicationUtilities {
|
||||||
|
|
||||||
|
/// \cond
|
||||||
|
|
||||||
const char *applicationName = nullptr;
|
const char *applicationName = nullptr;
|
||||||
const char *applicationAuthor = nullptr;
|
const char *applicationAuthor = nullptr;
|
||||||
const char *applicationVersion = nullptr;
|
const char *applicationVersion = nullptr;
|
||||||
|
@ -30,6 +33,8 @@ inline bool notEmpty(const char *str)
|
||||||
return str && *str;
|
return str && *str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \endcond
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \class ApplicationUtilities::Argument
|
* \class ApplicationUtilities::Argument
|
||||||
* \brief The Argument class is a wrapper for command line argument information.
|
* \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 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.
|
* 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_name(name),
|
||||||
m_abbreviation(abbreviation),
|
m_abbreviation(abbreviation),
|
||||||
m_description(description),
|
m_description(description),
|
||||||
m_example(example),
|
m_example(example),
|
||||||
m_required(false),
|
m_minOccurrences(0),
|
||||||
|
m_maxOccurrences(1),
|
||||||
m_combinable(false),
|
m_combinable(false),
|
||||||
m_implicit(false),
|
|
||||||
m_denotesOperation(false),
|
m_denotesOperation(false),
|
||||||
m_requiredValueCount(0),
|
m_requiredValueCount(0),
|
||||||
m_default(false),
|
m_default(false),
|
||||||
m_present(false),
|
|
||||||
m_isMainArg(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 << " ";
|
for(unsigned char i = 0; i < indentionLevel; ++i) os << " ";
|
||||||
if(notEmpty(name())) {
|
if(notEmpty(name())) {
|
||||||
os << "--" << name();
|
os << '-' << '-' << name();
|
||||||
}
|
}
|
||||||
if(notEmpty(name()) && notEmpty(abbreviation())) {
|
if(notEmpty(name()) && abbreviation()) {
|
||||||
os << ", ";
|
os << ',' << ' ';
|
||||||
}
|
}
|
||||||
if(notEmpty(abbreviation())) {
|
if(abbreviation()) {
|
||||||
os << "-" << abbreviation();
|
os << '-' << abbreviation();
|
||||||
}
|
}
|
||||||
if(requiredValueCount() > 0) {
|
if(requiredValueCount() != 0) {
|
||||||
int valueNamesPrint = 0;
|
unsigned int valueNamesPrint = 0;
|
||||||
for(auto i = valueNames().cbegin(), end = valueNames().cend(); i != end && valueNamesPrint < requiredValueCount(); ++i) {
|
for(auto i = valueNames().cbegin(), end = valueNames().cend(); i != end && valueNamesPrint < requiredValueCount(); ++i) {
|
||||||
os << " [" << *i << "]";
|
os << ' ' << '[' << *i << ']';
|
||||||
++valueNamesPrint;
|
++valueNamesPrint;
|
||||||
}
|
}
|
||||||
for(; valueNamesPrint < requiredValueCount(); ++valueNamesPrint) {
|
if(requiredValueCount() == static_cast<size_t>(-1)) {
|
||||||
os << " [value " << (valueNamesPrint + 1) << "]";
|
os << " ...";
|
||||||
|
} else {
|
||||||
|
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 << "]";
|
|
||||||
}
|
|
||||||
os << " ...";
|
|
||||||
}
|
}
|
||||||
++indentionLevel;
|
++indentionLevel;
|
||||||
if(notEmpty(description())) {
|
if(notEmpty(description())) {
|
||||||
os << endl;
|
os << endl;
|
||||||
for(unsigned char i = 0; i < indentionLevel; ++i) os << " ";
|
for(unsigned char i = 0; i < indentionLevel; ++i) os << ' ' << ' ';
|
||||||
os << description();
|
os << description();
|
||||||
}
|
}
|
||||||
if(isRequired()) {
|
if(isRequired()) {
|
||||||
os << endl;
|
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.";
|
os << "This argument is required.";
|
||||||
}
|
}
|
||||||
if(notEmpty(example())) {
|
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 << "Usage: " << example();
|
||||||
}
|
}
|
||||||
os << endl;
|
os << endl;
|
||||||
|
@ -182,31 +185,15 @@ void Argument::addSubArgument(Argument *arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Returns the names of the parents in the form "parent1", "parent2, "parent3", ...
|
* \brief Returns whether at least one parent argument is present.
|
||||||
* Returns an empty string if this Argument has no parents.
|
* \remarks Returns always true for main arguments.
|
||||||
* \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.
|
|
||||||
*/
|
*/
|
||||||
bool Argument::isParentPresent() const
|
bool Argument::isParentPresent() const
|
||||||
{
|
{
|
||||||
for(Argument *parent : m_parents) {
|
if(isMainArgument()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for(const Argument *parent : m_parents) {
|
||||||
if(parent->isPresent()) {
|
if(parent->isPresent()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -234,6 +221,15 @@ Argument *Argument::conflictsWithArgument() const
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Resets occurrences and values.
|
||||||
|
*/
|
||||||
|
void Argument::reset()
|
||||||
|
{
|
||||||
|
m_indices.clear();
|
||||||
|
m_values.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \class ApplicationUtilities::ArgumentParser
|
* \class ApplicationUtilities::ArgumentParser
|
||||||
* \brief The ArgumentParser class provides a means for handling command line arguments.
|
* \brief The ArgumentParser class provides a means for handling command line arguments.
|
||||||
|
@ -252,6 +248,7 @@ Argument *Argument::conflictsWithArgument() const
|
||||||
*/
|
*/
|
||||||
ArgumentParser::ArgumentParser() :
|
ArgumentParser::ArgumentParser() :
|
||||||
m_actualArgc(0),
|
m_actualArgc(0),
|
||||||
|
m_currentDirectory(nullptr),
|
||||||
m_ignoreUnknownArgs(false)
|
m_ignoreUnknownArgs(false)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
@ -338,293 +335,277 @@ Argument *ArgumentParser::findArg(const ArgumentVector &arguments, const Argumen
|
||||||
return nullptr; // no argument matches
|
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
|
#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:
|
* Asserts that
|
||||||
* - An argument is used as main argument and as sub argument at the same time.
|
* - The same argument has not been added twice to the same parent.
|
||||||
* - An argument abbreviation is used more then once.
|
* - Only one argument within a parent is default or implicit.
|
||||||
* - An argument name is used more then once.
|
* - Only main arguments denote operations.
|
||||||
* If none of these conditions are met, this method will do nothing.
|
* - 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
|
* \remarks
|
||||||
* parse() automatically befor the parsing is performed.
|
* - 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<const Argument *> verifiedArgs;
|
||||||
vector<string> abbreviations;
|
verifiedArgs.reserve(args.size());
|
||||||
|
vector<char> abbreviations;
|
||||||
|
abbreviations.reserve(args.size());
|
||||||
vector<string> names;
|
vector<string> names;
|
||||||
const Argument *implicitArg = nullptr;
|
names.reserve(args.size());
|
||||||
function<void (const ArgumentVector &args)> checkArguments;
|
bool hasDefault = false;
|
||||||
checkArguments = [&verifiedArgs, &abbreviations, &names, &checkArguments, &implicitArg, this] (const ArgumentVector &args) {
|
for(const Argument *arg : args) {
|
||||||
for(Argument *arg : args) {
|
assert(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) == verifiedArgs.cend());
|
||||||
if(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) != verifiedArgs.cend()) {
|
verifiedArgs.push_back(arg);
|
||||||
continue; // do not verify the same argument twice
|
assert(arg->isMainArgument() || !arg->denotesOperation());
|
||||||
}
|
assert(!arg->isDefault() || !hasDefault);
|
||||||
if(arg->isMainArgument() && arg->parents().size()) {
|
hasDefault |= arg->isDefault();
|
||||||
throw invalid_argument("Argument \"" + string(arg->name()) + "\" can not be used as main argument and sub argument at the same time.");
|
assert(!arg->abbreviation() || find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) == abbreviations.cend());
|
||||||
}
|
abbreviations.push_back(arg->abbreviation());
|
||||||
if(notEmpty(arg->abbreviation()) && find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) != abbreviations.cend()) {
|
assert(!arg->name() || find(names.cbegin(), names.cend(), arg->name()) == names.cend());
|
||||||
throw invalid_argument("Abbreviation \"" + string(arg->abbreviation()) + "\" has been used more then once.");
|
assert(arg->requiredValueCount() == 0 || arg->subArguments().size() == 0);
|
||||||
}
|
names.emplace_back(arg->name());
|
||||||
if(find(names.cbegin(), names.cend(), arg->name()) != names.cend()) {
|
verifyArgs(arg->subArguments());
|
||||||
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());
|
|
||||||
}
|
|
||||||
verifiedArgs.push_back(arg);
|
|
||||||
checkArguments(arg->subArguments());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
checkArguments(m_mainArgs);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief This method invokes verifySetup() before parsing. See its do documentation for more
|
* \brief Reads the specified commands line arguments.
|
||||||
* information about execptions that might be thrown to indicate an invalid setup of the parser.
|
* \remarks Results are stored in Argument instances added as main arguments and sub arguments.
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
void ArgumentParser::parseArgs(int argc, char *argv[])
|
void ArgumentParser::readSpecifiedArgs(ArgumentVector &args, std::size_t &index, const char **&argv, const char **end)
|
||||||
{
|
{
|
||||||
// initiate parser
|
enum ArgumentDenotationType : unsigned char {
|
||||||
IF_DEBUG_BUILD(verifySetup();)
|
Value = 0, // parameter value
|
||||||
m_actualArgc = 0; // reset actual agument count
|
Abbreviation = 1, // argument abbreviation
|
||||||
unsigned int actualArgc = 0;
|
FullName = 2 // full argument name
|
||||||
int valuesToRead = 0;
|
|
||||||
// read current directory
|
|
||||||
if(argc >= 1) {
|
|
||||||
m_currentDirectory = string(*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) {
|
|
||||||
for(Argument *arg : args) {
|
|
||||||
proc(parent, arg);
|
|
||||||
foreachArg(arg, arg->subArguments(), proc);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
// parse given arguments
|
|
||||||
if(argc >= 2) {
|
bool isTopLevel = index == 0;
|
||||||
Argument *currentArg = nullptr;
|
Argument *lastArg = nullptr;
|
||||||
// iterate through given arguments
|
vector<const char *> *values = nullptr;
|
||||||
for(char **i = argv + 1, **end = argv + argc; i != end; ++i) {
|
while(argv != end) {
|
||||||
string givenArg(*i); // convert argument to string
|
if(values && lastArg->requiredValueCount() != static_cast<size_t>(-1) && values->size() < lastArg->requiredValueCount()) {
|
||||||
if(!givenArg.empty()) { // skip empty entries
|
// there are still values to read
|
||||||
if(valuesToRead <= 0 && givenArg.size() > 1 && givenArg.front() == '-') {
|
values->emplace_back(*argv);
|
||||||
// we have no values left to read and the given arguments starts with '-'
|
++index, ++argv;
|
||||||
// -> the next "value" is a main argument or a sub argument
|
} else {
|
||||||
ArgumentPredicate pred;
|
// determine denotation type
|
||||||
string givenId;
|
const char *argDenotation = *argv;
|
||||||
size_t equationPos = givenArg.find('=');
|
bool abbreviationFound = false;
|
||||||
if(givenArg.size() > 2 && givenArg[1] == '-') {
|
unsigned char argDenotationType = Value;
|
||||||
// the argument starts with '--'
|
*argDenotation == '-' && (++argDenotation, ++argDenotationType)
|
||||||
// -> the full argument name has been provided
|
&& *argDenotation == '-' && (++argDenotation, ++argDenotationType);
|
||||||
givenId = givenArg.substr(2, equationPos - 2);
|
|
||||||
pred = [&givenId, equationPos] (Argument *arg) {
|
// try to find matching Argument instance
|
||||||
return arg->name() && arg->name() == givenId;
|
Argument *matchingArg = nullptr;
|
||||||
};
|
if(argDenotationType != Value) {
|
||||||
} else {
|
const char *const equationPos = strchr(argDenotation, '=');
|
||||||
// the argument starts with a single '-'
|
for(const auto argDenLen = equationPos ? static_cast<size_t>(equationPos - argDenotation) : strlen(argDenotation); ; matchingArg = nullptr) {
|
||||||
// -> the abbreviation has been provided
|
// search for arguments by abbreviation or name depending on the denotation type
|
||||||
givenId = givenArg.substr(1, equationPos - 1);
|
if(argDenotationType == Abbreviation) {
|
||||||
pred = [&givenId, equationPos] (Argument *arg) {
|
for(Argument *arg : args) {
|
||||||
return arg->abbreviation() && arg->abbreviation() == givenId;
|
if(arg->abbreviation() && arg->abbreviation() == *argDenotation) {
|
||||||
};
|
matchingArg = arg;
|
||||||
}
|
abbreviationFound = true;
|
||||||
// find the corresponding instance of the Argument class
|
break;
|
||||||
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 {
|
} else {
|
||||||
// the given argument seems to be unknown
|
for(Argument *arg : args) {
|
||||||
if(valuesToRead < 0) {
|
if(arg->name() && !strncmp(arg->name(), argDenotation, argDenLen)) {
|
||||||
// we have a variable number of values to expect -> treat "unknown argument" as value
|
matchingArg = arg;
|
||||||
goto readValue;
|
break;
|
||||||
} else {
|
}
|
||||||
// we have no more values to expect so we need to complain about the unknown argument
|
|
||||||
goto invalidArg;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(isTopLevel) {
|
||||||
|
if(m_ignoreUnknownArgs) {
|
||||||
|
cerr << "The specified argument \"" << *argv << "\" is unknown and will be ignored." << endl;
|
||||||
|
++index, ++argv;
|
||||||
|
} else {
|
||||||
|
throw Failure("The specified argument \"" + string(*argv) + "\" is unknown and will be ignored.");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
readValue:
|
return; // unknown argument name or abbreviation found -> continue with parent level
|
||||||
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;
|
|
||||||
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();
|
|
||||||
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(m_ignoreUnknownArgs) {
|
|
||||||
cout << msg << " It will be ignored." << endl;
|
|
||||||
} else {
|
|
||||||
throw Failure(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 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
|
* \brief Checks the constrains of the specified \a args.
|
||||||
if(arg->isDefault() && (arg->isMainArgument() || (parent && parent->isPresent()))) {
|
* \remarks Checks the contraints of sub arguments, too.
|
||||||
arg->m_present = true;
|
*/
|
||||||
if(firstPresentUncombinableArg(arg->isMainArgument() ? m_mainArgs : parent->subArguments(), arg)) {
|
void ArgumentParser::checkConstraints(const ArgumentVector &args)
|
||||||
arg->m_present = false;
|
{
|
||||||
}
|
for(const Argument *arg : args) {
|
||||||
}
|
const auto occurrences = arg->occurrences();
|
||||||
// throw an error if mandatory argument is not present
|
if(arg->isParentPresent() && occurrences > arg->maxOccurrences()) {
|
||||||
if(!arg->isPresent() && (arg->isRequired() && (arg->isMainArgument() || (parent && parent->isPresent())))) {
|
throw Failure("The argument \"" + string(arg->name()) + "\" mustn't be specified more than " + numberToString(arg->maxOccurrences()) + (arg->maxOccurrences() == 1 ? " time." : " times."));
|
||||||
throw Failure("The argument \"" + string(arg->name()) + "\" is required but not given.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
if(arg->isParentPresent() && occurrences < arg->minOccurrences()) {
|
||||||
foreachArg(nullptr, m_mainArgs, [this] (Argument *, Argument *arg) {
|
throw Failure("The argument \"" + string(arg->name()) + "\" must be specified at least " + numberToString(arg->minOccurrences()) + (arg->minOccurrences() == 1 ? " time." : " times."));
|
||||||
if(arg->isPresent()) {
|
}
|
||||||
if(!arg->isMainArgument() && arg->parents().size() && !arg->isParentPresent()) {
|
Argument *conflictingArgument = nullptr;
|
||||||
if(arg->parents().size() > 1) {
|
if(arg->isMainArgument()) {
|
||||||
throw Failure("The argument \"" + string(arg->name()) + "\" needs to be used together with one the following arguments: " + arg->parentNames());
|
if(!arg->isCombinable() && arg->isPresent()) {
|
||||||
} else {
|
conflictingArgument = firstPresentUncombinableArg(m_mainArgs, arg);
|
||||||
throw Failure("The argument \"" + string(arg->name()) + "\" needs to be used together with the argument \"" + arg->parents().front()->name() + "\".");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Argument *conflictingArgument = nullptr;
|
} else {
|
||||||
if(arg->isMainArgument()) {
|
conflictingArgument = arg->conflictsWithArgument();
|
||||||
if(!arg->isCombinable() && arg->isPresent()) {
|
}
|
||||||
conflictingArgument = firstPresentUncombinableArg(m_mainArgs, arg);
|
if(conflictingArgument) {
|
||||||
}
|
throw Failure("The argument \"" + string(conflictingArgument->name()) + "\" can not be combined with \"" + arg->name() + "\".");
|
||||||
} else {
|
}
|
||||||
conflictingArgument = arg->conflictsWithArgument();
|
for(size_t i = 0; i != occurrences; ++i) {
|
||||||
}
|
if(!arg->allRequiredValuesPresent(occurrences)) {
|
||||||
if(conflictingArgument) {
|
|
||||||
throw Failure("The argument \"" + string(conflictingArgument->name()) + "\" can not be combined with \"" + arg->name() + "\".");
|
|
||||||
}
|
|
||||||
if(!arg->allRequiredValuesPresent()) {
|
|
||||||
stringstream ss(stringstream::in | stringstream::out);
|
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:";
|
ss << "Not all parameter for argument \"" << arg->name() << "\" ";
|
||||||
int valueNamesPrint = 0;
|
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) {
|
for(const auto &name : arg->m_valueNames) {
|
||||||
ss << "\n" << name;
|
ss << "\n" << name;
|
||||||
++valueNamesPrint;
|
++valueNamesPrint;
|
||||||
}
|
}
|
||||||
while(valueNamesPrint < arg->m_requiredValueCount) {
|
if(arg->m_requiredValueCount != static_cast<size_t>(-1)) {
|
||||||
ss << "\nvalue " << (++valueNamesPrint);
|
while(valueNamesPrint < arg->m_requiredValueCount) {
|
||||||
|
ss << "\nvalue " << (++valueNamesPrint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
throw Failure(ss.str());
|
throw Failure(ss.str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
// set actual argument count
|
// check contraints of sub arguments recursively
|
||||||
m_actualArgc = actualArgc;
|
checkConstraints(arg->m_subArgs);
|
||||||
// interate through all arguments again to invoke callback functions
|
}
|
||||||
foreachArg(nullptr, m_mainArgs, [] (Argument *parent, Argument *arg) {
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \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->m_callbackFunction) {
|
||||||
if(arg->isMainArgument() || (parent && parent->isPresent())) {
|
for(const auto &valuesOfOccurance : arg->m_values) {
|
||||||
// only invoke if its a main argument or the parent is present
|
if(arg->isDefault() && valuesOfOccurance.empty()) {
|
||||||
if(arg->isPresent()) {
|
arg->m_callbackFunction(arg->defaultValues());
|
||||||
if(arg->isDefault() && !arg->values().size()) {
|
} else {
|
||||||
vector<string> defaultValues(arg->defaultValues().cbegin(), arg->defaultValues().cend());
|
arg->m_callbackFunction(valuesOfOccurance);
|
||||||
arg->m_callbackFunction(defaultValues);
|
|
||||||
} else {
|
|
||||||
arg->m_callbackFunction(arg->values());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
// 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.
|
* \brief Constructs a new help argument for the specified parser.
|
||||||
*/
|
*/
|
||||||
HelpArgument::HelpArgument(ArgumentParser &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;
|
CMD_UTILS_START_CONSOLE;
|
||||||
parser.printHelp(cout);
|
parser.printHelp(cout);
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,12 +3,12 @@
|
||||||
|
|
||||||
#include "./global.h"
|
#include "./global.h"
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <list>
|
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <stdexcept>
|
#ifdef DEBUG_BUILD
|
||||||
|
# include <cassert>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace ApplicationUtilities {
|
namespace ApplicationUtilities {
|
||||||
|
|
||||||
|
@ -37,35 +37,38 @@ class LIB_EXPORT Argument
|
||||||
friend class ArgumentParser;
|
friend class ArgumentParser;
|
||||||
|
|
||||||
public:
|
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();
|
~Argument();
|
||||||
|
|
||||||
const char *name() const;
|
const char *name() const;
|
||||||
void setName(const char *name);
|
void setName(const char *name);
|
||||||
const char *abbreviation() const;
|
char abbreviation() const;
|
||||||
void setAbbreviation(const char *abbreviation);
|
void setAbbreviation(char abbreviation);
|
||||||
const char *description() const;
|
const char *description() const;
|
||||||
void setDescription(const char *description);
|
void setDescription(const char *description);
|
||||||
const char *example() const;
|
const char *example() const;
|
||||||
void setExample(const char *example);
|
void setExample(const char *example);
|
||||||
const std::vector<std::string> &values() const;
|
const std::vector<const char *> &values(std::size_t occurrance = 0) const;
|
||||||
const std::string &value(std::size_t index) const;
|
std::size_t requiredValueCount() const;
|
||||||
std::size_t valueCount() const;
|
void setRequiredValueCount(std::size_t requiredValueCount);
|
||||||
int requiredValueCount() const;
|
const std::vector<const char *> &valueNames() const;
|
||||||
void setRequiredValueCount(int requiredValueCount);
|
|
||||||
const std::list<const char *> &valueNames() const;
|
|
||||||
void setValueNames(std::initializer_list<const char *> valueNames);
|
void setValueNames(std::initializer_list<const char *> valueNames);
|
||||||
void appendValueName(const char *valueName);
|
void appendValueName(const char *valueName);
|
||||||
bool allRequiredValuesPresent() const;
|
bool allRequiredValuesPresent(std::size_t occurrance = 0) const;
|
||||||
bool isDefault() const;
|
bool isDefault() const;
|
||||||
void setDefault(bool value);
|
void setDefault(bool isDefault);
|
||||||
const std::list<const char *> &defaultValues() const;
|
const std::vector<const char *> &defaultValues() const;
|
||||||
void setDefaultValues(const std::list<const char *> &defaultValues);
|
void setDefaultValues(const std::initializer_list<const char *> &defaultValues);
|
||||||
bool isPresent() const;
|
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;
|
bool isRequired() const;
|
||||||
void setRequired(bool value);
|
void setRequired(bool required);
|
||||||
bool isCombinable() const;
|
bool isCombinable() const;
|
||||||
void setCombinable(bool value);
|
void setCombinable(bool value);
|
||||||
bool isImplicit() const;
|
bool isImplicit() const;
|
||||||
|
@ -80,25 +83,25 @@ public:
|
||||||
bool hasSubArguments() const;
|
bool hasSubArguments() const;
|
||||||
const ArgumentVector parents() const;
|
const ArgumentVector parents() const;
|
||||||
bool isMainArgument() const;
|
bool isMainArgument() const;
|
||||||
std::string parentNames() const;
|
|
||||||
bool isParentPresent() const;
|
bool isParentPresent() const;
|
||||||
Argument *conflictsWithArgument() const;
|
Argument *conflictsWithArgument() const;
|
||||||
|
void reset();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const char *m_name;
|
const char *m_name;
|
||||||
const char *m_abbreviation;
|
char m_abbreviation;
|
||||||
const char *m_description;
|
const char *m_description;
|
||||||
const char *m_example;
|
const char *m_example;
|
||||||
bool m_required;
|
std::size_t m_minOccurrences;
|
||||||
|
std::size_t m_maxOccurrences;
|
||||||
bool m_combinable;
|
bool m_combinable;
|
||||||
bool m_implicit;
|
|
||||||
bool m_denotesOperation;
|
bool m_denotesOperation;
|
||||||
int m_requiredValueCount;
|
std::size_t m_requiredValueCount;
|
||||||
std::list<const char *> m_valueNames;
|
std::vector<const char *> m_valueNames;
|
||||||
bool m_default;
|
bool m_default;
|
||||||
std::list<const char *> m_defaultValues;
|
std::vector<const char *> m_defaultValues;
|
||||||
bool m_present;
|
std::vector<std::size_t> m_indices;
|
||||||
std::vector<std::string> m_values;
|
std::vector<std::vector<const char *> > m_values;
|
||||||
ArgumentVector m_subArgs;
|
ArgumentVector m_subArgs;
|
||||||
CallbackFunction m_callbackFunction;
|
CallbackFunction m_callbackFunction;
|
||||||
ArgumentVector m_parents;
|
ArgumentVector m_parents;
|
||||||
|
@ -129,12 +132,7 @@ inline void Argument::setName(const char *name)
|
||||||
#ifdef DEBUG_BUILD
|
#ifdef DEBUG_BUILD
|
||||||
if(name && *name) {
|
if(name && *name) {
|
||||||
for(const char *c = name; *c; ++c) {
|
for(const char *c = name; *c; ++c) {
|
||||||
switch(*c) {
|
assert(*c != ' ' && *c != '=');
|
||||||
case ' ': case '=':
|
|
||||||
throw std::invalid_argument("name mustn't contain white spaces or equation chars");
|
|
||||||
default:
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -147,7 +145,7 @@ inline void Argument::setName(const char *name)
|
||||||
* The parser compares the abbreviation with the characters following a "-" prefix to
|
* The parser compares the abbreviation with the characters following a "-" prefix to
|
||||||
* identify arguments.
|
* identify arguments.
|
||||||
*/
|
*/
|
||||||
inline const char *Argument::abbreviation() const
|
inline char Argument::abbreviation() const
|
||||||
{
|
{
|
||||||
return m_abbreviation;
|
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
|
* The parser compares the abbreviation with the characters following a "-" prefix to
|
||||||
* identify arguments.
|
* identify arguments.
|
||||||
*/
|
*/
|
||||||
inline void Argument::setAbbreviation(const char *abbreviation)
|
inline void Argument::setAbbreviation(char abbreviation)
|
||||||
{
|
{
|
||||||
#ifdef DEBUG_BUILD
|
IF_DEBUG_BUILD(assert(abbreviation != ' ' && abbreviation != '='));
|
||||||
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
|
|
||||||
m_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.
|
* 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;
|
return m_values[occurrance];
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* \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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -260,7 +228,7 @@ inline std::size_t Argument::valueCount() const
|
||||||
* \sa valueNames()
|
* \sa valueNames()
|
||||||
* \sa setValueNames()
|
* \sa setValueNames()
|
||||||
*/
|
*/
|
||||||
inline int Argument::requiredValueCount() const
|
inline std::size_t Argument::requiredValueCount() const
|
||||||
{
|
{
|
||||||
return m_requiredValueCount;
|
return m_requiredValueCount;
|
||||||
}
|
}
|
||||||
|
@ -276,7 +244,7 @@ inline int Argument::requiredValueCount() const
|
||||||
* \sa valueNames()
|
* \sa valueNames()
|
||||||
* \sa setValueNames()
|
* \sa setValueNames()
|
||||||
*/
|
*/
|
||||||
inline void Argument::setRequiredValueCount(int requiredValueCount)
|
inline void Argument::setRequiredValueCount(std::size_t requiredValueCount)
|
||||||
{
|
{
|
||||||
m_requiredValueCount = requiredValueCount;
|
m_requiredValueCount = requiredValueCount;
|
||||||
}
|
}
|
||||||
|
@ -289,7 +257,7 @@ inline void Argument::setRequiredValueCount(int requiredValueCount)
|
||||||
* \sa setValueNames()
|
* \sa setValueNames()
|
||||||
* \sa appendValueNames()
|
* \sa appendValueNames()
|
||||||
*/
|
*/
|
||||||
inline const std::list<const char *> &Argument::valueNames() const
|
inline const std::vector<const char *> &Argument::valueNames() const
|
||||||
{
|
{
|
||||||
return m_valueNames;
|
return m_valueNames;
|
||||||
}
|
}
|
||||||
|
@ -326,14 +294,11 @@ inline void Argument::appendValueName(const char *valueName)
|
||||||
/*!
|
/*!
|
||||||
* \brief Returns an indication whether all required values are present.
|
* \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_requiredValueCount == static_cast<std::size_t>(-1)
|
||||||
return (m_values.size() >= static_cast<size_t>(m_requiredValueCount))
|
|| (m_values[occurrance].size() >= static_cast<std::size_t>(m_requiredValueCount))
|
||||||
|| (m_default && m_defaultValues.size() >= static_cast<size_t>(m_requiredValueCount));
|
|| (m_default && m_defaultValues.size() >= static_cast<std::size_t>(m_requiredValueCount));
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -359,9 +324,9 @@ inline bool Argument::isDefault() const
|
||||||
* \brief Sets whether the argument is a default argument.
|
* \brief Sets whether the argument is a default argument.
|
||||||
* \sa isDefault()
|
* \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 setDefault()
|
||||||
* \sa setDefaultValues()
|
* \sa setDefaultValues()
|
||||||
*/
|
*/
|
||||||
inline const std::list<const char *> &Argument::defaultValues() const
|
inline const std::vector<const char *> &Argument::defaultValues() const
|
||||||
{
|
{
|
||||||
return m_defaultValues;
|
return m_defaultValues;
|
||||||
}
|
}
|
||||||
|
@ -385,18 +350,64 @@ inline const std::list<const char *> &Argument::defaultValues() const
|
||||||
* \sa setDefault()
|
* \sa setDefault()
|
||||||
* \sa defaultValues()
|
* \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;
|
m_defaultValues = defaultValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Returns an indication whether the argument could be detected
|
* \brief Returns an indication whether the argument could be detected when parsing.
|
||||||
* when parsing.
|
|
||||||
*/
|
*/
|
||||||
inline bool Argument::isPresent() const
|
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
|
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.
|
* The parser will complain if a mandatory argument is not present.
|
||||||
*
|
*
|
||||||
* * \sa isRequired()
|
* * \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;
|
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.
|
* \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
|
* \brief Sets a \a callback function which will be called by the parser if
|
||||||
* the argument could be found and no parsing errors occured.
|
* 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)
|
inline void Argument::setCallback(Argument::CallbackFunction callback)
|
||||||
{
|
{
|
||||||
|
@ -567,19 +561,22 @@ public:
|
||||||
void printHelp(std::ostream &os) const;
|
void printHelp(std::ostream &os) const;
|
||||||
Argument *findArg(const ArgumentPredicate &predicate) const;
|
Argument *findArg(const ArgumentPredicate &predicate) const;
|
||||||
static Argument *findArg(const ArgumentVector &arguments, const ArgumentPredicate &predicate);
|
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, char *argv[]);
|
||||||
|
void parseArgs(int argc, const char *argv[]);
|
||||||
unsigned int actualArgumentCount() const;
|
unsigned int actualArgumentCount() const;
|
||||||
const std::string ¤tDirectory() const;
|
const char *currentDirectory() const;
|
||||||
bool areUnknownArgumentsIgnored() const;
|
bool areUnknownArgumentsIgnored() const;
|
||||||
void setIgnoreUnknownArguments(bool ignore);
|
void setIgnoreUnknownArguments(bool ignore);
|
||||||
|
|
||||||
private:
|
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;
|
ArgumentVector m_mainArgs;
|
||||||
unsigned int m_actualArgc;
|
unsigned int m_actualArgc;
|
||||||
std::string m_currentDirectory;
|
const char *m_currentDirectory;
|
||||||
bool m_ignoreUnknownArgs;
|
bool m_ignoreUnknownArgs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -592,6 +589,14 @@ inline const ArgumentVector &ArgumentParser::mainArguments() const
|
||||||
return m_mainArgs;
|
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.
|
* \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.
|
* \brief Returns the current directory.
|
||||||
*/
|
*/
|
||||||
inline const std::string &ArgumentParser::currentDirectory() const
|
inline const char *ArgumentParser::currentDirectory() const
|
||||||
{
|
{
|
||||||
return m_currentDirectory;
|
return m_currentDirectory;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@ namespace ApplicationUtilities {
|
||||||
* \brief Constructs new fake Qt-config arguments.
|
* \brief Constructs new fake Qt-config arguments.
|
||||||
*/
|
*/
|
||||||
FakeQtConfigArguments::FakeQtConfigArguments() :
|
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_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_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
|
} // 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.
|
* \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
|
// run tests
|
||||||
TextUi::TestRunner runner;
|
TextUi::TestRunner runner;
|
||||||
TestFactoryRegistry ®istry = TestFactoryRegistry::getRegistry();
|
TestFactoryRegistry ®istry = TestFactoryRegistry::getRegistry();
|
||||||
const auto &units = testApp.units();
|
if(!testApp.unitsSpecified() || testApp.units().empty()) {
|
||||||
if(units.empty()) {
|
|
||||||
// no units specified -> test all
|
// no units specified -> test all
|
||||||
runner.addTest(registry.makeTest());
|
runner.addTest(registry.makeTest());
|
||||||
} else {
|
} else {
|
||||||
// pick specified units from overall test
|
// pick specified units from overall test
|
||||||
Test *overallTest = registry.makeTest();
|
Test *overallTest = registry.makeTest();
|
||||||
for(const string &unit : units) {
|
for(const char *unit : testApp.units()) {
|
||||||
try {
|
try {
|
||||||
runner.addTest(overallTest->findTest(unit));
|
runner.addTest(overallTest->findTest(unit));
|
||||||
} catch(const invalid_argument &) {
|
} catch(const invalid_argument &) {
|
||||||
|
|
|
@ -33,9 +33,9 @@ TestApplication *TestApplication::m_instance = nullptr;
|
||||||
*/
|
*/
|
||||||
TestApplication::TestApplication(int argc, char **argv) :
|
TestApplication::TestApplication(int argc, char **argv) :
|
||||||
m_helpArg(m_parser),
|
m_helpArg(m_parser),
|
||||||
m_testFilesPathArg("test-files-path", "p", "specifies the path of the directory with test files"),
|
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_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_unitsArg("units", 'u', "specifies the units to test; omit to test all units")
|
||||||
{
|
{
|
||||||
// check whether there is already an instance
|
// check whether there is already an instance
|
||||||
if(m_instance) {
|
if(m_instance) {
|
||||||
|
@ -69,8 +69,8 @@ TestApplication::TestApplication(int argc, char **argv) :
|
||||||
m_parser.parseArgs(argc, argv);
|
m_parser.parseArgs(argc, argv);
|
||||||
cerr << "Directories used to search for testfiles:" << endl;
|
cerr << "Directories used to search for testfiles:" << endl;
|
||||||
if(m_testFilesPathArg.isPresent()) {
|
if(m_testFilesPathArg.isPresent()) {
|
||||||
if(!m_testFilesPathArg.values().front().empty()) {
|
if(*m_testFilesPathArg.values().front()) {
|
||||||
cerr << (m_testFilesPathArgValue = m_testFilesPathArg.values().front() + '/') << endl;
|
cerr << ((m_testFilesPathArgValue = m_testFilesPathArg.values().front()) += '/') << endl;
|
||||||
} else {
|
} else {
|
||||||
cerr << (m_testFilesPathArgValue = "./") << endl;
|
cerr << (m_testFilesPathArgValue = "./") << endl;
|
||||||
}
|
}
|
||||||
|
@ -81,8 +81,8 @@ TestApplication::TestApplication(int argc, char **argv) :
|
||||||
cerr << "./testfiles/" << endl << endl;
|
cerr << "./testfiles/" << endl << endl;
|
||||||
cerr << "Directory used to store working copies:" << endl;
|
cerr << "Directory used to store working copies:" << endl;
|
||||||
if(m_workingDirArg.isPresent()) {
|
if(m_workingDirArg.isPresent()) {
|
||||||
if(!m_workingDirArg.values().front().empty()) {
|
if(*m_workingDirArg.values().front()) {
|
||||||
m_workingDir = m_workingDirArg.values().front() + '/';
|
(m_workingDir = m_workingDirArg.values().front()) += '/';
|
||||||
} else {
|
} else {
|
||||||
m_workingDir = "./";
|
m_workingDir = "./";
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,8 @@ public:
|
||||||
#ifdef PLATFORM_UNIX
|
#ifdef PLATFORM_UNIX
|
||||||
std::string workingCopyPath(const std::string &name) const;
|
std::string workingCopyPath(const std::string &name) const;
|
||||||
#endif
|
#endif
|
||||||
const std::vector<std::string> &units() const;
|
bool unitsSpecified() const;
|
||||||
|
const std::vector<const char *> &units() const;
|
||||||
static const TestApplication *instance();
|
static const TestApplication *instance();
|
||||||
|
|
||||||
private:
|
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();
|
return m_unitsArg.values();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue