|
|
|
#include "./argumentparser.h"
|
|
|
|
#include "./argumentparserprivate.h"
|
|
|
|
#include "./commandlineutils.h"
|
|
|
|
#include "./failure.h"
|
|
|
|
|
|
|
|
#include "../conversion/stringbuilder.h"
|
|
|
|
#include "../conversion/stringconversion.h"
|
|
|
|
#include "../io/ansiescapecodes.h"
|
|
|
|
#include "../io/path.h"
|
|
|
|
#include "../misc/levenshtein.h"
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <cstring>
|
|
|
|
#include <iostream>
|
|
|
|
#include <set>
|
|
|
|
#include <sstream>
|
|
|
|
#include <string>
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
using namespace std::placeholders;
|
|
|
|
using namespace ConversionUtilities;
|
|
|
|
using namespace EscapeCodes;
|
|
|
|
using namespace IoUtilities;
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \namespace ApplicationUtilities
|
|
|
|
* \brief Contains currently only ArgumentParser and related classes.
|
|
|
|
*/
|
|
|
|
namespace ApplicationUtilities {
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief The ArgumentDenotationType enum specifies the type of a given argument denotation.
|
|
|
|
*/
|
|
|
|
enum ArgumentDenotationType : unsigned char {
|
|
|
|
Value = 0, /**< parameter value */
|
|
|
|
Abbreviation = 1, /**< argument abbreviation */
|
|
|
|
FullName = 2 /**< full argument name */
|
|
|
|
};
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief The ArgumentCompletionInfo struct holds information internally used for shell completion and suggestions.
|
|
|
|
*/
|
|
|
|
struct ArgumentCompletionInfo {
|
|
|
|
ArgumentCompletionInfo(const ArgumentReader &reader);
|
|
|
|
|
|
|
|
const Argument *const lastDetectedArg;
|
|
|
|
size_t lastDetectedArgIndex = 0;
|
|
|
|
vector<Argument *> lastDetectedArgPath;
|
|
|
|
list<const Argument *> relevantArgs;
|
|
|
|
list<const Argument *> relevantPreDefinedValues;
|
|
|
|
const char *const *lastSpecifiedArg = nullptr;
|
|
|
|
unsigned int lastSpecifiedArgIndex = 0;
|
|
|
|
bool nextArgumentOrValue = false;
|
|
|
|
bool completeFiles = false, completeDirs = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Constructs a new completion info for the specified \a reader.
|
|
|
|
* \remarks Only assigns some defaults. Use ArgumentParser::determineCompletionInfo() to populate the struct with actual data.
|
|
|
|
*/
|
|
|
|
ArgumentCompletionInfo::ArgumentCompletionInfo(const ArgumentReader &reader)
|
|
|
|
: lastDetectedArg(reader.lastArg)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ArgumentSuggestion {
|
|
|
|
ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, bool hasDashPrefix);
|
|
|
|
ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, size_t suggestionSize, bool hasDashPrefix);
|
|
|
|
bool operator<(const ArgumentSuggestion &other) const;
|
|
|
|
bool operator==(const ArgumentSuggestion &other) const;
|
|
|
|
void addTo(multiset<ArgumentSuggestion> &suggestions, size_t limit) const;
|
|
|
|
|
|
|
|
const char *const suggestion;
|
|
|
|
const size_t suggestionSize;
|
|
|
|
const size_t editingDistance;
|
|
|
|
const bool hasDashPrefix;
|
|
|
|
};
|
|
|
|
|
|
|
|
ArgumentSuggestion::ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, size_t suggestionSize, bool isOperation)
|
|
|
|
: suggestion(suggestion)
|
|
|
|
, suggestionSize(suggestionSize)
|
|
|
|
, editingDistance(MiscUtilities::computeDamerauLevenshteinDistance(unknownArg, unknownArgSize, suggestion, suggestionSize))
|
|
|
|
, hasDashPrefix(isOperation)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
ArgumentSuggestion::ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, bool isOperation)
|
|
|
|
: ArgumentSuggestion(unknownArg, unknownArgSize, suggestion, strlen(suggestion), isOperation)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ArgumentSuggestion::operator<(const ArgumentSuggestion &other) const
|
|
|
|
{
|
|
|
|
return editingDistance < other.editingDistance;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ArgumentSuggestion::addTo(multiset<ArgumentSuggestion> &suggestions, size_t limit) const
|
|
|
|
{
|
|
|
|
if (suggestions.size() >= limit && !(*this < *--suggestions.end())) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
suggestions.emplace(*this);
|
|
|
|
while (suggestions.size() > limit) {
|
|
|
|
suggestions.erase(--suggestions.end());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \class ArgumentReader
|
|
|
|
* \brief The ArgumentReader class internally encapsulates the process of reading command line arguments.
|
|
|
|
* \remarks
|
|
|
|
* - For meaning of parameter see documentation of corresponding member variables.
|
|
|
|
* - Results are stored in specified \a args and assigned sub arguments.
|
|
|
|
* - This class is explicitely *not* part of the public API.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Initializes the internal reader for the specified \a parser and arguments.
|
|
|
|
*/
|
|
|
|
ArgumentReader::ArgumentReader(ArgumentParser &parser, const char *const *argv, const char *const *end, bool completionMode)
|
|
|
|
: parser(parser)
|
|
|
|
, args(parser.m_mainArgs)
|
|
|
|
, index(0)
|
|
|
|
, argv(argv)
|
|
|
|
, end(end)
|
|
|
|
, lastArg(nullptr)
|
|
|
|
, argDenotation(nullptr)
|
|
|
|
, completionMode(completionMode)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Resets the ArgumentReader to continue reading new \a argv.
|
|
|
|
*/
|
|
|
|
ArgumentReader &ArgumentReader::reset(const char *const *argv, const char *const *end)
|
|
|
|
{
|
|
|
|
this->argv = argv;
|
|
|
|
this->end = end;
|
|
|
|
index = 0;
|
|
|
|
lastArg = nullptr;
|
|
|
|
argDenotation = nullptr;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Reads the commands line arguments specified when constructing the object.
|
|
|
|
* \remarks Reads on main-argument-level.
|
|
|
|
*/
|
|
|
|
bool ArgumentReader::read()
|
|
|
|
{
|
|
|
|
return read(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns whether the \a denotation with the specified \a denotationLength matches the argument's \a name.
|
|
|
|
*/
|
|
|
|
bool Argument::matchesDenotation(const char *denotation, size_t denotationLength) const
|
|
|
|
{
|
|
|
|
return m_name && !strncmp(m_name, denotation, denotationLength) && *(m_name + denotationLength) == '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Reads the commands line arguments specified when constructing the object.
|
|
|
|
* \remarks The argument definitions to look for are specified via \a args. The method calls itself recursively
|
|
|
|
* to check for nested arguments as well.
|
|
|
|
* \returns Returns true if all arguments have been processed. Returns false on early exit because some argument
|
|
|
|
* is unknown and behavior for this case is set to UnknownArgumentBehavior::Fail.
|
|
|
|
*/
|
|
|
|
bool ArgumentReader::read(ArgumentVector &args)
|
|
|
|
{
|
|
|
|
// method is called recursively for sub args to the last argument (which is nullptr in the initial call) is the current parent argument
|
|
|
|
Argument *const parentArg = lastArg;
|
|
|
|
// determine the current path
|
|
|
|
const vector<Argument *> &parentPath = parentArg ? parentArg->path(parentArg->occurrences() - 1) : vector<Argument *>();
|
|
|
|
|
|
|
|
Argument *lastArgInLevel = nullptr;
|
|
|
|
vector<const char *> *values = nullptr;
|
|
|
|
|
|
|
|
// iterate through all argument denotations; loop might exit earlier when an denotation is unknown
|
|
|
|
while (argv != end) {
|
|
|
|
// check whether there are still values to read
|
|
|
|
if (values && lastArgInLevel->requiredValueCount() != Argument::varValueCount && values->size() < lastArgInLevel->requiredValueCount()) {
|
|
|
|
// read arg as value and continue with next arg
|
|
|
|
values->emplace_back(argDenotation ? argDenotation : *argv);
|
|
|
|
++index, ++argv, argDenotation = nullptr;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// determine how denotation must be processed
|
|
|
|
bool abbreviationFound = false;
|
|
|
|
if (argDenotation) {
|
|
|
|
// continue reading childs for abbreviation denotation already detected
|
|
|
|
abbreviationFound = false;
|
|
|
|
argDenotationType = Abbreviation;
|
|
|
|
} else {
|
|
|
|
// determine denotation type
|
|
|
|
argDenotation = *argv;
|
|
|
|
if (!*argDenotation && (!lastArgInLevel || values->size() >= lastArgInLevel->requiredValueCount())) {
|
|
|
|
// skip empty arguments
|
|
|
|
++index, ++argv, argDenotation = nullptr;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
abbreviationFound = false;
|
|
|
|
argDenotationType = Value;
|
|
|
|
*argDenotation == '-' && (++argDenotation, ++argDenotationType) && *argDenotation == '-' && (++argDenotation, ++argDenotationType);
|
|
|
|
}
|
|
|
|
|
|
|
|
// try to find matching Argument instance
|
|
|
|
Argument *matchingArg = nullptr;
|
|
|
|
if (argDenotationType != Value) {
|
|
|
|
// determine actual denotation length (everything before equation sign)
|
|
|
|
const char *const equationPos = strchr(argDenotation, '=');
|
|
|
|
const auto argDenotationLength = equationPos ? static_cast<size_t>(equationPos - argDenotation) : strlen(argDenotation);
|
|
|
|
|
|
|
|
// loop through each "part" of the denotation
|
|
|
|
// names are read at once, but for abbreviations each character is considered individually
|
|
|
|
for (; argDenotationLength; matchingArg = nullptr) {
|
|
|
|
// search for arguments by abbreviation or name depending on the previously determined denotation type
|
|
|
|
if (argDenotationType == Abbreviation) {
|
|
|
|
for (Argument *const arg : args) {
|
|
|
|
if (arg->abbreviation() && arg->abbreviation() == *argDenotation) {
|
|
|
|
matchingArg = arg;
|
|
|
|
abbreviationFound = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (Argument *const arg : args) {
|
|
|
|
if (arg->matchesDenotation(argDenotation, argDenotationLength)) {
|
|
|
|
matchingArg = arg;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!matchingArg) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// an argument matched the specified denotation so add an occurrence
|
|
|
|
matchingArg->m_occurrences.emplace_back(index, parentPath, parentArg);
|
|
|
|
|
|
|
|
// prepare reading parameter values
|
|
|
|
values = &matchingArg->m_occurrences.back().values;
|
|
|
|
|
|
|
|
// read value after equation sign
|
|
|
|
if ((argDenotationType != Abbreviation && equationPos) || (++argDenotation == equationPos)) {
|
|
|
|
values->push_back(equationPos + 1);
|
|
|
|
argDenotation = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
// read sub arguments, distinguish whether further abbreviations follow
|
|
|
|
++index, ++parser.m_actualArgc, lastArg = lastArgInLevel = matchingArg, lastArgDenotation = argv;
|
|
|
|
if (argDenotationType != Abbreviation || !argDenotation || !*argDenotation) {
|
|
|
|
// no further abbreviations follow -> read sub args for next argv
|
|
|
|
++argv, argDenotation = nullptr;
|
|
|
|
read(lastArg->m_subArgs);
|
|
|
|
argDenotation = nullptr;
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
// further abbreviations follow -> remember current arg value
|
|
|
|
const char *const *const currentArgValue = argv;
|
|
|
|
// don't increment argv, keep processing outstanding chars of argDenotation
|
|
|
|
read(lastArg->m_subArgs);
|
|
|
|
// stop further processing if the denotation has been consumed or even the next value has already been loaded
|
|
|
|
if (!argDenotation || currentArgValue != argv) {
|
|
|
|
argDenotation = nullptr;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// continue with next arg if we've got a match already
|
|
|
|
if (matchingArg) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// unknown argument might be a sibling of the parent element
|
|
|
|
for (auto parentArgument = parentPath.crbegin(), pathEnd = parentPath.crend();; ++parentArgument) {
|
|
|
|
for (Argument *const sibling : (parentArgument != pathEnd ? (*parentArgument)->subArguments() : parser.m_mainArgs)) {
|
|
|
|
if (sibling->occurrences() < sibling->maxOccurrences()) {
|
|
|
|
// check whether the denoted abbreviation matches the sibling's abbreviatiopn
|
|
|
|
if (argDenotationType == Abbreviation && (sibling->abbreviation() && sibling->abbreviation() == *argDenotation)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// check whether the denoted name matches the sibling's name
|
|
|
|
if (sibling->matchesDenotation(argDenotation, argDenotationLength)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (parentArgument == pathEnd) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// unknown argument might just be a parameter value of the last argument
|
|
|
|
if (lastArgInLevel && values->size() < lastArgInLevel->requiredValueCount()) {
|
|
|
|
values->emplace_back(abbreviationFound ? argDenotation : *argv);
|
|
|
|
++index, ++argv, argDenotation = nullptr;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// first value might denote "operation"
|
|
|
|
for (Argument *const arg : args) {
|
|
|
|
if (arg->denotesOperation() && arg->name() && !strcmp(arg->name(), *argv)) {
|
|
|
|
(matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
|
|
|
|
lastArgDenotation = argv;
|
|
|
|
++index, ++argv;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// use the first default argument which is not already present if there is still no match
|
|
|
|
if (!matchingArg && (!completionMode || (argv + 1 != end))) {
|
|
|
|
const bool uncombinableMainArgPresent = parentArg ? false : parser.isUncombinableMainArgPresent();
|
|
|
|
for (Argument *const arg : args) {
|
|
|
|
if (arg->isImplicit() && !arg->isPresent() && !arg->wouldConflictWithArgument()
|
|
|
|
&& (!uncombinableMainArgPresent || !arg->isMainArgument())) {
|
|
|
|
(matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (matchingArg) {
|
|
|
|
// an argument matched the specified denotation
|
|
|
|
if (lastArgInLevel == matchingArg) {
|
|
|
|
break; // break required? -> TODO: add test for this condition
|
|
|
|
}
|
|
|
|
|
|
|
|
// prepare reading parameter values
|
|
|
|
values = &matchingArg->m_occurrences.back().values;
|
|
|
|
|
|
|
|
// read sub arguments
|
|
|
|
++parser.m_actualArgc, lastArg = lastArgInLevel = matchingArg, argDenotation = nullptr;
|
|
|
|
read(lastArg->m_subArgs);
|
|
|
|
argDenotation = nullptr;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// argument denotation is unknown -> handle error
|
|
|
|
if (parentArg) {
|
|
|
|
// continue with parent level
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (completionMode) {
|
|
|
|
// ignore unknown denotation
|
|
|
|
++index, ++argv, argDenotation = nullptr;
|
|
|
|
} else {
|
|
|
|
switch (parser.m_unknownArgBehavior) {
|
|
|
|
case UnknownArgumentBehavior::Warn:
|
|
|
|
cerr << Phrases::Warning << "The specified argument \"" << *argv << "\" is unknown and will be ignored." << Phrases::EndFlush;
|
|
|
|
FALLTHROUGH;
|
|
|
|
case UnknownArgumentBehavior::Ignore:
|
|
|
|
// ignore unknown denotation
|
|
|
|
++index, ++argv, argDenotation = nullptr;
|
|
|
|
break;
|
|
|
|
case UnknownArgumentBehavior::Fail:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // while(argv != end)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \class Wrapper
|
|
|
|
* \brief The Wrapper class is internally used print text which might needs to be wrapped preserving the indentation.
|
|
|
|
* \remarks This class is explicitely *not* part of the public API.
|
|
|
|
*/
|
|
|
|
|
|
|
|
ostream &operator<<(ostream &os, const Wrapper &wrapper)
|
|
|
|
{
|
|
|
|
// determine max. number of columns
|
|
|
|
static const TerminalSize termSize(determineTerminalSize());
|
|
|
|
const auto maxColumns = termSize.columns ? termSize.columns : numeric_limits<unsigned short>::max();
|
|
|
|
|
|
|
|
// print wrapped string considering indentation
|
|
|
|
unsigned short currentCol = wrapper.m_indentation.level;
|
|
|
|
for (const char *currentChar = wrapper.m_str; *currentChar; ++currentChar) {
|
|
|
|
const bool wrappingRequired = currentCol >= maxColumns;
|
|
|
|
if (wrappingRequired || *currentChar == '\n') {
|
|
|
|
// insert newline (TODO: wrap only at end of a word)
|
|
|
|
os << '\n';
|
|
|
|
// print indentation (if enough space)
|
|
|
|
if (wrapper.m_indentation.level < maxColumns) {
|
|
|
|
os << wrapper.m_indentation;
|
|
|
|
currentCol = wrapper.m_indentation.level;
|
|
|
|
} else {
|
|
|
|
currentCol = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (*currentChar != '\n' && (!wrappingRequired || *currentChar != ' ')) {
|
|
|
|
os << *currentChar;
|
|
|
|
++currentCol;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return os;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// \brief Specifies the name of the application (used by ArgumentParser::printHelp()).
|
|
|
|
const char *applicationName = nullptr;
|
|
|
|
/// \brief Specifies the author of the application (used by ArgumentParser::printHelp()).
|
|
|
|
const char *applicationAuthor = nullptr;
|
|
|
|
/// \brief Specifies the version of the application (used by ArgumentParser::printHelp()).
|
|
|
|
const char *applicationVersion = nullptr;
|
|
|
|
/// \brief Specifies the URL to the application website (used by ArgumentParser::printHelp()).
|
|
|
|
const char *applicationUrl = nullptr;
|
|
|
|
/// \brief Specifies the dependency versions the application was linked against (used by ArgumentParser::printHelp()).
|
|
|
|
/// \deprecated Not used anymore. Use dependencyVersions2 instead.
|
|
|
|
std::initializer_list<const char *> dependencyVersions;
|
|
|
|
/// \brief Specifies the dependency versions the application was linked against (used by ArgumentParser::printHelp()).
|
|
|
|
std::vector<const char *> dependencyVersions2;
|
|
|
|
|
|
|
|
// TODO v5 use a struct for these properties
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Specifies a function quit the application.
|
|
|
|
* \remarks Currently only used after printing Bash completion. Default is std::exit().
|
|
|
|
*/
|
|
|
|
void (*exitFunction)(int) = &exit;
|
|
|
|
|
|
|
|
/// \cond
|
|
|
|
|
|
|
|
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.
|
|
|
|
*
|
|
|
|
* Instaces of the Argument class are used as definition when parsing command line
|
|
|
|
* arguments. Arguments can be assigned to an ArgumentParser using
|
|
|
|
* ArgumentParser::setMainArguments() and to another Argument instance using
|
|
|
|
* Argument::setSecondaryArguments().
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Constructs an Argument with the given \a name, \a abbreviation and \a description.
|
|
|
|
*
|
|
|
|
* 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, char abbreviation, const char *description, const char *example)
|
|
|
|
: m_name(name)
|
|
|
|
, m_abbreviation(abbreviation)
|
|
|
|
, m_environmentVar(nullptr)
|
|
|
|
, m_description(description)
|
|
|
|
, m_example(example)
|
|
|
|
, m_minOccurrences(0)
|
|
|
|
, m_maxOccurrences(1)
|
|
|
|
, m_combinable(false)
|
|
|
|
, m_denotesOperation(false)
|
|
|
|
, m_requiredValueCount(0)
|
|
|
|
, m_implicit(false)
|
|
|
|
, m_isMainArg(false)
|
|
|
|
, m_valueCompletionBehavior(ValueCompletionBehavior::PreDefinedValues | ValueCompletionBehavior::Files | ValueCompletionBehavior::Directories
|
|
|
|
| ValueCompletionBehavior::FileSystemIfNoPreDefinedValues)
|
|
|
|
, m_preDefinedCompletionValues(nullptr)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Destroys the Argument.
|
|
|
|
*/
|
|
|
|
Argument::~Argument()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns the first parameter value of the first occurrence of the argument.
|
|
|
|
* \remarks
|
|
|
|
* - If the argument is not present and the an environment variable has been set
|
|
|
|
* using setEnvironmentVariable() the value of the specified variable will be returned.
|
|
|
|
* - Returns nullptr if no value is available though.
|
|
|
|
*/
|
|
|
|
const char *Argument::firstValue() const
|
|
|
|
{
|
|
|
|
if (!m_occurrences.empty() && !m_occurrences.front().values.empty()) {
|
|
|
|
return m_occurrences.front().values.front();
|
|
|
|
} else if (m_environmentVar) {
|
|
|
|
return getenv(m_environmentVar);
|
|
|
|
} else {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Writes the name, the abbreviation and other information about the Argument to the give ostream.
|
|
|
|
*/
|
|
|
|
void Argument::printInfo(ostream &os, unsigned char indentation) const
|
|
|
|
{
|
|
|
|
Indentation ident(indentation);
|
|
|
|
os << ident;
|
|
|
|
EscapeCodes::setStyle(os, EscapeCodes::TextAttribute::Bold);
|
|
|
|
if (notEmpty(name())) {
|
|
|
|
if (!denotesOperation()) {
|
|
|
|
os << '-' << '-';
|
|
|
|
}
|
|
|
|
os << name();
|
|
|
|
}
|
|
|
|
if (notEmpty(name()) && abbreviation()) {
|
|
|
|
os << ',' << ' ';
|
|
|
|
}
|
|
|
|
if (abbreviation()) {
|
|
|
|
os << '-' << abbreviation();
|
|
|
|
}
|
|
|
|
EscapeCodes::setStyle(os);
|
|
|
|
if (requiredValueCount()) {
|
|
|
|
unsigned int valueNamesPrint = 0;
|
|
|
|
for (auto i = valueNames().cbegin(), end = valueNames().cend(); i != end && valueNamesPrint < requiredValueCount(); ++i) {
|
|
|
|
os << ' ' << '[' << *i << ']';
|
|
|
|
++valueNamesPrint;
|
|
|
|
}
|
|
|
|
if (requiredValueCount() == Argument::varValueCount) {
|
|
|
|
os << " ...";
|
|
|
|
} else {
|
|
|
|
for (; valueNamesPrint < requiredValueCount(); ++valueNamesPrint) {
|
|
|
|
os << " [value " << (valueNamesPrint + 1) << ']';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ident.level += 2;
|
|
|
|
if (notEmpty(description())) {
|
|
|
|
os << '\n' << ident << Wrapper(description(), ident);
|
|
|
|
}
|
|
|
|
if (isRequired()) {
|
|
|
|
os << '\n' << ident << "particularities: mandatory";
|
|
|
|
if (!isMainArgument()) {
|
|
|
|
os << " if parent argument is present";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (environmentVariable()) {
|
|
|
|
os << '\n' << ident << "default environment variable: " << Wrapper(environmentVariable(), ident + 30);
|
|
|
|
}
|
|
|
|
os << '\n';
|
|
|
|
for (const auto *arg : subArguments()) {
|
|
|
|
arg->printInfo(os, ident.level);
|
|
|
|
}
|
|
|
|
if (notEmpty(example())) {
|
|
|
|
if (ident.level == 2 && !subArguments().empty()) {
|
|
|
|
os << '\n';
|
|
|
|
}
|
|
|
|
os << ident << "example: " << Wrapper(example(), ident + 9);
|
|
|
|
os << '\n';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief This function return the first present and uncombinable argument of the given list of arguments.
|
|
|
|
*
|
|
|
|
* The Argument \a except will be ignored.
|
|
|
|
*/
|
|
|
|
Argument *firstPresentUncombinableArg(const ArgumentVector &args, const Argument *except)
|
|
|
|
{
|
|
|
|
for (Argument *arg : args) {
|
|
|
|
if (arg != except && arg->isPresent() && !arg->isCombinable()) {
|
|
|
|
return arg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Sets the secondary arguments for this arguments.
|
|
|
|
*
|
|
|
|
* The given arguments will be considered as secondary arguments of this argument by the argument parser.
|
|
|
|
* This means that the parser will complain if these arguments are given, but not this argument.
|
|
|
|
* If secondary arguments are labeled as mandatory their parent is also mandatory.
|
|
|
|
*
|
|
|
|
* The Argument does not take ownership. Do not destroy the given arguments as long as they are
|
|
|
|
* used as secondary arguments.
|
|
|
|
*
|
|
|
|
* \sa secondaryArguments()
|
|
|
|
* \sa addSecondaryArgument()
|
|
|
|
* \sa hasSecondaryArguments()
|
|
|
|
*/
|
|
|
|
void Argument::setSubArguments(const ArgumentInitializerList &secondaryArguments)
|
|
|
|
{
|
|
|
|
// remove this argument from the parents list of the previous secondary arguments
|
|
|
|
for (Argument *arg : m_subArgs) {
|
|
|
|
arg->m_parents.erase(remove(arg->m_parents.begin(), arg->m_parents.end(), this), arg->m_parents.end());
|
|
|
|
}
|
|
|
|
// assign secondary arguments
|
|
|
|
m_subArgs.assign(secondaryArguments);
|
|
|
|
// add this argument to the parents list of the assigned secondary arguments
|
|
|
|
// and set the parser
|
|
|
|
for (Argument *arg : m_subArgs) {
|
|
|
|
if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
|
|
|
|
arg->m_parents.push_back(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Adds \a arg as a secondary argument for this argument.
|
|
|
|
*
|
|
|
|
* \sa secondaryArguments()
|
|
|
|
* \sa setSecondaryArguments()
|
|
|
|
* \sa hasSecondaryArguments()
|
|
|
|
*/
|
|
|
|
void Argument::addSubArgument(Argument *arg)
|
|
|
|
{
|
|
|
|
if (find(m_subArgs.cbegin(), m_subArgs.cend(), arg) == m_subArgs.cend()) {
|
|
|
|
m_subArgs.push_back(arg);
|
|
|
|
if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
|
|
|
|
arg->m_parents.push_back(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns whether at least one parent argument is present.
|
|
|
|
* \remarks Returns always true for main arguments.
|
|
|
|
*/
|
|
|
|
bool Argument::isParentPresent() const
|
|
|
|
{
|
|
|
|
if (isMainArgument()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
for (const Argument *parent : m_parents) {
|
|
|
|
if (parent->isPresent()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Checks if this arguments conflicts with other arguments.
|
|
|
|
*
|
|
|
|
* If the argument is in conflict with an other argument this argument will be returned.
|
|
|
|
* Otherwise nullptr will be returned.
|
|
|
|
*
|
|
|
|
* \remarks Conflicts with main arguments aren't considered by this method!
|
|
|
|
*/
|
|
|
|
Argument *Argument::conflictsWithArgument() const
|
|
|
|
{
|
|
|
|
return isPresent() ? wouldConflictWithArgument() : nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Checks if this argument would conflict with other arguments if it was present.
|
|
|
|
*
|
|
|
|
* If the argument is in conflict with an other argument this argument will be returned.
|
|
|
|
* Otherwise nullptr will be returned.
|
|
|
|
*
|
|
|
|
* \remarks Conflicts with main arguments aren't considered by this method!
|
|
|
|
*/
|
|
|
|
Argument *Argument::wouldConflictWithArgument() const
|
|
|
|
{
|
|
|
|
if (!isCombinable()) {
|
|
|
|
for (Argument *parent : m_parents) {
|
|
|
|
for (Argument *sibling : parent->subArguments()) {
|
|
|
|
if (sibling != this && sibling->isPresent() && !sibling->isCombinable()) {
|
|
|
|
return sibling;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Returns the first operation argument specified by the user or nullptr if no operation has been specified.
|
|
|
|
* \remarks Only direct sub arguments of this argument are considered.
|
|
|
|
*/
|
|
|
|
Argument *Argument::specifiedOperation() const
|
|
|
|
{
|
|
|
|
for (Argument *arg : m_subArgs) {
|
|
|
|
if (arg->denotesOperation() && arg->isPresent()) {
|
|
|
|
return arg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Resets this argument and all sub arguments recursively.
|
|
|
|
* \sa Argument::reset()
|
|
|
|
*/
|
|
|
|
void Argument::resetRecursively()
|
|
|
|
{
|
|
|
|
for (Argument *arg : m_subArgs) {
|
|
|
|
arg->resetRecursively();
|
|
|
|
}
|
|
|
|
reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \class ApplicationUtilities::ArgumentParser
|
|
|
|
* \brief The ArgumentParser class provides a means for handling command line arguments.
|
|
|
|
*
|
|
|
|
* To setup the parser create instances of ApplicationUtilities::Argument to define a
|
|
|
|
* set of known arguments and assign these to the parser using setMainArguments().
|
|
|
|
*
|
|
|
|
* To invoke parsing call parseArgs(). The parser will verify the previously
|
|
|
|
* assigned definitions (and might throw std::invalid_argument) and then parse the
|
|
|
|
* given command line arguments according the definitions (and might throw
|
|
|
|
* ApplicationUtilities::Failure).
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Constructs a new ArgumentParser.
|
|
|
|
*/
|
|
|
|
ArgumentParser::ArgumentParser()
|
|
|
|
: m_actualArgc(0)
|
|
|
|
, m_executable(nullptr)
|
|
|
|
, m_unknownArgBehavior(UnknownArgumentBehavior::Fail)
|
|
|
|
, m_defaultArg(nullptr)
|
|
|
|
, m_helpArg(*this)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Sets the main arguments for the parser. The parser will use these argument definitions
|
|
|
|
* to when parsing the command line arguments and when printing help information.
|
|
|
|
* \remarks
|
|
|
|
* - The parser does not take ownership. Do not destroy the arguments as long as they are used as
|
|
|
|
* main arguments.
|
|
|
|
* - Sets the first specified argument as default argument if none has been assigned yet and the
|
|
|
|
* first argument does not require any values or has no mandatory sub arguments.
|
|
|
|
*/
|
|
|
|
void ArgumentParser::setMainArguments(const ArgumentInitializerList &mainArguments)
|
|
|
|
{
|
|
|
|
if (mainArguments.size()) {
|
|
|
|
for (Argument *arg : mainArguments) {
|
|
|
|
arg->m_isMainArg = true;
|
|
|
|
}
|
|
|
|
m_mainArgs.assign(mainArguments);
|
|
|
|
if (!m_defaultArg) {
|
|
|
|
if (!(*mainArguments.begin())->requiredValueCount()) {
|
|
|