From 7c189b18e19cee956ba57bcdf62a2fa1912c1ee3 Mon Sep 17 00:00:00 2001 From: Martchus Date: Thu, 6 Apr 2017 00:01:06 +0200 Subject: [PATCH] Allow nested operation arguments eg. syncthingctl pwd rescan --- CMakeLists.txt | 4 ++-- application/argumentparser.cpp | 21 +++++++++------------ tests/argumentparsertests.cpp | 30 ++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8aa70df..db020f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,8 +120,8 @@ set(META_APP_AUTHOR "Martchus") set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}") set(META_APP_DESCRIPTION "Common C++ classes and routines used by my applications such as argument parser, IO and conversion utilities") set(META_VERSION_MAJOR 4) -set(META_VERSION_MINOR 6) -set(META_VERSION_PATCH 2) +set(META_VERSION_MINOR 7) +set(META_VERSION_PATCH 0) # find required 3rd party libraries include(3rdParty) diff --git a/application/argumentparser.cpp b/application/argumentparser.cpp index 099c148..9334f47 100644 --- a/application/argumentparser.cpp +++ b/application/argumentparser.cpp @@ -195,14 +195,12 @@ void ArgumentReader::read(ArgumentVector &args) } // first value might denote "operation" - if(!index) { - 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; - } + 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; } } @@ -731,8 +729,8 @@ bool ArgumentParser::isUncombinableMainArgPresent() const * - The same argument has not been added twice to the same parent. * - Only one argument within a parent is default or implicit. * - Only main arguments denote operations. - * - Argument abbreviations are unique within each level. - * - Argument names are unique within within each level. + * - Argument abbreviations are unique within the same level. + * - Argument names are unique within within the same level. * * \remarks * - Verifies the sub arguments, too. @@ -752,7 +750,6 @@ void ApplicationUtilities::ArgumentParser::verifyArgs(const ArgumentVector &args for(const Argument *arg : args) { assert(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) == verifiedArgs.cend()); verifiedArgs.push_back(arg); - assert(arg->isMainArgument() || !arg->denotesOperation()); assert(!arg->isImplicit() || !hasImplicit); hasImplicit |= arg->isImplicit(); assert(!arg->abbreviation() || find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) == abbreviations.cend()); @@ -1026,7 +1023,7 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi 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()) { cout << '\'' << arg->name() << '\'' << ' '; } else { cout << '\'' << '-' << '-' << arg->name() << '\'' << ' '; diff --git a/tests/argumentparsertests.cpp b/tests/argumentparsertests.cpp index 58ab88c..795f877 100644 --- a/tests/argumentparsertests.cpp +++ b/tests/argumentparsertests.cpp @@ -340,6 +340,25 @@ void ArgumentParserTests::testParsing() CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent()); CPPUNIT_ASSERT(!strcmp(e.what(), "Not all parameter for argument \"fields\" provided. You have to provide the following parameter: title album artist trackpos")); } + + // nested operations + const char *argv14[] = {"tageditor", "get", "fields", "album=test", "-f", "somefile"}; + parser.resetArgs(); + fieldsArg.setRequiredValueCount(-1); + fieldsArg.setDenotesOperation(true); + parser.parseArgs(6, argv14); + CPPUNIT_ASSERT(displayTagInfoArg.isPresent()); + CPPUNIT_ASSERT(fieldsArg.isPresent()); + CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(0), "album=test")); + + // implicit flag still works when argument doesn't denote operation + parser.resetArgs(); + fieldsArg.setDenotesOperation(false); + parser.parseArgs(6, argv14); + CPPUNIT_ASSERT(displayTagInfoArg.isPresent()); + CPPUNIT_ASSERT(fieldsArg.isPresent()); + CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(0), "fields")); + CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(1), "album=test")); } /*! @@ -453,10 +472,21 @@ void ArgumentParserTests::testBashCompletion() cout.rdbuf(regularCoutBuffer); CPPUNIT_ASSERT_EQUAL("COMPREPLY=('--files' '--values' )\n"s, buffer.str()); + // nested operations should be proposed as operations + buffer.str(string()); + cout.rdbuf(buffer.rdbuf()); + parser.resetArgs(); + filesArg.setDenotesOperation(true); + reader.reset(argv2, argv2 + 1).read(); + parser.printBashCompletion(1, argv2, 1, reader); + cout.rdbuf(regularCoutBuffer); + CPPUNIT_ASSERT_EQUAL("COMPREPLY=('files' '--values' )\n"s, buffer.str()); + // specifying no args should propose all main arguments buffer.str(string()); cout.rdbuf(buffer.rdbuf()); parser.resetArgs(); + filesArg.setDenotesOperation(false); reader.reset(nullptr, nullptr).read(); parser.printBashCompletion(0, nullptr, 0, reader); cout.rdbuf(regularCoutBuffer);