From 28d2063d331b721e7f42aee600b98599fa3030cf Mon Sep 17 00:00:00 2001 From: Martchus Date: Fri, 23 Dec 2016 22:41:06 +0100 Subject: [PATCH] Fix Bash completion for abbreviations --- application/argumentparser.cpp | 23 ++++++++++++----- application/argumentparser.h | 2 +- application/argumentparserprivate.h | 16 +++++++----- tests/argumentparsertests.cpp | 40 +++++++++++++++++++++-------- 4 files changed, 57 insertions(+), 24 deletions(-) diff --git a/application/argumentparser.cpp b/application/argumentparser.cpp index dfbb448..e5ebe9a 100644 --- a/application/argumentparser.cpp +++ b/application/argumentparser.cpp @@ -51,6 +51,9 @@ ArgumentReader::ArgumentReader(ArgumentParser &parser, const char * const *argv, 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; @@ -63,6 +66,7 @@ ArgumentReader &ArgumentReader::reset(const char *const *argv, const char *const /*! * \brief Reads the commands line arguments specified when constructing the object. + * \remarks Reads on main-argument-level. */ void ArgumentReader::read() { @@ -71,6 +75,7 @@ void ArgumentReader::read() /*! * \brief Reads the commands line arguments specified when constructing the object. + * \remarks Reads on custom argument-level specified via \a args. */ void ArgumentReader::read(ArgumentVector &args) { @@ -91,7 +96,6 @@ void ArgumentReader::read(ArgumentVector &args) } 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; @@ -145,7 +149,7 @@ void ArgumentReader::read(ArgumentVector &args) } // read sub arguments - ++index, ++parser.m_actualArgc, lastArg = lastArgInLevel = matchingArg; + ++index, ++parser.m_actualArgc, lastArg = lastArgInLevel = matchingArg, lastArgDenotation = argv; if(argDenotationType != Abbreviation || (++argDenotation != equationPos)) { if(argDenotationType != Abbreviation || !*argDenotation) { // no further abbreviations follow -> read sub args for next argv @@ -194,6 +198,7 @@ void ArgumentReader::read(ArgumentVector &args) for(Argument *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; } @@ -676,7 +681,7 @@ void ArgumentParser::readArgs(int argc, const char * const *argv) } if(completionMode) { - printBashCompletion(argc, argv, currentWordIndex, reader.lastArg); + printBashCompletion(argc, argv, currentWordIndex, reader); exitFunction(0); // prevent the applicaton to continue with the regular execution } } else { @@ -699,6 +704,7 @@ void ArgumentParser::resetArgs() for(Argument *arg : m_mainArgs) { arg->resetRecursively(); } + m_actualArgc = 0; } /*! @@ -797,17 +803,18 @@ void insertSiblings(const ArgumentVector &siblings, list &targ * \remarks Arguments must have been parsed before with readSpecifiedArgs(). When calling this method, completionMode must * be set to true. */ -void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsigned int currentWordIndex, const Argument *lastDetectedArg) +void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader) { // variables to store relevant completions (arguments, pre-defined values, files/dirs) list relevantArgs, relevantPreDefinedValues; bool completeFiles = false, completeDirs = false, noWhitespace = false; // get the last argument the argument parser was able to detect successfully + const Argument *const lastDetectedArg = reader.lastArg; size_t lastDetectedArgIndex; vector lastDetectedArgPath; if(lastDetectedArg) { - lastDetectedArgIndex = lastDetectedArg->index(lastDetectedArg->occurrences() - 1); + lastDetectedArgIndex = reader.lastArgDenotation - argv; lastDetectedArgPath = lastDetectedArg->path(lastDetectedArg->occurrences() - 1); } @@ -1006,8 +1013,12 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi } } - if(openingDenotationType == Abbreviation && opening) { + if(opening && openingDenotationType == Abbreviation && !nextArgumentOrValue) { cout << '\'' << '-' << opening << arg->abbreviation() << '\'' << ' '; + } else if(lastDetectedArg && reader.argDenotationType == Abbreviation && !nextArgumentOrValue) { + if(reader.argv == reader.end) { + cout << '\'' << *(reader.argv - 1) << '\'' << ' '; + } } else if(arg->denotesOperation() && (!actualArgumentCount() || (currentWordIndex == 0 && (!lastDetectedArg || (lastDetectedArg->isPresent() && lastDetectedArgIndex == 0))))) { cout << '\'' << arg->name() << '\'' << ' '; } else { diff --git a/application/argumentparser.h b/application/argumentparser.h index 5495d44..17cdc13 100644 --- a/application/argumentparser.h +++ b/application/argumentparser.h @@ -238,7 +238,7 @@ public: private: IF_DEBUG_BUILD(void verifyArgs(const ArgumentVector &args, std::vector abbreviations, std::vector names);) - 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 ArgumentReader &reader); void checkConstraints(const ArgumentVector &args); void invokeCallbacks(const ArgumentVector &args); diff --git a/application/argumentparserprivate.h b/application/argumentparserprivate.h index e572de2..bfe92c2 100644 --- a/application/argumentparserprivate.h +++ b/application/argumentparserprivate.h @@ -10,21 +10,25 @@ struct CPP_UTILITIES_EXPORT ArgumentReader void read(); void read(ArgumentVector &args); - /// \brief Specifies the associated ArgumentParser instance. + /// \brief The associated ArgumentParser instance. ArgumentParser &parser; - /// \brief Specifies the Argument instances to store the results. Sub arguments of args are considered as well. + /// \brief 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. + /// \brief An 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. + /// \brief 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). + /// \brief Points to the element in argv where lastArg was encountered. Unspecified if lastArg is not set. + const char *const *lastArgDenotation; + /// \brief The currently processed abbreviation denotation (should be substring of one of the args in argv). Set to nullptr for processing 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()). + /// \brief The type of the currently processed abbreviation denotation. Unspecified if argDenotation is not set. + unsigned char argDenotationType; + /// \brief Whether completion mode is enabled. In this case reading args will be continued even if an denotation is unknown (regardless of unknownArgumentBehavior()). bool completionMode; }; diff --git a/tests/argumentparsertests.cpp b/tests/argumentparsertests.cpp index 8dcd940..793b675 100644 --- a/tests/argumentparsertests.cpp +++ b/tests/argumentparsertests.cpp @@ -417,7 +417,7 @@ void ArgumentParserTests::testBashCompletion() const char *const argv1[] = {"se"}; ArgumentReader reader(parser, argv1, argv1 + 1, true); reader.read(); - parser.printBashCompletion(1, argv1, 0, reader.lastArg); + parser.printBashCompletion(1, argv1, 0, reader); cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=()\n"), buffer.str()); @@ -426,7 +426,7 @@ void ArgumentParserTests::testBashCompletion() cout.rdbuf(buffer.rdbuf()); getArg.setDenotesOperation(true), setArg.setDenotesOperation(true); reader.reset(argv1, argv1 + 1).read(); - parser.printBashCompletion(1, argv1, 0, reader.lastArg); + parser.printBashCompletion(1, argv1, 0, reader); cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('set' )\n"), buffer.str()); @@ -436,7 +436,7 @@ void ArgumentParserTests::testBashCompletion() cout.rdbuf(buffer.rdbuf()); parser.resetArgs(); reader.reset(argv2, argv2 + 1).read(); - parser.printBashCompletion(1, argv2, 0, reader.lastArg); + parser.printBashCompletion(1, argv2, 0, reader); cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('set' )\n"), buffer.str()); @@ -445,7 +445,7 @@ void ArgumentParserTests::testBashCompletion() cout.rdbuf(buffer.rdbuf()); parser.resetArgs(); reader.reset(argv2, argv2 + 1).read(); - parser.printBashCompletion(1, argv2, 1, reader.lastArg); + parser.printBashCompletion(1, argv2, 1, reader); cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('--files' '--values' )\n"), buffer.str()); @@ -454,7 +454,7 @@ void ArgumentParserTests::testBashCompletion() cout.rdbuf(buffer.rdbuf()); parser.resetArgs(); reader.reset(nullptr, nullptr).read(); - parser.printBashCompletion(0, nullptr, 0, reader.lastArg); + parser.printBashCompletion(0, nullptr, 0, reader); cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('display-file-info' 'get' 'set' '--help' )\n"), buffer.str()); @@ -464,7 +464,7 @@ void ArgumentParserTests::testBashCompletion() cout.rdbuf(buffer.rdbuf()); parser.resetArgs(); reader.reset(argv3, argv3 + 2).read(); - parser.printBashCompletion(2, argv3, 2, reader.lastArg); + parser.printBashCompletion(2, argv3, 2, reader); cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('title' 'album' 'artist' 'trackpos' '--files' )\n"), buffer.str()); @@ -474,7 +474,7 @@ void ArgumentParserTests::testBashCompletion() cout.rdbuf(buffer.rdbuf()); parser.resetArgs(); reader.reset(argv4, argv4 + 3).read(); - parser.printBashCompletion(3, argv4, 2, reader.lastArg); + parser.printBashCompletion(3, argv4, 2, reader); cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('album=' 'artist=' ); compopt -o nospace\n"), buffer.str()); @@ -489,7 +489,7 @@ void ArgumentParserTests::testBashCompletion() cout.rdbuf(buffer.rdbuf()); parser.resetArgs(); reader.reset(argv5, argv5 + 3).read(); - parser.printBashCompletion(3, argv5, 2, reader.lastArg); + parser.printBashCompletion(3, argv5, 2, reader); cout.rdbuf(regularCoutBuffer); // order for file names is not specified const string res(buffer.str()); @@ -505,7 +505,7 @@ void ArgumentParserTests::testBashCompletion() cout.rdbuf(buffer.rdbuf()); parser.resetArgs(); reader.reset(argv6, argv6 + 2).read(); - parser.printBashCompletion(2, argv6, 1, reader.lastArg); + parser.printBashCompletion(2, argv6, 1, reader); cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('--files' '--values' )\n"), buffer.str()); @@ -515,7 +515,7 @@ void ArgumentParserTests::testBashCompletion() cout.rdbuf(buffer.rdbuf()); parser.resetArgs(); reader.reset(argv7, argv7 + 3).read(); - parser.printBashCompletion(3, argv7, 2, reader.lastArg); + parser.printBashCompletion(3, argv7, 2, reader); cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('--files' '--nested-sub' '--verbose' )\n"), buffer.str()); @@ -525,10 +525,28 @@ void ArgumentParserTests::testBashCompletion() cout.rdbuf(buffer.rdbuf()); parser.resetArgs(); reader.reset(argv8, argv8 + 3).read(); - parser.printBashCompletion(3, argv8, 2, reader.lastArg); + parser.printBashCompletion(3, argv8, 2, reader); cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('title=' 'trackpos=' ); compopt -o nospace\n"), buffer.str()); + // combined abbreviations + const char *const argv9[] = {"-gf"}; + buffer.str(string()); + cout.rdbuf(buffer.rdbuf()); + parser.resetArgs(); + reader.reset(argv9, argv9 + 1).read(); + parser.printBashCompletion(1, argv9, 0, reader); + cout.rdbuf(regularCoutBuffer); + CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('-gf' )\n"), buffer.str()); + + buffer.str(string()); + cout.rdbuf(buffer.rdbuf()); + parser.resetArgs(); + reader.reset(argv9, argv9 + 1).read(); + parser.printBashCompletion(1, argv9, 1, reader); + cout.rdbuf(regularCoutBuffer); + CPPUNIT_ASSERT_EQUAL(static_cast(0), buffer.str().find("COMPREPLY=('--fields' ")); + } catch(...) { cout.rdbuf(regularCoutBuffer); throw;