From cab332bcad5c9c0eb64a5fad57fc97f16eca1896 Mon Sep 17 00:00:00 2001 From: Martchus Date: Fri, 23 Dec 2016 09:55:12 +0100 Subject: [PATCH] Refactor reading arguments Replace ArgumentParser::readSpecifiedArgs() with ArgumentReader class to simplify argument list --- CMakeLists.txt | 1 + application/argumentparser.cpp | 439 ++++++++++++++------------- application/argumentparser.h | 7 +- application/argumentparserprivate.h | 33 ++ conversion/binaryconversionprivate.h | 4 +- tests/argumentparsertests.cpp | 82 +++-- 6 files changed, 306 insertions(+), 260 deletions(-) create mode 100644 application/argumentparserprivate.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0365013..42ffd68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) # add project files set(HEADER_FILES application/argumentparser.h + application/argumentparserprivate.h application/commandlineutils.h application/failure.h application/fakeqtconfigarguments.h diff --git a/application/argumentparser.cpp b/application/argumentparser.cpp index e851d83..dfbb448 100644 --- a/application/argumentparser.cpp +++ b/application/argumentparser.cpp @@ -1,4 +1,5 @@ #include "./argumentparser.h" +#include "./argumentparserprivate.h" #include "./commandlineutils.h" #include "./failure.h" @@ -24,6 +25,234 @@ using namespace IoUtilities; */ 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 &parentPath = parentArg ? parentArg->path(parentArg->occurrences() - 1) : vector(); + + Argument *lastArgInLevel = nullptr; + vector *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(-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(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()). const char *applicationName = nullptr; /// \brief Specifies the author of the application (used by ArgumentParser::printHelp()). @@ -48,15 +277,6 @@ inline bool notEmpty(const char *str) /// \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 * \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 + ArgumentReader reader(*this, argv, argv + (completionMode ? min(static_cast(argc), currentWordIndex + 1) : static_cast(argc)), completionMode); try { - const char *const *argv2 = argv; - const char *argDenotation = nullptr; - readSpecifiedArgs(m_mainArgs, index, argv2, argv + (completionMode ? min(static_cast(argc), currentWordIndex + 1) : static_cast(argc)), lastDetectedArgument, argDenotation, completionMode); + reader.read(); } catch(const Failure &) { if(!completionMode) { throw; @@ -461,7 +676,7 @@ void ArgumentParser::readArgs(int argc, const char * const *argv) } if(completionMode) { - printBashCompletion(argc, argv, currentWordIndex, lastDetectedArgument); + printBashCompletion(argc, argv, currentWordIndex, reader.lastArg); exitFunction(0); // prevent the applicaton to continue with the regular execution } } else { @@ -539,196 +754,6 @@ void ApplicationUtilities::ArgumentParser::verifyArgs(const ArgumentVector &args } #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 &parentPath = parentArg ? parentArg->path(parentArg->occurrences() - 1) : vector(); - - Argument *lastArgInLevel = nullptr; - vector *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(-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(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 * printing completion. diff --git a/application/argumentparser.h b/application/argumentparser.h index 62a166e..5495d44 100644 --- a/application/argumentparser.h +++ b/application/argumentparser.h @@ -34,6 +34,7 @@ CPP_UTILITIES_EXPORT extern void(*exitFunction)(int); class Argument; class ArgumentParser; +class ArgumentReader; typedef std::initializer_list ArgumentInitializerList; typedef std::vector ArgumentVector; @@ -128,7 +129,8 @@ inline ArgumentOccurrence::ArgumentOccurrence(std::size_t index, const std::vect class CPP_UTILITIES_EXPORT Argument { - friend class ArgumentParser; + friend ArgumentParser; + friend ArgumentReader; public: typedef std::function CallbackFunction; @@ -212,6 +214,8 @@ private: class CPP_UTILITIES_EXPORT ArgumentParser { friend ArgumentParserTests; + friend ArgumentReader; + public: ArgumentParser(); @@ -234,7 +238,6 @@ public: private: IF_DEBUG_BUILD(void verifyArgs(const ArgumentVector &args, std::vector abbreviations, std::vector 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 checkConstraints(const ArgumentVector &args); void invokeCallbacks(const ArgumentVector &args); diff --git a/application/argumentparserprivate.h b/application/argumentparserprivate.h new file mode 100644 index 0000000..e572de2 --- /dev/null +++ b/application/argumentparserprivate.h @@ -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 diff --git a/conversion/binaryconversionprivate.h b/conversion/binaryconversionprivate.h index be7b03f..f66567e 100644 --- a/conversion/binaryconversionprivate.h +++ b/conversion/binaryconversionprivate.h @@ -1,8 +1,6 @@ -#ifdef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL - #ifndef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL # error "Do not include binaryconversionprivate.h directly." -#endif +#else #include "./types.h" diff --git a/tests/argumentparsertests.cpp b/tests/argumentparsertests.cpp index 9930ba5..8dcd940 100644 --- a/tests/argumentparsertests.cpp +++ b/tests/argumentparsertests.cpp @@ -1,6 +1,7 @@ #include "./testutils.h" #include "../application/argumentparser.h" +#include "../application/argumentparserprivate.h" #include "../application/failure.h" #include "../application/fakeqtconfigarguments.h" @@ -407,10 +408,6 @@ void ArgumentParserTests::testBashCompletion() parser.setMainArguments({&helpArg, &displayFileInfoArg, &getArg, &setArg}); - size_t index = 0; - Argument *lastDetectedArg = nullptr; - const char *argDenotation = nullptr; - // redirect cout to custom buffer stringstream buffer; streambuf *regularCoutBuffer = cout.rdbuf(buffer.rdbuf()); @@ -418,73 +415,66 @@ void ArgumentParserTests::testBashCompletion() try { // fail due to operation flags not set const char *const argv1[] = {"se"}; - const char *const *argv = argv1; - parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv1 + 1, lastDetectedArg, argDenotation = nullptr, true); - parser.printBashCompletion(1, argv1, 0, lastDetectedArg); + ArgumentReader reader(parser, argv1, argv1 + 1, true); + reader.read(); + parser.printBashCompletion(1, argv1, 0, reader.lastArg); cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=()\n"), buffer.str()); // correct operation arg flags - index = 0, lastDetectedArg = nullptr, buffer.str(string()); + buffer.str(string()); cout.rdbuf(buffer.rdbuf()); getArg.setDenotesOperation(true), setArg.setDenotesOperation(true); - argv = argv1; - parser.resetArgs(); - parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv1 + 1, lastDetectedArg, argDenotation = nullptr, true); - parser.printBashCompletion(1, argv1, 0, lastDetectedArg); + reader.reset(argv1, argv1 + 1).read(); + parser.printBashCompletion(1, argv1, 0, reader.lastArg); cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('set' )\n"), buffer.str()); // argument at current cursor position already specified -> the completion should just return the argument const char *const argv2[] = {"set"}; - index = 0, lastDetectedArg = nullptr, buffer.str(string()); + buffer.str(string()); cout.rdbuf(buffer.rdbuf()); - argv = argv2; parser.resetArgs(); - parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv2 + 1, lastDetectedArg, argDenotation = nullptr, true); - parser.printBashCompletion(1, argv2, 0, lastDetectedArg); + reader.reset(argv2, argv2 + 1).read(); + parser.printBashCompletion(1, argv2, 0, reader.lastArg); cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('set' )\n"), buffer.str()); // 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()); - argv = argv2; parser.resetArgs(); - parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv2 + 1, lastDetectedArg, argDenotation = nullptr, true); - parser.printBashCompletion(1, argv2, 1, lastDetectedArg); + reader.reset(argv2, argv2 + 1).read(); + parser.printBashCompletion(1, argv2, 1, reader.lastArg); cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('--files' '--values' )\n"), buffer.str()); // specifying no args should propose all main arguments - index = 0, lastDetectedArg = nullptr, buffer.str(string()); + buffer.str(string()); cout.rdbuf(buffer.rdbuf()); - argv = nullptr; parser.resetArgs(); - parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, nullptr, lastDetectedArg, argDenotation = nullptr, true); - parser.printBashCompletion(0, nullptr, 0, lastDetectedArg); + reader.reset(nullptr, nullptr).read(); + parser.printBashCompletion(0, nullptr, 0, reader.lastArg); cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('display-file-info' 'get' 'set' '--help' )\n"), buffer.str()); // pre-defined values const char *const argv3[] = {"get", "--fields"}; - index = 0, lastDetectedArg = nullptr, buffer.str(string()); + buffer.str(string()); cout.rdbuf(buffer.rdbuf()); - argv = argv3; parser.resetArgs(); - parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv3 + 2, lastDetectedArg, argDenotation = nullptr, true); - parser.printBashCompletion(2, argv3, 2, lastDetectedArg); + reader.reset(argv3, argv3 + 2).read(); + parser.printBashCompletion(2, argv3, 2, reader.lastArg); cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('title' 'album' 'artist' 'trackpos' '--files' )\n"), buffer.str()); // pre-defined values with equation sign, one letter already present const char *const argv4[] = {"set", "--values", "a"}; - index = 0, lastDetectedArg = nullptr, buffer.str(string()); + buffer.str(string()); cout.rdbuf(buffer.rdbuf()); - argv = argv4; parser.resetArgs(); - parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv4 + 3, lastDetectedArg, argDenotation = nullptr, true); - parser.printBashCompletion(3, argv4, 2, lastDetectedArg); + reader.reset(argv4, argv4 + 3).read(); + parser.printBashCompletion(3, argv4, 2, reader.lastArg); cout.rdbuf(regularCoutBuffer); 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); TestUtilities::testFilePath("t.aac"); const char *const argv5[] = {"get", "--files", iniFilePath.c_str()}; - index = 0, lastDetectedArg = nullptr, buffer.str(string()); + buffer.str(string()); cout.rdbuf(buffer.rdbuf()); - argv = argv5; parser.resetArgs(); - parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv5 + 3, lastDetectedArg, argDenotation = nullptr, true); - parser.printBashCompletion(3, argv5, 2, lastDetectedArg); + reader.reset(argv5, argv5 + 3).read(); + parser.printBashCompletion(3, argv5, 2, reader.lastArg); cout.rdbuf(regularCoutBuffer); // order for file names is not specified const string res(buffer.str()); @@ -512,34 +501,31 @@ void ArgumentParserTests::testBashCompletion() // sub arguments const char *const argv6[] = {"set", "--"}; - index = 0, lastDetectedArg = nullptr, buffer.str(string()); + buffer.str(string()); cout.rdbuf(buffer.rdbuf()); - argv = argv6; parser.resetArgs(); - parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv6 + 2, lastDetectedArg, argDenotation = nullptr, true); - parser.printBashCompletion(2, argv6, 1, lastDetectedArg); + reader.reset(argv6, argv6 + 2).read(); + parser.printBashCompletion(2, argv6, 1, reader.lastArg); cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('--files' '--values' )\n"), buffer.str()); // nested sub arguments const char *const argv7[] = {"-i", "--sub", "--"}; - index = 0, lastDetectedArg = nullptr, buffer.str(string()); + buffer.str(string()); cout.rdbuf(buffer.rdbuf()); - argv = argv7; parser.resetArgs(); - parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv7 + 3, lastDetectedArg, argDenotation = nullptr, true); - parser.printBashCompletion(3, argv7, 2, lastDetectedArg); + reader.reset(argv7, argv7 + 3).read(); + parser.printBashCompletion(3, argv7, 2, reader.lastArg); cout.rdbuf(regularCoutBuffer); 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 const char *const argv8[] = {"set", "--values", "t"}; - index = 0, lastDetectedArg = nullptr, buffer.str(string()); + buffer.str(string()); cout.rdbuf(buffer.rdbuf()); - argv = argv8; parser.resetArgs(); - parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv8 + 3, lastDetectedArg, argDenotation = nullptr, true); - parser.printBashCompletion(3, argv8, 2, lastDetectedArg); + reader.reset(argv8, argv8 + 3).read(); + parser.printBashCompletion(3, argv8, 2, reader.lastArg); cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('title=' 'trackpos=' ); compopt -o nospace\n"), buffer.str());