Fix Bash completion for abbreviations
This commit is contained in:
parent
cab332bcad
commit
28d2063d33
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue