Rewrite argument parsing

This commit is contained in:
Martchus 2016-06-12 01:56:57 +02:00
parent 4f87cc1181
commit 526cbc5282
10 changed files with 702 additions and 435 deletions

View File

@ -58,6 +58,7 @@ set(TEST_SRC_FILES
tests/conversiontests.cpp
tests/iotests.cpp
tests/chronotests.cpp
tests/argumentparsertests.cpp
)
set(CMAKE_MODULE_FILES

View File

@ -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

View File

@ -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) << "]";
if(requiredValueCount() == static_cast<size_t>(-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;
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,293 +335,277 @@ 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());
}
verifiedArgs.push_back(arg);
checkArguments(arg->subArguments());
}
};
checkArguments(m_mainArgs);
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);
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());
}
}
#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);
} 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);
}
enum ArgumentDenotationType : unsigned char {
Value = 0, // parameter value
Abbreviation = 1, // argument abbreviation
FullName = 2 // full argument name
};
// 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.");
}
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 {
// 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) {
if(arg->abbreviation() && arg->abbreviation() == *argDenotation) {
matchingArg = arg;
abbreviationFound = true;
break;
}
}
} 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;
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;
}
}
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 {
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;
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, &currentArg, 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);
}
}
return; // unknown argument name or abbreviation found -> continue with parent level
}
}
}
}
// 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.");
}
}
/*!
* \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."));
}
});
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() + "\".");
}
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()) {
conflictingArgument = firstPresentUncombinableArg(m_mainArgs, arg);
}
Argument *conflictingArgument = nullptr;
if(arg->isMainArgument()) {
if(!arg->isCombinable() && arg->isPresent()) {
conflictingArgument = firstPresentUncombinableArg(m_mainArgs, arg);
}
} else {
conflictingArgument = arg->conflictsWithArgument();
}
if(conflictingArgument) {
throw Failure("The argument \"" + string(conflictingArgument->name()) + "\" can not be combined with \"" + arg->name() + "\".");
}
if(!arg->allRequiredValuesPresent()) {
} else {
conflictingArgument = arg->conflictsWithArgument();
}
if(conflictingArgument) {
throw Failure("The argument \"" + string(conflictingArgument->name()) + "\" can not be combined with \"" + arg->name() + "\".");
}
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;
}
while(valueNamesPrint < arg->m_requiredValueCount) {
ss << "\nvalue " << (++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);
} else {
arg->m_callbackFunction(arg->values());
}
for(const auto &valuesOfOccurance : arg->m_values) {
if(arg->isDefault() && valuesOfOccurance.empty()) {
arg->m_callbackFunction(arg->defaultValues());
} else {
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);
});

View File

@ -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 &currentDirectory() 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;
}

View File

@ -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

View File

@ -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.
*

View File

@ -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);
}

View File

@ -23,14 +23,13 @@ int main(int argc, char **argv)
// run tests
TextUi::TestRunner runner;
TestFactoryRegistry &registry = 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 &) {

View File

@ -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 = "./";
}

View File

@ -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();
}