Fix bash completion when dir/file contains single quote

Also a few other improvements in bash completion code
This commit is contained in:
Martchus 2016-10-22 19:32:16 +02:00
parent 73d42c287c
commit 72426e2d4c
6 changed files with 88 additions and 63 deletions

View File

@ -691,18 +691,19 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
lastDetectedArgPath = lastDetectedArg->path(lastDetectedArg->occurrences() - 1); lastDetectedArgPath = lastDetectedArg->path(lastDetectedArg->occurrences() - 1);
} }
bool nextArgumentOrValue; // determine last arg, omitting trailing empty args
const char *const *lastSpecifiedArg; const char *const *lastSpecifiedArg;
unsigned int lastSpecifiedArgIndex; unsigned int lastSpecifiedArgIndex;
if(argc) { if(argc) {
// determine last arg omitting trailing empty args
lastSpecifiedArgIndex = static_cast<unsigned int>(argc) - 1; lastSpecifiedArgIndex = static_cast<unsigned int>(argc) - 1;
lastSpecifiedArg = argv + lastSpecifiedArgIndex; lastSpecifiedArg = argv + lastSpecifiedArgIndex;
for(; lastSpecifiedArg >= argv && **lastSpecifiedArg == '\0'; --lastSpecifiedArg, --lastSpecifiedArgIndex); for(; lastSpecifiedArg >= argv && **lastSpecifiedArg == '\0'; --lastSpecifiedArg, --lastSpecifiedArgIndex);
} }
// determine arguments relevant for completion
bool nextArgumentOrValue;
if(lastDetectedArg && lastDetectedArg->isPresent()) { if(lastDetectedArg && lastDetectedArg->isPresent()) {
if((nextArgumentOrValue = currentWordIndex > lastDetectedArgIndex)) { if((nextArgumentOrValue = (currentWordIndex > lastDetectedArgIndex))) {
// parameter values of the last arg are possible completions // parameter values of the last arg are possible completions
auto currentValueCount = lastDetectedArg->values(lastDetectedArg->occurrences() - 1).size(); auto currentValueCount = lastDetectedArg->values(lastDetectedArg->occurrences() - 1).size();
if(currentValueCount) { if(currentValueCount) {
@ -740,6 +741,7 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
} }
} else { } else {
// no arguments detected -> just use main arguments for completion
nextArgumentOrValue = true; nextArgumentOrValue = true;
insertSiblings(m_mainArgs, relevantArgs); insertSiblings(m_mainArgs, relevantArgs);
} }
@ -790,27 +792,37 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
if(argc && currentWordIndex <= lastSpecifiedArgIndex && opening) { if(argc && currentWordIndex <= lastSpecifiedArgIndex && opening) {
if(openingDenotationType == Value) { if(openingDenotationType == Value) {
bool wordStart = true, ok = false, equationSignAlreadyPresent = false; bool wordStart = true, ok = false, equationSignAlreadyPresent = false;
int wordIndex = 0; size_t wordIndex = 0;
for(const char *i = arg->preDefinedCompletionValues(), *end = opening + openingLen; *i;) { for(const char *i = arg->preDefinedCompletionValues(), *end = opening + openingLen; *i;) {
if(wordStart) { if(wordStart) {
const char *i1 = i, *i2 = opening; const char *i1 = i, *i2 = opening;
for(; *i1 && i2 != end && *i1 == *i2; ++i1, ++i2); for(; *i1 && i2 != end && *i1 == *i2; ++i1, ++i2);
ok = (i2 == end); if((ok = (i2 == end))) {
cout << '\'';
}
wordStart = false; wordStart = false;
wordIndex = 0; wordIndex = 0;
} else if((wordStart = (*i == ' ') || (*i == '\n'))) { } else if((wordStart = (*i == ' ') || (*i == '\n'))) {
equationSignAlreadyPresent = false; equationSignAlreadyPresent = false;
if(ok) {
cout << '\'' << ' ';
}
++i;
continue;
} else if(*i == '=') { } else if(*i == '=') {
equationSignAlreadyPresent = true; equationSignAlreadyPresent = true;
} }
if(ok) { if(ok) {
if(!compoundOpeningStartLen || wordIndex >= compoundOpeningStartLen) { if(!compoundOpeningStartLen || wordIndex >= compoundOpeningStartLen) {
if(*i == '\'') {
cout << "'\"'\"'";
}
cout << *i; cout << *i;
} }
++i, ++wordIndex; ++i, ++wordIndex;
if(appendEquationSign && !equationSignAlreadyPresent) { switch(*i) {
switch(*i) { case ' ': case '\n': case '\0':
case ' ': case '\n': case '\0': if(appendEquationSign && !equationSignAlreadyPresent) {
cout << '='; cout << '=';
noWhitespace = true; noWhitespace = true;
equationSignAlreadyPresent = false; equationSignAlreadyPresent = false;
@ -822,23 +834,33 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
} }
cout << ' '; cout << ' ';
} }
} else if(appendEquationSign) { } else if(const char *i = arg->preDefinedCompletionValues()) {
bool equationSignAlreadyPresent = false; bool equationSignAlreadyPresent = false;
for(const char *i = arg->preDefinedCompletionValues(); *i;) { cout << '\'';
cout << *i; while(*i) {
if(*i == '\'') {
cout << "'\"'\"'";
} else {
cout << *i;
}
switch(*(++i)) { switch(*(++i)) {
case '=': case '=':
equationSignAlreadyPresent = true; equationSignAlreadyPresent = true;
break; break;
case ' ': case '\n': case '\0': case ' ': case '\n': case '\0':
if(!equationSignAlreadyPresent) { if(appendEquationSign && !equationSignAlreadyPresent) {
cout << '='; cout << '=';
equationSignAlreadyPresent = false; equationSignAlreadyPresent = false;
} }
if(*i != '\0') {
cout << '\'';
if(*(++i)) {
cout << ' ' << '\'';
}
}
} }
} }
} else { cout << '\'' << ' ';
cout << arg->preDefinedCompletionValues() << ' ';
} }
} }
} }
@ -861,11 +883,11 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
} }
if(openingDenotationType == Abbreviation && opening) { if(openingDenotationType == Abbreviation && opening) {
cout << '-' << opening << arg->abbreviation() << ' '; cout << '\'' << '-' << opening << arg->abbreviation() << '\'' << ' ';
} 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 {
cout << '-' << '-' << arg->name() << ' '; cout << '\'' << '-' << '-' << arg->name() << '\'' << ' ';
} }
} }
// -> completions for files and dirs // -> completions for files and dirs
@ -873,7 +895,7 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
string actualDir, actualFile; string actualDir, actualFile;
bool haveFileOrDirCompletions = false; bool haveFileOrDirCompletions = false;
if(argc && currentWordIndex == lastSpecifiedArgIndex && opening) { if(argc && currentWordIndex == lastSpecifiedArgIndex && opening) {
// the "opening" might contain escaped characters which need to be unescaped first // the "opening" might contain escaped characters which need to be unescaped first (let's hope this covers all possible escapings)
string unescapedOpening(opening); string unescapedOpening(opening);
findAndReplace<string>(unescapedOpening, "\\ ", " "); findAndReplace<string>(unescapedOpening, "\\ ", " ");
findAndReplace<string>(unescapedOpening, "\\,", ","); findAndReplace<string>(unescapedOpening, "\\,", ",");
@ -882,6 +904,9 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
findAndReplace<string>(unescapedOpening, "\\!", "!"); findAndReplace<string>(unescapedOpening, "\\!", "!");
findAndReplace<string>(unescapedOpening, "\\#", "#"); findAndReplace<string>(unescapedOpening, "\\#", "#");
findAndReplace<string>(unescapedOpening, "\\$", "$"); findAndReplace<string>(unescapedOpening, "\\$", "$");
findAndReplace<string>(unescapedOpening, "\\'", "'");
findAndReplace<string>(unescapedOpening, "\\\"", "\"");
findAndReplace<string>(unescapedOpening, "\\\\", "\\");
// determine the "directory" part // determine the "directory" part
string dir = directory(unescapedOpening); string dir = directory(unescapedOpening);
if(dir.empty()) { if(dir.empty()) {
@ -905,42 +930,35 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
} }
actualFile = move(file); actualFile = move(file);
} }
// -> completion for files
// -> completion for files and dirs
DirectoryEntryType entryTypes = DirectoryEntryType::None;
if(completeFiles) { if(completeFiles) {
if(argc && currentWordIndex <= lastSpecifiedArgIndex && opening) { entryTypes |= DirectoryEntryType::File;
for(const string &dirEntry : directoryEntries(actualDir.c_str(), DirectoryEntryType::File)) {
if(startsWith(dirEntry, actualFile)) {
cout << '\'';
if(actualDir != ".") {
cout << actualDir;
}
cout << dirEntry << '\'' << ' ';
haveFileOrDirCompletions = true;
}
}
} else {
for(const string &dirEntry : directoryEntries(".", DirectoryEntryType::File)) {
cout << dirEntry << ' ';
haveFileOrDirCompletions = true;
}
}
} }
// -> completion for dirs
if(completeDirs) { if(completeDirs) {
entryTypes |= DirectoryEntryType::Directory;
}
if(entryTypes != DirectoryEntryType::None) {
const string replace("'"), with("'\"'\"'");
if(argc && currentWordIndex <= lastSpecifiedArgIndex && opening) { if(argc && currentWordIndex <= lastSpecifiedArgIndex && opening) {
for(const string &dirEntry : directoryEntries(actualDir.c_str(), DirectoryEntryType::Directory)) { list<string> entries = directoryEntries(actualDir.c_str(), entryTypes);
findAndReplace(actualDir, replace, with);
for(string &dirEntry : entries) {
if(startsWith(dirEntry, actualFile)) { if(startsWith(dirEntry, actualFile)) {
cout << '\''; cout << '\'';
if(actualDir != ".") { if(actualDir != ".") {
cout << actualDir; cout << actualDir;
} }
findAndReplace(dirEntry, replace, with);
cout << dirEntry << '\'' << ' '; cout << dirEntry << '\'' << ' ';
haveFileOrDirCompletions = true; haveFileOrDirCompletions = true;
} }
} }
} else { } else {
for(const string &dirEntry : directoryEntries(".", DirectoryEntryType::Directory)) { for(string &dirEntry : directoryEntries(".", entryTypes)) {
cout << '\'' << dirEntry << '/' << '\'' << ' '; findAndReplace(dirEntry, replace, with);
cout << '\'' << dirEntry << '\'' << ' ';
haveFileOrDirCompletions = true; haveFileOrDirCompletions = true;
} }
} }

View File

@ -238,8 +238,7 @@ private:
/*! /*!
* \brief Returns the name of the argument. * \brief Returns the name of the argument.
* *
* The parser compares the name with the characters following a "--" prefix to * The parser compares the name with the characters following a "--" prefix to identify arguments.
* identify arguments.
*/ */
inline const char *Argument::name() const inline const char *Argument::name() const
{ {
@ -249,17 +248,17 @@ inline const char *Argument::name() const
/*! /*!
* \brief Sets the name of the argument. * \brief Sets the name of the argument.
* *
* The name mustn't be empty or contain white spaces or equation chars. * The name mustn't be empty, start with a minus or contain white spaces, equation chars, quotes and newlines.
* *
* The parser compares the name with the characters following a "--" prefix to * The parser compares the name with the characters following a "--" prefix to identify arguments.
* identify arguments.
*/ */
inline void Argument::setName(const char *name) inline void Argument::setName(const char *name)
{ {
#ifdef DEBUG_BUILD #ifdef DEBUG_BUILD
if(name && *name) { if(name && *name) {
assert(*name != '-');
for(const char *c = name; *c; ++c) { for(const char *c = name; *c; ++c) {
assert(*c != ' ' && *c != '='); assert(*c != ' ' && *c != '=' && *c != '\'' && *c != '\"' && *c != '\n' && *c != '\r');
} }
} }
#endif #endif
@ -269,8 +268,7 @@ inline void Argument::setName(const char *name)
/*! /*!
* \brief Returns the abbreviation of the argument. * \brief Returns the abbreviation of the argument.
* *
* The parser compares the abbreviation with the characters following a "-" prefix to * The parser compares the abbreviation with the characters following a "-" prefix to identify arguments.
* identify arguments.
*/ */
inline char Argument::abbreviation() const inline char Argument::abbreviation() const
{ {
@ -280,15 +278,14 @@ inline char Argument::abbreviation() const
/*! /*!
* \brief Sets the abbreviation of the argument. * \brief Sets the abbreviation of the argument.
* *
* The abbreviation might be empty but mustn't contain any white spaces or * The abbreviation might be empty but mustn't be white spaces, equation char, single quote, double quote or newline.
* equation chars when provided.
* *
* The parser compares the abbreviation with the characters following a "-" prefix to * The parser compares the abbreviation with the characters following a "-" prefix to identify arguments.
* identify arguments.
*/ */
inline void Argument::setAbbreviation(char abbreviation) inline void Argument::setAbbreviation(char abbreviation)
{ {
IF_DEBUG_BUILD(assert(abbreviation != ' ' && abbreviation != '=')); IF_DEBUG_BUILD(assert(abbreviation != ' ' && abbreviation != '=' && abbreviation != '-'
&& abbreviation != '\'' && abbreviation != '"' && abbreviation != '\n' && abbreviation != '\r'));
m_abbreviation = abbreviation; m_abbreviation = abbreviation;
} }

View File

@ -38,6 +38,11 @@ constexpr DirectoryEntryType operator|(DirectoryEntryType lhs, DirectoryEntryTyp
return static_cast<DirectoryEntryType>(static_cast<unsigned char>(lhs) | static_cast<unsigned char>(rhs)); return static_cast<DirectoryEntryType>(static_cast<unsigned char>(lhs) | static_cast<unsigned char>(rhs));
} }
constexpr DirectoryEntryType &operator|=(DirectoryEntryType &lhs, DirectoryEntryType rhs)
{
return (lhs = static_cast<DirectoryEntryType>(static_cast<unsigned char>(lhs) | static_cast<unsigned char>(rhs)));
}
constexpr DirectoryEntryType operator&(DirectoryEntryType lhs, DirectoryEntryType rhs) constexpr DirectoryEntryType operator&(DirectoryEntryType lhs, DirectoryEntryType rhs)
{ {
return static_cast<DirectoryEntryType>(static_cast<unsigned char>(lhs) & static_cast<unsigned char>(rhs)); return static_cast<DirectoryEntryType>(static_cast<unsigned char>(lhs) & static_cast<unsigned char>(rhs));

1
testfiles/t.aac Normal file
View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

View File

@ -399,7 +399,7 @@ void ArgumentParserTests::testBashCompletion()
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv1 + 1, lastDetectedArg, true); parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv1 + 1, lastDetectedArg, true);
parser.printBashCompletion(1, argv1, 0, lastDetectedArg); parser.printBashCompletion(1, argv1, 0, lastDetectedArg);
cout.rdbuf(regularCoutBuffer); cout.rdbuf(regularCoutBuffer);
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=(set )\n"), buffer.str()); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('set' )\n"), buffer.str());
// argument is already specified // argument is already specified
const char *const argv2[] = {"set"}; const char *const argv2[] = {"set"};
@ -409,7 +409,7 @@ void ArgumentParserTests::testBashCompletion()
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv2 + 1, lastDetectedArg, true); parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv2 + 1, lastDetectedArg, true);
parser.printBashCompletion(1, argv2, 0, lastDetectedArg); parser.printBashCompletion(1, argv2, 0, lastDetectedArg);
cout.rdbuf(regularCoutBuffer); cout.rdbuf(regularCoutBuffer);
CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=(set )\n"), buffer.str()); CPPUNIT_ASSERT_EQUAL(string("COMPREPLY=('set' )\n"), buffer.str());
// advance the cursor position -> the completion should propose the next argument // advance the cursor position -> the completion should propose the next argument
index = 0, lastDetectedArg = nullptr, buffer.str(string()), setArg.reset(); index = 0, lastDetectedArg = nullptr, buffer.str(string()), setArg.reset();
@ -418,7 +418,7 @@ void ArgumentParserTests::testBashCompletion()
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv2 + 1, lastDetectedArg, true); parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv2 + 1, lastDetectedArg, true);
parser.printBashCompletion(1, argv2, 1, lastDetectedArg); parser.printBashCompletion(1, argv2, 1, lastDetectedArg);
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());
// specifying no args should propose all main arguments // specifying no args should propose all main arguments
index = 0, lastDetectedArg = nullptr, buffer.str(string()), getArg.reset(), setArg.reset(); index = 0, lastDetectedArg = nullptr, buffer.str(string()), getArg.reset(), setArg.reset();
@ -427,7 +427,7 @@ void ArgumentParserTests::testBashCompletion()
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, nullptr, lastDetectedArg, true); parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, nullptr, lastDetectedArg, true);
parser.printBashCompletion(0, nullptr, 0, lastDetectedArg); parser.printBashCompletion(0, nullptr, 0, lastDetectedArg);
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());
// values // values
const char *const argv3[] = {"get", "--fields"}; const char *const argv3[] = {"get", "--fields"};
@ -437,7 +437,7 @@ void ArgumentParserTests::testBashCompletion()
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv3 + 2, lastDetectedArg, true); parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv3 + 2, lastDetectedArg, true);
parser.printBashCompletion(2, argv3, 2, lastDetectedArg); parser.printBashCompletion(2, argv3, 2, lastDetectedArg);
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());
// values with equation sign, one letter already present // values with equation sign, one letter already present
const char *const argv4[] = {"set", "--values", "a"}; const char *const argv4[] = {"set", "--values", "a"};
@ -447,11 +447,14 @@ void ArgumentParserTests::testBashCompletion()
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv4 + 3, lastDetectedArg, true); parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv4 + 3, lastDetectedArg, true);
parser.printBashCompletion(3, argv4, 2, lastDetectedArg); parser.printBashCompletion(3, argv4, 2, lastDetectedArg);
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());
// file names // file names
string iniFilePath = TestUtilities::testFilePath("test.ini"); string iniFilePath = TestUtilities::testFilePath("test.ini");
iniFilePath.resize(iniFilePath.size() - 3); iniFilePath.resize(iniFilePath.size() - 4);
string mkvFilePath = TestUtilities::testFilePath("test 'with quote'.mkv");
mkvFilePath.resize(mkvFilePath.size() - 17);
TestUtilities::testFilePath("t.aac");
const char *const argv5[] = {"get", "--files", iniFilePath.c_str()}; const char *const argv5[] = {"get", "--files", iniFilePath.c_str()};
index = 0, lastDetectedArg = nullptr, buffer.str(string()), getArg.reset(), setArg.reset(); index = 0, lastDetectedArg = nullptr, buffer.str(string()), getArg.reset(), setArg.reset();
cout.rdbuf(buffer.rdbuf()); cout.rdbuf(buffer.rdbuf());
@ -459,7 +462,7 @@ void ArgumentParserTests::testBashCompletion()
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv5 + 3, lastDetectedArg, true); parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv5 + 3, lastDetectedArg, true);
parser.printBashCompletion(3, argv5, 2, lastDetectedArg); parser.printBashCompletion(3, argv5, 2, lastDetectedArg);
cout.rdbuf(regularCoutBuffer); cout.rdbuf(regularCoutBuffer);
CPPUNIT_ASSERT_EQUAL("COMPREPLY=('" + iniFilePath + "ini' ); compopt -o filenames\n", buffer.str()); CPPUNIT_ASSERT_EQUAL("COMPREPLY=('" + mkvFilePath + " '\"'\"'with quote'\"'\"'.mkv' '" + iniFilePath + ".ini' ); compopt -o filenames\n", buffer.str());
// sub arguments // sub arguments
const char *const argv6[] = {"set", "--"}; const char *const argv6[] = {"set", "--"};
@ -469,7 +472,7 @@ void ArgumentParserTests::testBashCompletion()
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv6 + 2, lastDetectedArg, true); parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv6 + 2, lastDetectedArg, true);
parser.printBashCompletion(2, argv6, 1, lastDetectedArg); parser.printBashCompletion(2, argv6, 1, lastDetectedArg);
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());
// nested sub arguments // nested sub arguments
const char *const argv7[] = {"-i", "--sub", "--"}; const char *const argv7[] = {"-i", "--sub", "--"};
@ -479,7 +482,7 @@ void ArgumentParserTests::testBashCompletion()
parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv7 + 3, lastDetectedArg, true); parser.readSpecifiedArgs(parser.m_mainArgs, index, argv, argv7 + 3, lastDetectedArg, true);
parser.printBashCompletion(3, argv7, 2, lastDetectedArg); parser.printBashCompletion(3, argv7, 2, lastDetectedArg);
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());
} catch(...) { } catch(...) {
cout.rdbuf(regularCoutBuffer); cout.rdbuf(regularCoutBuffer);