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)
{}
/*!
* \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<const Argument *> &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<const Argument *> 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<Argument *> 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 {

View File

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

View File

@ -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;
};

View File

@ -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<string::size_type>(0), buffer.str().find("COMPREPLY=('--fields' "));
} catch(...) {
cout.rdbuf(regularCoutBuffer);
throw;