Fix Bash completion for abbreviations

This commit is contained in:
Martchus 2016-12-23 22:41:06 +01:00
parent cab332bcad
commit 28d2063d33
4 changed files with 57 additions and 24 deletions

View File

@ -51,6 +51,9 @@ ArgumentReader::ArgumentReader(ArgumentParser &parser, const char * const *argv,
completionMode(completionMode) completionMode(completionMode)
{} {}
/*!
* \brief Resets the ArgumentReader to continue reading new \a argv.
*/
ArgumentReader &ArgumentReader::reset(const char *const *argv, const char *const *end) ArgumentReader &ArgumentReader::reset(const char *const *argv, const char *const *end)
{ {
this->argv = argv; 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. * \brief Reads the commands line arguments specified when constructing the object.
* \remarks Reads on main-argument-level.
*/ */
void ArgumentReader::read() void ArgumentReader::read()
{ {
@ -71,6 +75,7 @@ void ArgumentReader::read()
/*! /*!
* \brief Reads the commands line arguments specified when constructing the object. * \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) void ArgumentReader::read(ArgumentVector &args)
{ {
@ -91,7 +96,6 @@ void ArgumentReader::read(ArgumentVector &args)
} else { } else {
// determine how denotation must be processed // determine how denotation must be processed
bool abbreviationFound = false; bool abbreviationFound = false;
unsigned char argDenotationType;
if(argDenotation) { if(argDenotation) {
// continue reading childs for abbreviation denotation already detected // continue reading childs for abbreviation denotation already detected
abbreviationFound = false; abbreviationFound = false;
@ -145,7 +149,7 @@ void ArgumentReader::read(ArgumentVector &args)
} }
// read sub arguments // 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 != equationPos)) {
if(argDenotationType != Abbreviation || !*argDenotation) { if(argDenotationType != Abbreviation || !*argDenotation) {
// no further abbreviations follow -> read sub args for next argv // no further abbreviations follow -> read sub args for next argv
@ -194,6 +198,7 @@ void ArgumentReader::read(ArgumentVector &args)
for(Argument *arg : args) { for(Argument *arg : args) {
if(arg->denotesOperation() && arg->name() && !strcmp(arg->name(), *argv)) { if(arg->denotesOperation() && arg->name() && !strcmp(arg->name(), *argv)) {
(matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg); (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
lastArgDenotation = argv;
++index, ++argv; ++index, ++argv;
break; break;
} }
@ -676,7 +681,7 @@ void ArgumentParser::readArgs(int argc, const char * const *argv)
} }
if(completionMode) { if(completionMode) {
printBashCompletion(argc, argv, currentWordIndex, reader.lastArg); printBashCompletion(argc, argv, currentWordIndex, reader);
exitFunction(0); // prevent the applicaton to continue with the regular execution exitFunction(0); // prevent the applicaton to continue with the regular execution
} }
} else { } else {
@ -699,6 +704,7 @@ void ArgumentParser::resetArgs()
for(Argument *arg : m_mainArgs) { for(Argument *arg : m_mainArgs) {
arg->resetRecursively(); arg->resetRecursively();
} }
m_actualArgc = 0;
} }
/*! /*!
@ -797,17 +803,18 @@ void insertSiblings(const ArgumentVector &siblings, list<const Argument *> &targ
* \remarks Arguments must have been parsed before with readSpecifiedArgs(). When calling this method, completionMode must * \remarks Arguments must have been parsed before with readSpecifiedArgs(). When calling this method, completionMode must
* be set to true. * 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) // variables to store relevant completions (arguments, pre-defined values, files/dirs)
list<const Argument *> relevantArgs, relevantPreDefinedValues; list<const Argument *> relevantArgs, relevantPreDefinedValues;
bool completeFiles = false, completeDirs = false, noWhitespace = false; bool completeFiles = false, completeDirs = false, noWhitespace = false;
// get the last argument the argument parser was able to detect successfully // get the last argument the argument parser was able to detect successfully
const Argument *const lastDetectedArg = reader.lastArg;
size_t lastDetectedArgIndex; size_t lastDetectedArgIndex;
vector<Argument *> lastDetectedArgPath; vector<Argument *> lastDetectedArgPath;
if(lastDetectedArg) { if(lastDetectedArg) {
lastDetectedArgIndex = lastDetectedArg->index(lastDetectedArg->occurrences() - 1); lastDetectedArgIndex = reader.lastArgDenotation - argv;
lastDetectedArgPath = lastDetectedArg->path(lastDetectedArg->occurrences() - 1); 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() << '\'' << ' '; 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))))) { } else if(arg->denotesOperation() && (!actualArgumentCount() || (currentWordIndex == 0 && (!lastDetectedArg || (lastDetectedArg->isPresent() && lastDetectedArgIndex == 0))))) {
cout << '\'' << arg->name() << '\'' << ' '; cout << '\'' << arg->name() << '\'' << ' ';
} else { } else {

View File

@ -238,7 +238,7 @@ 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 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 checkConstraints(const ArgumentVector &args);
void invokeCallbacks(const ArgumentVector &args); void invokeCallbacks(const ArgumentVector &args);

View File

@ -10,21 +10,25 @@ struct CPP_UTILITIES_EXPORT ArgumentReader
void read(); void read();
void read(ArgumentVector &args); void read(ArgumentVector &args);
/// \brief Specifies the associated ArgumentParser instance. /// \brief The associated ArgumentParser instance.
ArgumentParser &parser; 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; 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; size_t index;
/// \brief Points to the first argument denotation and will be incremented when a denotation has been processed. /// \brief Points to the first argument denotation and will be incremented when a denotation has been processed.
const char *const *argv; const char *const *argv;
/// \brief Points to the end of the \a argv array. /// \brief Points to the end of the \a argv array.
const char *const *end; 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; 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; 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; bool completionMode;
}; };

View File

@ -417,7 +417,7 @@ void ArgumentParserTests::testBashCompletion()
const char *const argv1[] = {"se"}; const char *const argv1[] = {"se"};
ArgumentReader reader(parser, argv1, argv1 + 1, true); ArgumentReader reader(parser, argv1, argv1 + 1, true);
reader.read(); reader.read();
parser.printBashCompletion(1, argv1, 0, reader.lastArg); parser.printBashCompletion(1, argv1, 0, reader);
cout.rdbuf(regularCoutBuffer); cout.rdbuf(regularCoutBuffer);
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=()\n"), buffer.str()); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=()\n"), buffer.str());
@ -426,7 +426,7 @@ void ArgumentParserTests::testBashCompletion()
cout.rdbuf(buffer.rdbuf()); cout.rdbuf(buffer.rdbuf());
getArg.setDenotesOperation(true), setArg.setDenotesOperation(true); getArg.setDenotesOperation(true), setArg.setDenotesOperation(true);
reader.reset(argv1, argv1 + 1).read(); reader.reset(argv1, argv1 + 1).read();
parser.printBashCompletion(1, argv1, 0, reader.lastArg); parser.printBashCompletion(1, argv1, 0, reader);
cout.rdbuf(regularCoutBuffer); cout.rdbuf(regularCoutBuffer);
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('set' )\n"), buffer.str()); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('set' )\n"), buffer.str());
@ -436,7 +436,7 @@ void ArgumentParserTests::testBashCompletion()
cout.rdbuf(buffer.rdbuf()); cout.rdbuf(buffer.rdbuf());
parser.resetArgs(); parser.resetArgs();
reader.reset(argv2, argv2 + 1).read(); reader.reset(argv2, argv2 + 1).read();
parser.printBashCompletion(1, argv2, 0, reader.lastArg); parser.printBashCompletion(1, argv2, 0, reader);
cout.rdbuf(regularCoutBuffer); cout.rdbuf(regularCoutBuffer);
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('set' )\n"), buffer.str()); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('set' )\n"), buffer.str());
@ -445,7 +445,7 @@ void ArgumentParserTests::testBashCompletion()
cout.rdbuf(buffer.rdbuf()); cout.rdbuf(buffer.rdbuf());
parser.resetArgs(); parser.resetArgs();
reader.reset(argv2, argv2 + 1).read(); reader.reset(argv2, argv2 + 1).read();
parser.printBashCompletion(1, argv2, 1, reader.lastArg); parser.printBashCompletion(1, argv2, 1, reader);
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());
@ -454,7 +454,7 @@ void ArgumentParserTests::testBashCompletion()
cout.rdbuf(buffer.rdbuf()); cout.rdbuf(buffer.rdbuf());
parser.resetArgs(); parser.resetArgs();
reader.reset(nullptr, nullptr).read(); reader.reset(nullptr, nullptr).read();
parser.printBashCompletion(0, nullptr, 0, reader.lastArg); parser.printBashCompletion(0, nullptr, 0, reader);
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());
@ -464,7 +464,7 @@ void ArgumentParserTests::testBashCompletion()
cout.rdbuf(buffer.rdbuf()); cout.rdbuf(buffer.rdbuf());
parser.resetArgs(); parser.resetArgs();
reader.reset(argv3, argv3 + 2).read(); reader.reset(argv3, argv3 + 2).read();
parser.printBashCompletion(2, argv3, 2, reader.lastArg); parser.printBashCompletion(2, argv3, 2, reader);
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());
@ -474,7 +474,7 @@ void ArgumentParserTests::testBashCompletion()
cout.rdbuf(buffer.rdbuf()); cout.rdbuf(buffer.rdbuf());
parser.resetArgs(); parser.resetArgs();
reader.reset(argv4, argv4 + 3).read(); reader.reset(argv4, argv4 + 3).read();
parser.printBashCompletion(3, argv4, 2, reader.lastArg); parser.printBashCompletion(3, argv4, 2, reader);
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());
@ -489,7 +489,7 @@ void ArgumentParserTests::testBashCompletion()
cout.rdbuf(buffer.rdbuf()); cout.rdbuf(buffer.rdbuf());
parser.resetArgs(); parser.resetArgs();
reader.reset(argv5, argv5 + 3).read(); reader.reset(argv5, argv5 + 3).read();
parser.printBashCompletion(3, argv5, 2, reader.lastArg); parser.printBashCompletion(3, argv5, 2, reader);
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());
@ -505,7 +505,7 @@ void ArgumentParserTests::testBashCompletion()
cout.rdbuf(buffer.rdbuf()); cout.rdbuf(buffer.rdbuf());
parser.resetArgs(); parser.resetArgs();
reader.reset(argv6, argv6 + 2).read(); reader.reset(argv6, argv6 + 2).read();
parser.printBashCompletion(2, argv6, 1, reader.lastArg); parser.printBashCompletion(2, argv6, 1, reader);
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());
@ -515,7 +515,7 @@ void ArgumentParserTests::testBashCompletion()
cout.rdbuf(buffer.rdbuf()); cout.rdbuf(buffer.rdbuf());
parser.resetArgs(); parser.resetArgs();
reader.reset(argv7, argv7 + 3).read(); reader.reset(argv7, argv7 + 3).read();
parser.printBashCompletion(3, argv7, 2, reader.lastArg); parser.printBashCompletion(3, argv7, 2, reader);
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());
@ -525,10 +525,28 @@ void ArgumentParserTests::testBashCompletion()
cout.rdbuf(buffer.rdbuf()); cout.rdbuf(buffer.rdbuf());
parser.resetArgs(); parser.resetArgs();
reader.reset(argv8, argv8 + 3).read(); reader.reset(argv8, argv8 + 3).read();
parser.printBashCompletion(3, argv8, 2, reader.lastArg); parser.printBashCompletion(3, argv8, 2, reader);
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());
// 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<string::size_type>(0), buffer.str().find("COMPREPLY=('--fields' "));
} catch(...) { } catch(...) {
cout.rdbuf(regularCoutBuffer); cout.rdbuf(regularCoutBuffer);
throw; throw;