Refactor reading arguments
Replace ArgumentParser::readSpecifiedArgs() with ArgumentReader class to simplify argument list
This commit is contained in:
parent
9d91cfca07
commit
cab332bcad
|
@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
|
||||||
# add project files
|
# add project files
|
||||||
set(HEADER_FILES
|
set(HEADER_FILES
|
||||||
application/argumentparser.h
|
application/argumentparser.h
|
||||||
|
application/argumentparserprivate.h
|
||||||
application/commandlineutils.h
|
application/commandlineutils.h
|
||||||
application/failure.h
|
application/failure.h
|
||||||
application/fakeqtconfigarguments.h
|
application/fakeqtconfigarguments.h
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "./argumentparser.h"
|
#include "./argumentparser.h"
|
||||||
|
#include "./argumentparserprivate.h"
|
||||||
#include "./commandlineutils.h"
|
#include "./commandlineutils.h"
|
||||||
#include "./failure.h"
|
#include "./failure.h"
|
||||||
|
|
||||||
|
@ -24,6 +25,234 @@ using namespace IoUtilities;
|
||||||
*/
|
*/
|
||||||
namespace ApplicationUtilities {
|
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 ArgReader struct 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.
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
{}
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
void ArgumentReader::read()
|
||||||
|
{
|
||||||
|
read(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Reads the commands line arguments specified when constructing the object.
|
||||||
|
*/
|
||||||
|
void 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) {
|
||||||
|
if(values && lastArgInLevel->requiredValueCount() != static_cast<size_t>(-1) && values->size() < lastArgInLevel->requiredValueCount()) {
|
||||||
|
// there are still values to read
|
||||||
|
values->emplace_back(argDenotation ? argDenotation : *argv);
|
||||||
|
++index, ++argv, argDenotation = nullptr;
|
||||||
|
} else {
|
||||||
|
// determine how denotation must be processed
|
||||||
|
bool abbreviationFound = false;
|
||||||
|
unsigned char argDenotationType;
|
||||||
|
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;
|
||||||
|
size_t argDenotationLength;
|
||||||
|
if(argDenotationType != Value) {
|
||||||
|
const char *const equationPos = strchr(argDenotation, '=');
|
||||||
|
for(argDenotationLength = equationPos ? static_cast<size_t>(equationPos - argDenotation) : strlen(argDenotation); argDenotationLength; matchingArg = nullptr) {
|
||||||
|
// search for arguments by abbreviation or name depending on the previously determined denotation type
|
||||||
|
if(argDenotationType == Abbreviation) {
|
||||||
|
for(Argument *arg : args) {
|
||||||
|
if(arg->abbreviation() && arg->abbreviation() == *argDenotation) {
|
||||||
|
matchingArg = arg;
|
||||||
|
abbreviationFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for(Argument *arg : args) {
|
||||||
|
if(arg->name() && !strncmp(arg->name(), argDenotation, argDenotationLength) && *(arg->name() + argDenotationLength) == '\0') {
|
||||||
|
matchingArg = arg;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(matchingArg) {
|
||||||
|
// 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;
|
||||||
|
if(equationPos) {
|
||||||
|
values->push_back(equationPos + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// read sub arguments
|
||||||
|
++index, ++parser.m_actualArgc, lastArg = lastArgInLevel = matchingArg;
|
||||||
|
if(argDenotationType != Abbreviation || (++argDenotation != equationPos)) {
|
||||||
|
if(argDenotationType != Abbreviation || !*argDenotation) {
|
||||||
|
// no further abbreviations follow -> read sub args for next argv
|
||||||
|
++argv, argDenotation = nullptr;
|
||||||
|
read(lastArg->m_subArgs);
|
||||||
|
argDenotation = nullptr;
|
||||||
|
} else {
|
||||||
|
// further abbreviations follow -> don't increment argv, keep processing outstanding chars of argDenotation
|
||||||
|
read(lastArg->m_subArgs);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} // else: another abbreviated argument follows (and it is not present in the sub args)
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!matchingArg) {
|
||||||
|
// unknown argument might be a sibling of the parent element
|
||||||
|
if(argDenotationType != Value) {
|
||||||
|
for(auto parentArgument = parentPath.crbegin(), pathEnd = parentPath.crend(); ; ++parentArgument) {
|
||||||
|
for(Argument *sibling : (parentArgument != pathEnd ? (*parentArgument)->subArguments() : parser.m_mainArgs)) {
|
||||||
|
if(sibling->occurrences() < sibling->maxOccurrences()) {
|
||||||
|
if((argDenotationType == Abbreviation && (sibling->abbreviation() && sibling->abbreviation() == *argDenotation))
|
||||||
|
|| (sibling->name() && !strncmp(sibling->name(), argDenotation, argDenotationLength))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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"
|
||||||
|
if(!index) {
|
||||||
|
for(Argument *arg : args) {
|
||||||
|
if(arg->denotesOperation() && arg->name() && !strcmp(arg->name(), *argv)) {
|
||||||
|
(matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
|
||||||
|
++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 *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;
|
||||||
|
}
|
||||||
|
if(completionMode) {
|
||||||
|
// ignore unknown denotation
|
||||||
|
++index, ++argv, argDenotation = nullptr;
|
||||||
|
} else {
|
||||||
|
switch(parser.m_unknownArgBehavior) {
|
||||||
|
case UnknownArgumentBehavior::Warn:
|
||||||
|
cerr << "The specified argument \"" << *argv << "\" is unknown and will be ignored." << endl;
|
||||||
|
FALLTHROUGH;
|
||||||
|
case UnknownArgumentBehavior::Ignore:
|
||||||
|
// ignore unknown denotation
|
||||||
|
++index, ++argv, argDenotation = nullptr;
|
||||||
|
break;
|
||||||
|
case UnknownArgumentBehavior::Fail:
|
||||||
|
throw Failure("The specified argument \"" + string(*argv) + "\" is unknown and will be ignored.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // if(!matchingArg)
|
||||||
|
} // no values to read
|
||||||
|
} // while(argv != end)
|
||||||
|
}
|
||||||
|
|
||||||
/// \brief Specifies the name of the application (used by ArgumentParser::printHelp()).
|
/// \brief Specifies the name of the application (used by ArgumentParser::printHelp()).
|
||||||
const char *applicationName = nullptr;
|
const char *applicationName = nullptr;
|
||||||
/// \brief Specifies the author of the application (used by ArgumentParser::printHelp()).
|
/// \brief Specifies the author of the application (used by ArgumentParser::printHelp()).
|
||||||
|
@ -48,15 +277,6 @@ inline bool notEmpty(const char *str)
|
||||||
|
|
||||||
/// \endcond
|
/// \endcond
|
||||||
|
|
||||||
/*!
|
|
||||||
* \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 */
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \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.
|
||||||
|
@ -445,15 +665,10 @@ void ArgumentParser::readArgs(int argc, const char * const *argv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// those variables are modified by readSpecifiedArgs() and reflect the current reading position
|
|
||||||
size_t index = 0;
|
|
||||||
Argument *lastDetectedArgument = nullptr;
|
|
||||||
|
|
||||||
// read specified arguments
|
// read specified arguments
|
||||||
|
ArgumentReader reader(*this, argv, argv + (completionMode ? min(static_cast<unsigned int>(argc), currentWordIndex + 1) : static_cast<unsigned int>(argc)), completionMode);
|
||||||
try {
|
try {
|
||||||
const char *const *argv2 = argv;
|
reader.read();
|
||||||
const char *argDenotation = nullptr;
|
|
||||||
readSpecifiedArgs(m_mainArgs, index, argv2, argv + (completionMode ? min(static_cast<unsigned int>(argc), currentWordIndex + 1) : static_cast<unsigned int>(argc)), lastDetectedArgument, argDenotation, completionMode);
|
|
||||||
} catch(const Failure &) {
|
} catch(const Failure &) {
|
||||||
if(!completionMode) {
|
if(!completionMode) {
|
||||||
throw;
|
throw;
|
||||||
|
@ -461,7 +676,7 @@ void ArgumentParser::readArgs(int argc, const char * const *argv)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(completionMode) {
|
if(completionMode) {
|
||||||
printBashCompletion(argc, argv, currentWordIndex, lastDetectedArgument);
|
printBashCompletion(argc, argv, currentWordIndex, reader.lastArg);
|
||||||
exitFunction(0); // prevent the applicaton to continue with the regular execution
|
exitFunction(0); // prevent the applicaton to continue with the regular execution
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -539,196 +754,6 @@ void ApplicationUtilities::ArgumentParser::verifyArgs(const ArgumentVector &args
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*!
|
|
||||||
* \brief Reads the specified commands line arguments.
|
|
||||||
* \param args Specifies the Argument instances to store the results. Sub arguments of \a args are considered as well.
|
|
||||||
* \param index Specifies and index which is incremented when an argument is encountered (the current index is stored in the occurrence) or a value is encountered.
|
|
||||||
* \param argv Points to the first argument denotation and will be incremented when a denotation has been processed.
|
|
||||||
* \param end Points to the end of the \a argv array.
|
|
||||||
* \param lastArg Specifies the last Argument instance which could be detected. Set to nullptr in the initial call. Used for Bash completion.
|
|
||||||
* \param argDenotation Specifies the currently processed abbreviation denotation (should be substring of \a argv). Set to nullptr for processing \a argv from the beginning (default).
|
|
||||||
* \param completionMode Specifies whether completion mode is enabled. In this case reading args will be continued even if an denotation is unknown (regardless of unknownArgumentBehavior()).
|
|
||||||
* \remarks Results are stored in specified \a args and assigned sub arguments.
|
|
||||||
*/
|
|
||||||
void ArgumentParser::readSpecifiedArgs(ArgumentVector &args, std::size_t &index, const char *const *&argv, const char *const *end, Argument *&lastArg, const char *&argDenotation, bool completionMode)
|
|
||||||
{
|
|
||||||
// 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) {
|
|
||||||
if(values && lastArgInLevel->requiredValueCount() != static_cast<size_t>(-1) && values->size() < lastArgInLevel->requiredValueCount()) {
|
|
||||||
// there are still values to read
|
|
||||||
values->emplace_back(argDenotation ? argDenotation : *argv);
|
|
||||||
++index, ++argv, argDenotation = nullptr;
|
|
||||||
} else {
|
|
||||||
// determine how denotation must be processed
|
|
||||||
bool abbreviationFound = false;
|
|
||||||
unsigned char argDenotationType;
|
|
||||||
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;
|
|
||||||
size_t argDenotationLength;
|
|
||||||
if(argDenotationType != Value) {
|
|
||||||
const char *const equationPos = strchr(argDenotation, '=');
|
|
||||||
for(argDenotationLength = equationPos ? static_cast<size_t>(equationPos - argDenotation) : strlen(argDenotation); argDenotationLength; matchingArg = nullptr) {
|
|
||||||
// search for arguments by abbreviation or name depending on the previously determined denotation type
|
|
||||||
if(argDenotationType == Abbreviation) {
|
|
||||||
for(Argument *arg : args) {
|
|
||||||
if(arg->abbreviation() && arg->abbreviation() == *argDenotation) {
|
|
||||||
matchingArg = arg;
|
|
||||||
abbreviationFound = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(Argument *arg : args) {
|
|
||||||
if(arg->name() && !strncmp(arg->name(), argDenotation, argDenotationLength) && *(arg->name() + argDenotationLength) == '\0') {
|
|
||||||
matchingArg = arg;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(matchingArg) {
|
|
||||||
// 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;
|
|
||||||
if(equationPos) {
|
|
||||||
values->push_back(equationPos + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// read sub arguments
|
|
||||||
++index, ++m_actualArgc, lastArg = lastArgInLevel = matchingArg;
|
|
||||||
if(argDenotationType != Abbreviation || (++argDenotation != equationPos)) {
|
|
||||||
if(argDenotationType != Abbreviation || !*argDenotation) {
|
|
||||||
// no further abbreviations follow -> read sub args for next argv
|
|
||||||
readSpecifiedArgs(lastArg->m_subArgs, index, ++argv, end, lastArg, argDenotation = nullptr, completionMode);
|
|
||||||
argDenotation = nullptr;
|
|
||||||
} else {
|
|
||||||
// further abbreviations follow -> don't increment argv, keep processing outstanding chars of argDenotation
|
|
||||||
readSpecifiedArgs(lastArg->m_subArgs, index, argv, end, lastArg, argDenotation, completionMode);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} // else: another abbreviated argument follows (and it is not present in the sub args)
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!matchingArg) {
|
|
||||||
// unknown argument might be a sibling of the parent element
|
|
||||||
if(argDenotationType != Value) {
|
|
||||||
for(auto parentArgument = parentPath.crbegin(), pathEnd = parentPath.crend(); ; ++parentArgument) {
|
|
||||||
for(Argument *sibling : (parentArgument != pathEnd ? (*parentArgument)->subArguments() : m_mainArgs)) {
|
|
||||||
if(sibling->occurrences() < sibling->maxOccurrences()) {
|
|
||||||
if((argDenotationType == Abbreviation && (sibling->abbreviation() && sibling->abbreviation() == *argDenotation))
|
|
||||||
|| (sibling->name() && !strncmp(sibling->name(), argDenotation, argDenotationLength))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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"
|
|
||||||
if(!index) {
|
|
||||||
for(Argument *arg : args) {
|
|
||||||
if(arg->denotesOperation() && arg->name() && !strcmp(arg->name(), *argv)) {
|
|
||||||
(matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
|
|
||||||
++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 : isUncombinableMainArgPresent();
|
|
||||||
for(Argument *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
|
|
||||||
++m_actualArgc, lastArg = lastArgInLevel = matchingArg;
|
|
||||||
readSpecifiedArgs(lastArg->m_subArgs, index, argv, end, lastArg, argDenotation = nullptr, completionMode);
|
|
||||||
argDenotation = nullptr;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// argument denotation is unknown -> handle error
|
|
||||||
if(parentArg) {
|
|
||||||
// continue with parent level
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(completionMode) {
|
|
||||||
// ignore unknown denotation
|
|
||||||
++index, ++argv, argDenotation = nullptr;
|
|
||||||
} else {
|
|
||||||
switch(m_unknownArgBehavior) {
|
|
||||||
case UnknownArgumentBehavior::Warn:
|
|
||||||
cerr << "The specified argument \"" << *argv << "\" is unknown and will be ignored." << endl;
|
|
||||||
FALLTHROUGH;
|
|
||||||
case UnknownArgumentBehavior::Ignore:
|
|
||||||
// ignore unknown denotation
|
|
||||||
++index, ++argv, argDenotation = nullptr;
|
|
||||||
break;
|
|
||||||
case UnknownArgumentBehavior::Fail:
|
|
||||||
throw Failure("The specified argument \"" + string(*argv) + "\" is unknown and will be ignored.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // if(!matchingArg)
|
|
||||||
} // no values to read
|
|
||||||
} // while(argv != end)
|
|
||||||
}
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Returns whether \a arg1 should be listed before \a arg2 when
|
* \brief Returns whether \a arg1 should be listed before \a arg2 when
|
||||||
* printing completion.
|
* printing completion.
|
||||||
|
|
|
@ -34,6 +34,7 @@ CPP_UTILITIES_EXPORT extern void(*exitFunction)(int);
|
||||||
|
|
||||||
class Argument;
|
class Argument;
|
||||||
class ArgumentParser;
|
class ArgumentParser;
|
||||||
|
class ArgumentReader;
|
||||||
|
|
||||||
typedef std::initializer_list<Argument *> ArgumentInitializerList;
|
typedef std::initializer_list<Argument *> ArgumentInitializerList;
|
||||||
typedef std::vector<Argument *> ArgumentVector;
|
typedef std::vector<Argument *> ArgumentVector;
|
||||||
|
@ -128,7 +129,8 @@ inline ArgumentOccurrence::ArgumentOccurrence(std::size_t index, const std::vect
|
||||||
|
|
||||||
class CPP_UTILITIES_EXPORT Argument
|
class CPP_UTILITIES_EXPORT Argument
|
||||||
{
|
{
|
||||||
friend class ArgumentParser;
|
friend ArgumentParser;
|
||||||
|
friend ArgumentReader;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
typedef std::function <void (const ArgumentOccurrence &)> CallbackFunction;
|
typedef std::function <void (const ArgumentOccurrence &)> CallbackFunction;
|
||||||
|
@ -212,6 +214,8 @@ private:
|
||||||
class CPP_UTILITIES_EXPORT ArgumentParser
|
class CPP_UTILITIES_EXPORT ArgumentParser
|
||||||
{
|
{
|
||||||
friend ArgumentParserTests;
|
friend ArgumentParserTests;
|
||||||
|
friend ArgumentReader;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ArgumentParser();
|
ArgumentParser();
|
||||||
|
|
||||||
|
@ -234,7 +238,6 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
IF_DEBUG_BUILD(void verifyArgs(const ArgumentVector &args, std::vector<char> abbreviations, std::vector<const char *> names);)
|
IF_DEBUG_BUILD(void verifyArgs(const ArgumentVector &args, std::vector<char> abbreviations, std::vector<const char *> names);)
|
||||||
void readSpecifiedArgs(ArgumentVector &args, std::size_t &index, const char *const *&argv, const char *const *end, Argument *&lastArg, const char *&argDenotation, bool completionMode = false);
|
|
||||||
void printBashCompletion(int argc, const char * const *argv, unsigned int cursorPos, const Argument *lastDetectedArg);
|
void printBashCompletion(int argc, const char * const *argv, unsigned int cursorPos, const Argument *lastDetectedArg);
|
||||||
void checkConstraints(const ArgumentVector &args);
|
void checkConstraints(const ArgumentVector &args);
|
||||||
void invokeCallbacks(const ArgumentVector &args);
|
void invokeCallbacks(const ArgumentVector &args);
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
#ifndef APPLICATION_UTILITIES_ARGUMENTPARSER_PRIVATE_H
|
||||||
|
#define APPLICATION_UTILITIES_ARGUMENTPARSER_PRIVATE_H
|
||||||
|
|
||||||
|
namespace ApplicationUtilities {
|
||||||
|
|
||||||
|
struct CPP_UTILITIES_EXPORT ArgumentReader
|
||||||
|
{
|
||||||
|
ArgumentReader(ArgumentParser &parser, const char *const *argv, const char *const *end, bool completionMode = false);
|
||||||
|
ApplicationUtilities::ArgumentReader &reset(const char *const *argv, const char *const *end);
|
||||||
|
void read();
|
||||||
|
void read(ArgumentVector &args);
|
||||||
|
|
||||||
|
/// \brief Specifies the associated ArgumentParser instance.
|
||||||
|
ArgumentParser &parser;
|
||||||
|
/// \brief Specifies the Argument instances to store the results. Sub arguments of args are considered as well.
|
||||||
|
ArgumentVector &args;
|
||||||
|
/// \brief Specifies and index which is incremented when an argument is encountered (the current index is stored in the occurrence) or a value is encountered.
|
||||||
|
size_t index;
|
||||||
|
/// \brief Points to the first argument denotation and will be incremented when a denotation has been processed.
|
||||||
|
const char *const *argv;
|
||||||
|
/// \brief Points to the end of the \a argv array.
|
||||||
|
const char *const *end;
|
||||||
|
/// \brief Specifies the last Argument instance which could be detected. Set to nullptr in the initial call. Used for Bash completion.
|
||||||
|
Argument *lastArg;
|
||||||
|
/// \brief Specifies the currently processed abbreviation denotation (should be substring of \a argv). Set to nullptr for processing \a argv from the beginning (default).
|
||||||
|
const char *argDenotation;
|
||||||
|
/// \brief Specifies whether completion mode is enabled. In this case reading args will be continued even if an denotation is unknown (regardless of unknownArgumentBehavior()).
|
||||||
|
bool completionMode;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // APPLICATION_UTILITIES_ARGUMENTPARSER_PRIVATE_H
|
|
@ -1,8 +1,6 @@
|
||||||
#ifdef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL
|
|
||||||
|
|
||||||
#ifndef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL
|
#ifndef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL
|
||||||
# error "Do not include binaryconversionprivate.h directly."
|
# error "Do not include binaryconversionprivate.h directly."
|
||||||
#endif
|
#else
|
||||||
|
|
||||||
#include "./types.h"
|
#include "./types.h"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "./testutils.h"
|
#include "./testutils.h"
|
||||||
|
|
||||||
#include "../application/argumentparser.h"
|
#include "../application/argumentparser.h"
|
||||||
|
#include "../application/argumentparserprivate.h"
|
||||||
#include "../application/failure.h"
|
#include "../application/failure.h"
|
||||||
#include "../application/fakeqtconfigarguments.h"
|
#include "../application/fakeqtconfigarguments.h"
|
||||||
|
|
||||||
|
@ -407,10 +408,6 @@ void ArgumentParserTests::testBashCompletion()
|
||||||
|
|
||||||
parser.setMainArguments({&helpArg, &displayFileInfoArg, &getArg, &setArg});
|
parser.setMainArguments({&helpArg, &displayFileInfoArg, &getArg, &setArg});
|
||||||
|
|
||||||
size_t index = 0;
|
|
||||||
Argument *lastDetectedArg = nullptr;
|
|
||||||
const char *argDenotation = nullptr;
|
|
||||||
|
|
||||||
// redirect cout to custom buffer
|
// redirect cout to custom buffer
|
||||||
stringstream buffer;
|
stringstream buffer;
|
||||||
streambuf *regularCoutBuffer = cout.rdbuf(buffer.rdbuf());
|
streambuf *regularCoutBuffer = cout.rdbuf(buffer.rdbuf());
|
||||||
|
@ -418,73 +415,66 @@ void ArgumentParserTests::testBashCompletion()
|
||||||
try {
|
try {
|
||||||
// fail due to operation flags not set
|
// fail due to operation flags not set
|
||||||
const char *const argv1[] = {"se"};
|
const char *const argv1[] = {"se"};
|
||||||
const char *const *argv = argv1;
|
ArgumentReader reader(parser, argv1, argv1 + 1, true);
|
||||||
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv1 + 1, lastDetectedArg, argDenotation = nullptr, true);
|
reader.read();
|
||||||
parser.printBashCompletion(1, argv1, 0, lastDetectedArg);
|
parser.printBashCompletion(1, argv1, 0, reader.lastArg);
|
||||||
cout.rdbuf(regularCoutBuffer);
|
cout.rdbuf(regularCoutBuffer);
|
||||||
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=()\n"), buffer.str());
|
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=()\n"), buffer.str());
|
||||||
|
|
||||||
// correct operation arg flags
|
// correct operation arg flags
|
||||||
index = 0, lastDetectedArg = nullptr, buffer.str(string());
|
buffer.str(string());
|
||||||
cout.rdbuf(buffer.rdbuf());
|
cout.rdbuf(buffer.rdbuf());
|
||||||
getArg.setDenotesOperation(true), setArg.setDenotesOperation(true);
|
getArg.setDenotesOperation(true), setArg.setDenotesOperation(true);
|
||||||
argv = argv1;
|
reader.reset(argv1, argv1 + 1).read();
|
||||||
parser.resetArgs();
|
parser.printBashCompletion(1, argv1, 0, reader.lastArg);
|
||||||
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv1 + 1, lastDetectedArg, argDenotation = nullptr, true);
|
|
||||||
parser.printBashCompletion(1, argv1, 0, lastDetectedArg);
|
|
||||||
cout.rdbuf(regularCoutBuffer);
|
cout.rdbuf(regularCoutBuffer);
|
||||||
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('set' )\n"), buffer.str());
|
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('set' )\n"), buffer.str());
|
||||||
|
|
||||||
// argument at current cursor position already specified -> the completion should just return the argument
|
// argument at current cursor position already specified -> the completion should just return the argument
|
||||||
const char *const argv2[] = {"set"};
|
const char *const argv2[] = {"set"};
|
||||||
index = 0, lastDetectedArg = nullptr, buffer.str(string());
|
buffer.str(string());
|
||||||
cout.rdbuf(buffer.rdbuf());
|
cout.rdbuf(buffer.rdbuf());
|
||||||
argv = argv2;
|
|
||||||
parser.resetArgs();
|
parser.resetArgs();
|
||||||
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv2 + 1, lastDetectedArg, argDenotation = nullptr, true);
|
reader.reset(argv2, argv2 + 1).read();
|
||||||
parser.printBashCompletion(1, argv2, 0, lastDetectedArg);
|
parser.printBashCompletion(1, argv2, 0, reader.lastArg);
|
||||||
cout.rdbuf(regularCoutBuffer);
|
cout.rdbuf(regularCoutBuffer);
|
||||||
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('set' )\n"), buffer.str());
|
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('set' )\n"), buffer.str());
|
||||||
|
|
||||||
// advance the cursor position -> the completion should propose the next argument
|
// advance the cursor position -> the completion should propose the next argument
|
||||||
index = 0, lastDetectedArg = nullptr, buffer.str(string());
|
buffer.str(string());
|
||||||
cout.rdbuf(buffer.rdbuf());
|
cout.rdbuf(buffer.rdbuf());
|
||||||
argv = argv2;
|
|
||||||
parser.resetArgs();
|
parser.resetArgs();
|
||||||
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv2 + 1, lastDetectedArg, argDenotation = nullptr, true);
|
reader.reset(argv2, argv2 + 1).read();
|
||||||
parser.printBashCompletion(1, argv2, 1, lastDetectedArg);
|
parser.printBashCompletion(1, argv2, 1, reader.lastArg);
|
||||||
cout.rdbuf(regularCoutBuffer);
|
cout.rdbuf(regularCoutBuffer);
|
||||||
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('--files' '--values' )\n"), buffer.str());
|
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('--files' '--values' )\n"), buffer.str());
|
||||||
|
|
||||||
// specifying no args should propose all main arguments
|
// specifying no args should propose all main arguments
|
||||||
index = 0, lastDetectedArg = nullptr, buffer.str(string());
|
buffer.str(string());
|
||||||
cout.rdbuf(buffer.rdbuf());
|
cout.rdbuf(buffer.rdbuf());
|
||||||
argv = nullptr;
|
|
||||||
parser.resetArgs();
|
parser.resetArgs();
|
||||||
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, nullptr, lastDetectedArg, argDenotation = nullptr, true);
|
reader.reset(nullptr, nullptr).read();
|
||||||
parser.printBashCompletion(0, nullptr, 0, lastDetectedArg);
|
parser.printBashCompletion(0, nullptr, 0, reader.lastArg);
|
||||||
cout.rdbuf(regularCoutBuffer);
|
cout.rdbuf(regularCoutBuffer);
|
||||||
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('display-file-info' 'get' 'set' '--help' )\n"), buffer.str());
|
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('display-file-info' 'get' 'set' '--help' )\n"), buffer.str());
|
||||||
|
|
||||||
// pre-defined values
|
// pre-defined values
|
||||||
const char *const argv3[] = {"get", "--fields"};
|
const char *const argv3[] = {"get", "--fields"};
|
||||||
index = 0, lastDetectedArg = nullptr, buffer.str(string());
|
buffer.str(string());
|
||||||
cout.rdbuf(buffer.rdbuf());
|
cout.rdbuf(buffer.rdbuf());
|
||||||
argv = argv3;
|
|
||||||
parser.resetArgs();
|
parser.resetArgs();
|
||||||
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv3 + 2, lastDetectedArg, argDenotation = nullptr, true);
|
reader.reset(argv3, argv3 + 2).read();
|
||||||
parser.printBashCompletion(2, argv3, 2, lastDetectedArg);
|
parser.printBashCompletion(2, argv3, 2, reader.lastArg);
|
||||||
cout.rdbuf(regularCoutBuffer);
|
cout.rdbuf(regularCoutBuffer);
|
||||||
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('title' 'album' 'artist' 'trackpos' '--files' )\n"), buffer.str());
|
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('title' 'album' 'artist' 'trackpos' '--files' )\n"), buffer.str());
|
||||||
|
|
||||||
// pre-defined values with equation sign, one letter already present
|
// pre-defined values with equation sign, one letter already present
|
||||||
const char *const argv4[] = {"set", "--values", "a"};
|
const char *const argv4[] = {"set", "--values", "a"};
|
||||||
index = 0, lastDetectedArg = nullptr, buffer.str(string());
|
buffer.str(string());
|
||||||
cout.rdbuf(buffer.rdbuf());
|
cout.rdbuf(buffer.rdbuf());
|
||||||
argv = argv4;
|
|
||||||
parser.resetArgs();
|
parser.resetArgs();
|
||||||
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv4 + 3, lastDetectedArg, argDenotation = nullptr, true);
|
reader.reset(argv4, argv4 + 3).read();
|
||||||
parser.printBashCompletion(3, argv4, 2, lastDetectedArg);
|
parser.printBashCompletion(3, argv4, 2, reader.lastArg);
|
||||||
cout.rdbuf(regularCoutBuffer);
|
cout.rdbuf(regularCoutBuffer);
|
||||||
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('album=' 'artist=' ); compopt -o nospace\n"), buffer.str());
|
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('album=' 'artist=' ); compopt -o nospace\n"), buffer.str());
|
||||||
|
|
||||||
|
@ -495,12 +485,11 @@ void ArgumentParserTests::testBashCompletion()
|
||||||
mkvFilePath.resize(mkvFilePath.size() - 17);
|
mkvFilePath.resize(mkvFilePath.size() - 17);
|
||||||
TestUtilities::testFilePath("t.aac");
|
TestUtilities::testFilePath("t.aac");
|
||||||
const char *const argv5[] = {"get", "--files", iniFilePath.c_str()};
|
const char *const argv5[] = {"get", "--files", iniFilePath.c_str()};
|
||||||
index = 0, lastDetectedArg = nullptr, buffer.str(string());
|
buffer.str(string());
|
||||||
cout.rdbuf(buffer.rdbuf());
|
cout.rdbuf(buffer.rdbuf());
|
||||||
argv = argv5;
|
|
||||||
parser.resetArgs();
|
parser.resetArgs();
|
||||||
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv5 + 3, lastDetectedArg, argDenotation = nullptr, true);
|
reader.reset(argv5, argv5 + 3).read();
|
||||||
parser.printBashCompletion(3, argv5, 2, lastDetectedArg);
|
parser.printBashCompletion(3, argv5, 2, reader.lastArg);
|
||||||
cout.rdbuf(regularCoutBuffer);
|
cout.rdbuf(regularCoutBuffer);
|
||||||
// order for file names is not specified
|
// order for file names is not specified
|
||||||
const string res(buffer.str());
|
const string res(buffer.str());
|
||||||
|
@ -512,34 +501,31 @@ void ArgumentParserTests::testBashCompletion()
|
||||||
|
|
||||||
// sub arguments
|
// sub arguments
|
||||||
const char *const argv6[] = {"set", "--"};
|
const char *const argv6[] = {"set", "--"};
|
||||||
index = 0, lastDetectedArg = nullptr, buffer.str(string());
|
buffer.str(string());
|
||||||
cout.rdbuf(buffer.rdbuf());
|
cout.rdbuf(buffer.rdbuf());
|
||||||
argv = argv6;
|
|
||||||
parser.resetArgs();
|
parser.resetArgs();
|
||||||
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv6 + 2, lastDetectedArg, argDenotation = nullptr, true);
|
reader.reset(argv6, argv6 + 2).read();
|
||||||
parser.printBashCompletion(2, argv6, 1, lastDetectedArg);
|
parser.printBashCompletion(2, argv6, 1, reader.lastArg);
|
||||||
cout.rdbuf(regularCoutBuffer);
|
cout.rdbuf(regularCoutBuffer);
|
||||||
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('--files' '--values' )\n"), buffer.str());
|
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('--files' '--values' )\n"), buffer.str());
|
||||||
|
|
||||||
// nested sub arguments
|
// nested sub arguments
|
||||||
const char *const argv7[] = {"-i", "--sub", "--"};
|
const char *const argv7[] = {"-i", "--sub", "--"};
|
||||||
index = 0, lastDetectedArg = nullptr, buffer.str(string());
|
buffer.str(string());
|
||||||
cout.rdbuf(buffer.rdbuf());
|
cout.rdbuf(buffer.rdbuf());
|
||||||
argv = argv7;
|
|
||||||
parser.resetArgs();
|
parser.resetArgs();
|
||||||
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv7 + 3, lastDetectedArg, argDenotation = nullptr, true);
|
reader.reset(argv7, argv7 + 3).read();
|
||||||
parser.printBashCompletion(3, argv7, 2, lastDetectedArg);
|
parser.printBashCompletion(3, argv7, 2, reader.lastArg);
|
||||||
cout.rdbuf(regularCoutBuffer);
|
cout.rdbuf(regularCoutBuffer);
|
||||||
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('--files' '--nested-sub' '--verbose' )\n"), buffer.str());
|
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('--files' '--nested-sub' '--verbose' )\n"), buffer.str());
|
||||||
|
|
||||||
// started pre-defined values with equation sign, one letter already present, last value matches
|
// started pre-defined values with equation sign, one letter already present, last value matches
|
||||||
const char *const argv8[] = {"set", "--values", "t"};
|
const char *const argv8[] = {"set", "--values", "t"};
|
||||||
index = 0, lastDetectedArg = nullptr, buffer.str(string());
|
buffer.str(string());
|
||||||
cout.rdbuf(buffer.rdbuf());
|
cout.rdbuf(buffer.rdbuf());
|
||||||
argv = argv8;
|
|
||||||
parser.resetArgs();
|
parser.resetArgs();
|
||||||
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv8 + 3, lastDetectedArg, argDenotation = nullptr, true);
|
reader.reset(argv8, argv8 + 3).read();
|
||||||
parser.printBashCompletion(3, argv8, 2, lastDetectedArg);
|
parser.printBashCompletion(3, argv8, 2, reader.lastArg);
|
||||||
cout.rdbuf(regularCoutBuffer);
|
cout.rdbuf(regularCoutBuffer);
|
||||||
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('title=' 'trackpos=' ); compopt -o nospace\n"), buffer.str());
|
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('title=' 'trackpos=' ); compopt -o nospace\n"), buffer.str());
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue