Rework ArgumentParser::parseArgs()

* Remove "ext()" and "orExit()" versions
* Exit by default (might be intrusive but it is the most
  common use)
* Rename Failure to ParseError
This commit is contained in:
Martchus 2019-06-10 16:03:27 +02:00
parent 93bdf5b4f1
commit 4c1b733290
9 changed files with 132 additions and 180 deletions

View File

@ -4,7 +4,6 @@ cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
set(HEADER_FILES set(HEADER_FILES
application/argumentparser.h application/argumentparser.h
application/commandlineutils.h application/commandlineutils.h
application/failure.h
application/fakeqtconfigarguments.h application/fakeqtconfigarguments.h
application/global.h application/global.h
chrono/datetime.h chrono/datetime.h
@ -28,6 +27,7 @@ set(HEADER_FILES
misc/math.h misc/math.h
misc/memory.h misc/memory.h
misc/multiarray.h misc/multiarray.h
misc/parseerror.h
misc/traits.h misc/traits.h
misc/levenshtein.h misc/levenshtein.h
tests/testutils.h tests/testutils.h
@ -37,7 +37,6 @@ set(SRC_FILES
application/argumentparserprivate.h application/argumentparserprivate.h
application/argumentparser.cpp application/argumentparser.cpp
application/commandlineutils.cpp application/commandlineutils.cpp
application/failure.cpp
application/fakeqtconfigarguments.cpp application/fakeqtconfigarguments.cpp
chrono/datetime.cpp chrono/datetime.cpp
chrono/period.cpp chrono/period.cpp
@ -53,6 +52,7 @@ set(SRC_FILES
io/nativefilestream.cpp io/nativefilestream.cpp
io/misc.cpp io/misc.cpp
misc/math.cpp misc/math.cpp
misc/parseerror.cpp
misc/levenshtein.cpp misc/levenshtein.cpp
tests/testutils.cpp) tests/testutils.cpp)

View File

@ -1,13 +1,13 @@
#include "./argumentparser.h" #include "./argumentparser.h"
#include "./argumentparserprivate.h" #include "./argumentparserprivate.h"
#include "./commandlineutils.h" #include "./commandlineutils.h"
#include "./failure.h"
#include "../conversion/stringbuilder.h" #include "../conversion/stringbuilder.h"
#include "../conversion/stringconversion.h" #include "../conversion/stringconversion.h"
#include "../io/ansiescapecodes.h" #include "../io/ansiescapecodes.h"
#include "../io/path.h" #include "../io/path.h"
#include "../misc/levenshtein.h" #include "../misc/levenshtein.h"
#include "../misc/parseerror.h"
#include <algorithm> #include <algorithm>
#include <cstdlib> #include <cstdlib>
@ -855,36 +855,6 @@ void ArgumentParser::printHelp(ostream &os) const
} }
} }
/*!
* \brief Parses the specified command line arguments.
* \remarks
* - The results are stored in the Argument instances assigned as main arguments and sub arguments.
* - Calls the assigned callbacks if no constraints are violated.
* - This method will not return in case shell completion is requested. This behavior can be altered
* by overriding ApplicationUtilities::exitFunction which defaults to &std::exit.
* \throws Throws Failure if the specified arguments are invalid or violate the constraints defined
* by the Argument instances.
* \sa readArgs(), parseArgsOrExit()
*/
void ArgumentParser::parseArgs(int argc, const char *const *argv)
{
parseArgsExt(argc, argv, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
}
/*!
* \brief Parses the specified command line arguments.
* \remarks The same as parseArgs(), except that this method will not throw an exception in the error
* case. Instead, it will print an error message and terminate the application with exit
* code 1.
* \sa parseArgs(), readArgs()
* \deprecated In next major release, this method will be removed. parseArgs() can serve the same
* purpose then.
*/
void ArgumentParser::parseArgsOrExit(int argc, const char *const *argv)
{
parseArgsExt(argc, argv);
}
/*! /*!
* \brief Parses the specified command line arguments. * \brief Parses the specified command line arguments.
* *
@ -898,11 +868,10 @@ void ArgumentParser::parseArgsOrExit(int argc, const char *const *argv)
* - This method will not return in case shell completion is requested. This behavior can be altered * - This method will not return in case shell completion is requested. This behavior can be altered
* by overriding ApplicationUtilities::exitFunction which defaults to &std::exit. * by overriding ApplicationUtilities::exitFunction which defaults to &std::exit.
* \throws Throws Failure if the specified arguments are invalid and the ParseArgumentBehavior::ExitOnFailure * \throws Throws Failure if the specified arguments are invalid and the ParseArgumentBehavior::ExitOnFailure
* flag is not present. * flag is *not* present.
* \sa parseArgs(), readArgs(), parseArgsOrExit() * \sa parseArgs(), readArgs(), parseArgsOrExit()
* \deprecated In next major release, this method will be available as parseArgs().
*/ */
void ArgumentParser::parseArgsExt(int argc, const char *const *argv, ParseArgumentBehavior behavior) void ArgumentParser::parseArgs(int argc, const char *const *argv, ParseArgumentBehavior behavior)
{ {
try { try {
readArgs(argc, argv); readArgs(argc, argv);
@ -915,7 +884,7 @@ void ArgumentParser::parseArgsExt(int argc, const char *const *argv, ParseArgume
if (behavior & ParseArgumentBehavior::InvokeCallbacks) { if (behavior & ParseArgumentBehavior::InvokeCallbacks) {
invokeCallbacks(m_mainArgs); invokeCallbacks(m_mainArgs);
} }
} catch (const Failure &failure) { } catch (const ParseError &failure) {
if (behavior & ParseArgumentBehavior::ExitOnFailure) { if (behavior & ParseArgumentBehavior::ExitOnFailure) {
CMD_UTILS_START_CONSOLE; CMD_UTILS_START_CONSOLE;
cerr << failure; cerr << failure;
@ -988,7 +957,7 @@ void ArgumentParser::readArgs(int argc, const char *const *argv)
// fail when not all arguments could be processed, except when in completion mode // fail when not all arguments could be processed, except when in completion mode
if (!completionMode && !allArgsProcessed) { if (!completionMode && !allArgsProcessed) {
const auto suggestions(findSuggestions(argc, argv, static_cast<unsigned int>(argc - 1), reader)); const auto suggestions(findSuggestions(argc, argv, static_cast<unsigned int>(argc - 1), reader));
throw Failure(argsToString("The specified argument \"", *reader.argv, "\" is unknown.", suggestions)); throw ParseError(argsToString("The specified argument \"", *reader.argv, "\" is unknown.", suggestions));
} }
// print Bash completion and prevent the applicaton to continue with the regular execution // print Bash completion and prevent the applicaton to continue with the regular execution
@ -1577,11 +1546,11 @@ void ArgumentParser::checkConstraints(const ArgumentVector &args)
for (const Argument *arg : args) { for (const Argument *arg : args) {
const auto occurrences = arg->occurrences(); const auto occurrences = arg->occurrences();
if (arg->isParentPresent() && occurrences > arg->maxOccurrences()) { if (arg->isParentPresent() && occurrences > arg->maxOccurrences()) {
throw Failure(argsToString("The argument \"", arg->name(), "\" mustn't be specified more than ", arg->maxOccurrences(), throw ParseError(argsToString("The argument \"", arg->name(), "\" mustn't be specified more than ", arg->maxOccurrences(),
(arg->maxOccurrences() == 1 ? " time." : " times."))); (arg->maxOccurrences() == 1 ? " time." : " times.")));
} }
if (arg->isParentPresent() && occurrences < arg->minOccurrences()) { if (arg->isParentPresent() && occurrences < arg->minOccurrences()) {
throw Failure(argsToString("The argument \"", arg->name(), "\" must be specified at least ", arg->minOccurrences(), throw ParseError(argsToString("The argument \"", arg->name(), "\" must be specified at least ", arg->minOccurrences(),
(arg->minOccurrences() == 1 ? " time." : " times."))); (arg->minOccurrences() == 1 ? " time." : " times.")));
} }
Argument *conflictingArgument = nullptr; Argument *conflictingArgument = nullptr;
@ -1593,7 +1562,7 @@ void ArgumentParser::checkConstraints(const ArgumentVector &args)
conflictingArgument = arg->conflictsWithArgument(); conflictingArgument = arg->conflictsWithArgument();
} }
if (conflictingArgument) { if (conflictingArgument) {
throw Failure(argsToString("The argument \"", conflictingArgument->name(), "\" can not be combined with \"", arg->name(), "\".")); throw ParseError(argsToString("The argument \"", conflictingArgument->name(), "\" can not be combined with \"", arg->name(), "\"."));
} }
for (size_t i = 0; i != occurrences; ++i) { for (size_t i = 0; i != occurrences; ++i) {
if (arg->allRequiredValuesPresent(i)) { if (arg->allRequiredValuesPresent(i)) {
@ -1615,7 +1584,7 @@ void ArgumentParser::checkConstraints(const ArgumentVector &args)
ss << "\nvalue " << (++valueNamesPrint); ss << "\nvalue " << (++valueNamesPrint);
} }
} }
throw Failure(ss.str()); throw ParseError(ss.str());
} }
// check contraints of sub arguments recursively // check contraints of sub arguments recursively
@ -1759,7 +1728,7 @@ void NoColorArgument::apply() const
*/ */
void ValueConversion::Helper::ArgumentValueConversionError::throwFailure(const std::vector<Argument *> &argumentPath) const void ValueConversion::Helper::ArgumentValueConversionError::throwFailure(const std::vector<Argument *> &argumentPath) const
{ {
throw Failure(argumentPath.empty() throw ParseError(argumentPath.empty()
? argsToString("Conversion of top-level value \"", valueToConvert, "\" to type \"", targetTypeName, "\" failed: ", errorMessage) ? argsToString("Conversion of top-level value \"", valueToConvert, "\" to type \"", targetTypeName, "\" failed: ", errorMessage)
: argsToString("Conversion of value \"", valueToConvert, "\" (for argument --", argumentPath.back()->name(), ") to type \"", : argsToString("Conversion of value \"", valueToConvert, "\" (for argument --", argumentPath.back()->name(), ") to type \"",
targetTypeName, "\" failed: ", errorMessage)); targetTypeName, "\" failed: ", errorMessage));
@ -1770,7 +1739,7 @@ void ValueConversion::Helper::ArgumentValueConversionError::throwFailure(const s
*/ */
void ArgumentOccurrence::throwNumberOfValuesNotSufficient(unsigned long valuesToConvert) const void ArgumentOccurrence::throwNumberOfValuesNotSufficient(unsigned long valuesToConvert) const
{ {
throw Failure(path.empty() throw ParseError(path.empty()
? argsToString("Expected ", valuesToConvert, " top-level values to be present but only ", values.size(), " have been specified.") ? argsToString("Expected ", valuesToConvert, " top-level values to be present but only ", values.size(), " have been specified.")
: argsToString("Expected ", valuesToConvert, " values for argument --", path.back()->name(), " to be present but only ", values.size(), : argsToString("Expected ", valuesToConvert, " values for argument --", path.back()->name(), " to be present but only ", values.size(),
" have been specified.")); " have been specified."));

View File

@ -460,9 +460,7 @@ public:
// declare operations which will consider previously assigned argument definitions and maybe modify parsing results // declare operations which will consider previously assigned argument definitions and maybe modify parsing results
void printHelp(std::ostream &os) const; void printHelp(std::ostream &os) const;
void parseArgs(int argc, const char *const *argv); void parseArgs(int argc, const char *const *argv,
void parseArgsOrExit(int argc, const char *const *argv);
void parseArgsExt(int argc, const char *const *argv,
ParseArgumentBehavior behavior ParseArgumentBehavior behavior
= ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks | ParseArgumentBehavior::ExitOnFailure); = ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks | ParseArgumentBehavior::ExitOnFailure);
void readArgs(int argc, const char *const *argv); void readArgs(int argc, const char *const *argv);

View File

@ -1,60 +0,0 @@
#include "./failure.h"
#include "../io/ansiescapecodes.h"
#include <iostream>
namespace ApplicationUtilities {
/*!
* \class ApplicationUtilities::Failure
* \brief The Failure class is thrown by an ArgumentParser when a parsing error occurs.
*
* \sa ApplicationUtilities::ArgumentParser
*/
/*!
* Constructs a new Failure.
*/
Failure::Failure()
: m_what("unspecified parsing exception")
{
}
/*!
* Constructs a new Failure. \a what is a std::string
* describing the cause of the Failure.
*/
Failure::Failure(const std::string &what)
: m_what(what)
{
}
/*!
* Destroys the Failure.
*/
Failure::~Failure() noexcept
{
}
/*!
* Returns a C-style character string describing the cause
* of the Failure.
*/
const char *Failure::what() const noexcept
{
return m_what.c_str();
}
/*!
* \brief Prints an error message "Unable to parse arguments: ..." for the specified \a failure.
*/
std::ostream &operator<<(std::ostream &o, const Failure &failure)
{
using namespace std;
using namespace EscapeCodes;
return o << Phrases::Error << "Unable to parse arguments: " << TextAttribute::Reset << failure.what() << "\nSee --help for available commands."
<< endl;
}
} // namespace ApplicationUtilities

View File

@ -1,28 +0,0 @@
#ifndef APPLICATION_UTILITIES_FAILURE_H
#define APPLICATION_UTILITIES_FAILURE_H
#include "../global.h"
#include <exception>
#include <iosfwd>
#include <string>
namespace ApplicationUtilities {
class CPP_UTILITIES_EXPORT Failure : public std::exception {
public:
Failure();
Failure(const std::string &what);
~Failure() noexcept override;
virtual const char *what() const noexcept override;
private:
std::string m_what;
};
CPP_UTILITIES_EXPORT std::ostream &operator<<(std::ostream &o, const Failure &failure);
} // namespace ApplicationUtilities
#endif // APPLICATION_UTILITIES_FAILURE_H

34
misc/parseerror.cpp Normal file
View File

@ -0,0 +1,34 @@
#include "./parseerror.h"
#include "../io/ansiescapecodes.h"
#include <iostream>
namespace ApplicationUtilities {
/*!
* \class ApplicationUtilities::ParseError
* \brief The ParseError class is thrown by an ArgumentParser when a parsing error occurs.
* \remarks The class might be used in other parsers, too.
* \sa ApplicationUtilities::ArgumentParser
*/
/*!
* \brief Destroys the ParseError.
*/
ParseError::~ParseError() noexcept
{
}
/*!
* \brief Prints an error message "Unable to parse arguments: ..." for the specified \a failure.
*/
std::ostream &operator<<(std::ostream &o, const ParseError &failure)
{
using namespace std;
using namespace EscapeCodes;
return o << Phrases::Error << "Unable to parse arguments: " << TextAttribute::Reset << failure.what() << "\nSee --help for available commands."
<< endl;
}
} // namespace ApplicationUtilities

38
misc/parseerror.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef APPLICATION_UTILITIES_PARSE_ERROR_H
#define APPLICATION_UTILITIES_PARSE_ERROR_H
#include "../global.h"
#include <iosfwd>
#include <stdexcept>
namespace ApplicationUtilities {
class CPP_UTILITIES_EXPORT ParseError : public std::runtime_error {
public:
ParseError();
ParseError(const std::string &what);
~ParseError() noexcept override;
};
/*!
* \brief Constructs a new ParseError.
*/
inline ParseError::ParseError()
: std::runtime_error("undetermined parsing")
{
}
/*!
* \brief Constructs a new ParseError. \a what is a std::string describing the cause of the ParseError.
*/
inline ParseError::ParseError(const std::string &what)
: std::runtime_error(what)
{
}
CPP_UTILITIES_EXPORT std::ostream &operator<<(std::ostream &o, const ParseError &failure);
} // namespace ApplicationUtilities
#endif // APPLICATION_UTILITIES_PARSE_ERROR_H

View File

@ -7,12 +7,13 @@
#include "../application/argumentparser.h" #include "../application/argumentparser.h"
#include "../application/argumentparserprivate.h" #include "../application/argumentparserprivate.h"
#include "../application/commandlineutils.h" #include "../application/commandlineutils.h"
#include "../application/failure.h"
#include "../application/fakeqtconfigarguments.h" #include "../application/fakeqtconfigarguments.h"
#include "../io/ansiescapecodes.h" #include "../io/ansiescapecodes.h"
#include "../io/path.h" #include "../io/path.h"
#include "../misc/parseerror.h"
#include "resources/config.h" #include "resources/config.h"
#include <cppunit/TestFixture.h> #include <cppunit/TestFixture.h>
@ -155,7 +156,7 @@ void ArgumentParserTests::testParsing()
{ &qtConfigArgs.qtWidgetsGuiArg(), &printFieldNamesArg, &displayTagInfoArg, &displayFileInfoArg, &parser.noColorArg(), &parser.helpArg() }); { &qtConfigArgs.qtWidgetsGuiArg(), &printFieldNamesArg, &displayTagInfoArg, &displayFileInfoArg, &parser.noColorArg(), &parser.helpArg() });
// no args present // no args present
parser.parseArgs(0, nullptr); parser.parseArgs(0, nullptr, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_ASSERT(!parser.executable()); CPPUNIT_ASSERT(!parser.executable());
CPPUNIT_ASSERT(!parser.specifiedOperation()); CPPUNIT_ASSERT(!parser.specifiedOperation());
CPPUNIT_ASSERT_EQUAL(0u, parser.actualArgumentCount()); CPPUNIT_ASSERT_EQUAL(0u, parser.actualArgumentCount());
@ -164,9 +165,9 @@ void ArgumentParserTests::testParsing()
const char *argv[] = { "tageditor", "get", "album", "title", "diskpos", "-f", "somefile" }; const char *argv[] = { "tageditor", "get", "album", "title", "diskpos", "-f", "somefile" };
// try to parse, this should fail // try to parse, this should fail
try { try {
parser.parseArgs(7, argv); parser.parseArgs(7, argv, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_FAIL("Exception expected."); CPPUNIT_FAIL("Exception expected.");
} catch (const Failure &e) { } catch (const ParseError &e) {
CPPUNIT_ASSERT_EQUAL("The argument \"files\" can not be combined with \"fields\"."s, string(e.what())); CPPUNIT_ASSERT_EQUAL("The argument \"files\" can not be combined with \"fields\"."s, string(e.what()));
// test printing btw // test printing btw
stringstream ss; stringstream ss;
@ -180,7 +181,7 @@ void ArgumentParserTests::testParsing()
// arguments read correctly after successful parse // arguments read correctly after successful parse
filesArg.setCombinable(true); filesArg.setCombinable(true);
parser.resetArgs(); parser.resetArgs();
parser.parseArgs(7, argv); parser.parseArgs(7, argv, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
// check results // check results
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent()); CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
CPPUNIT_ASSERT(!displayFileInfoArg.isPresent()); CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
@ -198,7 +199,7 @@ void ArgumentParserTests::testParsing()
const char *argv2[] = { "tageditor", "", "-p", "album", "title", "diskpos", "", "--files", "somefile" }; const char *argv2[] = { "tageditor", "", "-p", "album", "title", "diskpos", "", "--files", "somefile" };
// reparse the args // reparse the args
parser.resetArgs(); parser.resetArgs();
parser.parseArgs(9, argv2); parser.parseArgs(9, argv2, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
// check results again // check results again
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent()); CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
CPPUNIT_ASSERT(!displayFileInfoArg.isPresent()); CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
@ -217,9 +218,9 @@ void ArgumentParserTests::testParsing()
const char *argv3[] = { "tageditor", "album", "title", "diskpos", "--files", "somefile" }; const char *argv3[] = { "tageditor", "album", "title", "diskpos", "--files", "somefile" };
try { try {
parser.resetArgs(); parser.resetArgs();
parser.parseArgs(6, argv3); parser.parseArgs(6, argv3, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_FAIL("Exception expected."); CPPUNIT_FAIL("Exception expected.");
} catch (const Failure &e) { } catch (const ParseError &e) {
CPPUNIT_ASSERT_EQUAL("The specified argument \"album\" is unknown.\nDid you mean get or --help?"s, string(e.what())); CPPUNIT_ASSERT_EQUAL("The specified argument \"album\" is unknown.\nDid you mean get or --help?"s, string(e.what()));
} }
@ -227,9 +228,9 @@ void ArgumentParserTests::testParsing()
const char *argv18[] = { "tageditor", "get", "album", "title", "diskpos", "--verbose", "--fi" }; const char *argv18[] = { "tageditor", "get", "album", "title", "diskpos", "--verbose", "--fi" };
try { try {
parser.resetArgs(); parser.resetArgs();
parser.parseArgs(7, argv18); parser.parseArgs(7, argv18, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_FAIL("Exception expected."); CPPUNIT_FAIL("Exception expected.");
} catch (const Failure &e) { } catch (const ParseError &e) {
CPPUNIT_ASSERT_EQUAL("The specified argument \"--fi\" is unknown.\nDid you mean --files or --no-color?"s, string(e.what())); CPPUNIT_ASSERT_EQUAL("The specified argument \"--fi\" is unknown.\nDid you mean --files or --no-color?"s, string(e.what()));
} }
@ -246,7 +247,7 @@ void ArgumentParserTests::testParsing()
#endif #endif
parser.resetArgs(); parser.resetArgs();
EscapeCodes::enabled = false; EscapeCodes::enabled = false;
parser.parseArgs(6, argv3); parser.parseArgs(6, argv3, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
// none of the arguments should be present now // none of the arguments should be present now
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent()); CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
@ -260,7 +261,7 @@ void ArgumentParserTests::testParsing()
const char *argv4[] = { "tageditor", "-i", "-vf", "test" }; const char *argv4[] = { "tageditor", "-i", "-vf", "test" };
parser.setUnknownArgumentBehavior(UnknownArgumentBehavior::Fail); parser.setUnknownArgumentBehavior(UnknownArgumentBehavior::Fail);
parser.resetArgs(); parser.resetArgs();
parser.parseArgs(4, argv4); parser.parseArgs(4, argv4, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent()); CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
CPPUNIT_ASSERT(displayFileInfoArg.isPresent()); CPPUNIT_ASSERT(displayFileInfoArg.isPresent());
CPPUNIT_ASSERT(verboseArg.isPresent()); CPPUNIT_ASSERT(verboseArg.isPresent());
@ -274,9 +275,9 @@ void ArgumentParserTests::testParsing()
displayFileInfoArg.reset(); displayFileInfoArg.reset();
fileArg.reset(); fileArg.reset();
try { try {
parser.parseArgs(4, argv4); parser.parseArgs(4, argv4, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_FAIL("Exception expected."); CPPUNIT_FAIL("Exception expected.");
} catch (const Failure &e) { } catch (const ParseError &e) {
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent()); CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
CPPUNIT_ASSERT(!strcmp(e.what(), "The argument \"verbose\" mustn't be specified more than 1 time.")); CPPUNIT_ASSERT(!strcmp(e.what(), "The argument \"verbose\" mustn't be specified more than 1 time."));
} }
@ -285,13 +286,13 @@ void ArgumentParserTests::testParsing()
displayFileInfoArg.reset(); displayFileInfoArg.reset();
fileArg.reset(); fileArg.reset();
verboseArg.setConstraints(0, Argument::varValueCount); verboseArg.setConstraints(0, Argument::varValueCount);
parser.parseArgs(4, argv4); parser.parseArgs(4, argv4, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent()); CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
// constraint checking: mandatory argument // constraint checking: mandatory argument
verboseArg.setRequired(true); verboseArg.setRequired(true);
parser.resetArgs(); parser.resetArgs();
parser.parseArgs(4, argv4); parser.parseArgs(4, argv4, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent()); CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
// contraint checking: error about missing mandatory argument // contraint checking: error about missing mandatory argument
@ -300,9 +301,9 @@ void ArgumentParserTests::testParsing()
fileArg.reset(); fileArg.reset();
verboseArg.reset(); verboseArg.reset();
try { try {
parser.parseArgs(4, argv5); parser.parseArgs(4, argv5, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_FAIL("Exception expected."); CPPUNIT_FAIL("Exception expected.");
} catch (const Failure &e) { } catch (const ParseError &e) {
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent()); CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
CPPUNIT_ASSERT(!strcmp(e.what(), "The argument \"verbose\" must be specified at least 1 time.")); CPPUNIT_ASSERT(!strcmp(e.what(), "The argument \"verbose\" must be specified at least 1 time."));
} }
@ -311,7 +312,7 @@ void ArgumentParserTests::testParsing()
// combined abbreviation with nesting "-pf" // combined abbreviation with nesting "-pf"
const char *argv10[] = { "tageditor", "-pf", "test" }; const char *argv10[] = { "tageditor", "-pf", "test" };
parser.resetArgs(); parser.resetArgs();
parser.parseArgs(3, argv10); parser.parseArgs(3, argv10, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_ASSERT(displayTagInfoArg.isPresent()); CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
CPPUNIT_ASSERT(!displayFileInfoArg.isPresent()); CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
CPPUNIT_ASSERT(!fileArg.isPresent()); CPPUNIT_ASSERT(!fileArg.isPresent());
@ -323,16 +324,16 @@ void ArgumentParserTests::testParsing()
// constraint checking: no complains about missing -i // constraint checking: no complains about missing -i
const char *argv6[] = { "tageditor", "-g" }; const char *argv6[] = { "tageditor", "-g" };
parser.resetArgs(); parser.resetArgs();
parser.parseArgs(2, argv6); parser.parseArgs(2, argv6, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_ASSERT(qtConfigArgs.qtWidgetsGuiArg().isPresent()); CPPUNIT_ASSERT(qtConfigArgs.qtWidgetsGuiArg().isPresent());
// constraint checking: dependend arguments (-f requires -i or -p) // constraint checking: dependend arguments (-f requires -i or -p)
const char *argv7[] = { "tageditor", "-f", "test" }; const char *argv7[] = { "tageditor", "-f", "test" };
parser.resetArgs(); parser.resetArgs();
try { try {
parser.parseArgs(3, argv7); parser.parseArgs(3, argv7, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_FAIL("Exception expected."); CPPUNIT_FAIL("Exception expected.");
} catch (const Failure &e) { } catch (const ParseError &e) {
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent()); CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
CPPUNIT_ASSERT_EQUAL("The specified argument \"-f\" is unknown.\nDid you mean get or --help?"s, string(e.what())); CPPUNIT_ASSERT_EQUAL("The specified argument \"-f\" is unknown.\nDid you mean get or --help?"s, string(e.what()));
} }
@ -340,7 +341,7 @@ void ArgumentParserTests::testParsing()
// equation sign syntax // equation sign syntax
const char *argv11[] = { "tageditor", "-if=test-v" }; const char *argv11[] = { "tageditor", "-if=test-v" };
parser.resetArgs(); parser.resetArgs();
parser.parseArgs(2, argv11); parser.parseArgs(2, argv11, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_ASSERT(!filesArg.isPresent()); CPPUNIT_ASSERT(!filesArg.isPresent());
CPPUNIT_ASSERT(fileArg.isPresent()); CPPUNIT_ASSERT(fileArg.isPresent());
CPPUNIT_ASSERT(!verboseArg.isPresent()); CPPUNIT_ASSERT(!verboseArg.isPresent());
@ -348,7 +349,7 @@ void ArgumentParserTests::testParsing()
CPPUNIT_ASSERT_EQUAL("test-v"s, string(fileArg.values(0).front())); CPPUNIT_ASSERT_EQUAL("test-v"s, string(fileArg.values(0).front()));
const char *argv15[] = { "tageditor", "-i", "--file=test", "-v" }; const char *argv15[] = { "tageditor", "-i", "--file=test", "-v" };
parser.resetArgs(); parser.resetArgs();
parser.parseArgs(4, argv15); parser.parseArgs(4, argv15, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_ASSERT(!filesArg.isPresent()); CPPUNIT_ASSERT(!filesArg.isPresent());
CPPUNIT_ASSERT(fileArg.isPresent()); CPPUNIT_ASSERT(fileArg.isPresent());
CPPUNIT_ASSERT(verboseArg.isPresent()); CPPUNIT_ASSERT(verboseArg.isPresent());
@ -358,7 +359,7 @@ void ArgumentParserTests::testParsing()
// specifying value directly after abbreviation // specifying value directly after abbreviation
const char *argv12[] = { "tageditor", "-iftest" }; const char *argv12[] = { "tageditor", "-iftest" };
parser.resetArgs(); parser.resetArgs();
parser.parseArgs(2, argv12); parser.parseArgs(2, argv12, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_ASSERT(!filesArg.isPresent()); CPPUNIT_ASSERT(!filesArg.isPresent());
CPPUNIT_ASSERT(fileArg.isPresent()); CPPUNIT_ASSERT(fileArg.isPresent());
CPPUNIT_ASSERT_EQUAL(1_st, fileArg.values(0).size()); CPPUNIT_ASSERT_EQUAL(1_st, fileArg.values(0).size());
@ -367,7 +368,7 @@ void ArgumentParserTests::testParsing()
// specifying top-level argument after abbreviation // specifying top-level argument after abbreviation
const char *argv17[] = { "tageditor", "-if=test-v", "--no-color" }; const char *argv17[] = { "tageditor", "-if=test-v", "--no-color" };
parser.resetArgs(); parser.resetArgs();
parser.parseArgs(3, argv17); parser.parseArgs(3, argv17, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_ASSERT(!filesArg.isPresent()); CPPUNIT_ASSERT(!filesArg.isPresent());
CPPUNIT_ASSERT(fileArg.isPresent()); CPPUNIT_ASSERT(fileArg.isPresent());
CPPUNIT_ASSERT(!verboseArg.isPresent()); CPPUNIT_ASSERT(!verboseArg.isPresent());
@ -378,7 +379,7 @@ void ArgumentParserTests::testParsing()
// default argument // default argument
const char *argv8[] = { "tageditor" }; const char *argv8[] = { "tageditor" };
parser.resetArgs(); parser.resetArgs();
parser.parseArgs(1, argv8); parser.parseArgs(1, argv8, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_ASSERT(qtConfigArgs.qtWidgetsGuiArg().isPresent()); CPPUNIT_ASSERT(qtConfigArgs.qtWidgetsGuiArg().isPresent());
CPPUNIT_ASSERT(!displayFileInfoArg.isPresent()); CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
CPPUNIT_ASSERT(!verboseArg.isPresent()); CPPUNIT_ASSERT(!verboseArg.isPresent());
@ -396,7 +397,7 @@ void ArgumentParserTests::testParsing()
const char *argv13[] = { "tageditor", "get", "--fields", "album=test", "title", "diskpos", "--files", "somefile" }; const char *argv13[] = { "tageditor", "get", "--fields", "album=test", "title", "diskpos", "--files", "somefile" };
verboseArg.setRequired(false); verboseArg.setRequired(false);
parser.resetArgs(); parser.resetArgs();
parser.parseArgs(8, argv13); parser.parseArgs(8, argv13, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
// this should still work without complaints // this should still work without complaints
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent()); CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
CPPUNIT_ASSERT(!displayFileInfoArg.isPresent()); CPPUNIT_ASSERT(!displayFileInfoArg.isPresent());
@ -416,9 +417,9 @@ void ArgumentParserTests::testParsing()
fieldsArg.setRequiredValueCount(4); fieldsArg.setRequiredValueCount(4);
parser.resetArgs(); parser.resetArgs();
try { try {
parser.parseArgs(5, argv9); parser.parseArgs(5, argv9, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_FAIL("Exception expected."); CPPUNIT_FAIL("Exception expected.");
} catch (const Failure &e) { } catch (const ParseError &e) {
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent()); CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
CPPUNIT_ASSERT_EQUAL( CPPUNIT_ASSERT_EQUAL(
"Not all parameter for argument \"fields\" provided. You have to provide the following parameter: title album artist trackpos"s, "Not all parameter for argument \"fields\" provided. You have to provide the following parameter: title album artist trackpos"s,
@ -430,9 +431,9 @@ void ArgumentParserTests::testParsing()
fieldsArg.setRequiredValueCount(Argument::varValueCount); fieldsArg.setRequiredValueCount(Argument::varValueCount);
parser.resetArgs(); parser.resetArgs();
try { try {
parser.parseArgs(6, argv16); parser.parseArgs(6, argv16, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_FAIL("Exception expected."); CPPUNIT_FAIL("Exception expected.");
} catch (const Failure &e) { } catch (const ParseError &e) {
CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent()); CPPUNIT_ASSERT(!qtConfigArgs.qtWidgetsGuiArg().isPresent());
CPPUNIT_ASSERT_EQUAL("The specified argument \"--hel\" is unknown.\nDid you mean --help or get?"s, string(e.what())); CPPUNIT_ASSERT_EQUAL("The specified argument \"--hel\" is unknown.\nDid you mean --help or get?"s, string(e.what()));
} }
@ -441,7 +442,7 @@ void ArgumentParserTests::testParsing()
const char *argv14[] = { "tageditor", "get", "fields", "album=test", "-f", "somefile" }; const char *argv14[] = { "tageditor", "get", "fields", "album=test", "-f", "somefile" };
parser.resetArgs(); parser.resetArgs();
fieldsArg.setDenotesOperation(true); fieldsArg.setDenotesOperation(true);
parser.parseArgs(6, argv14); parser.parseArgs(6, argv14, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_ASSERT(displayTagInfoArg.isPresent()); CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
CPPUNIT_ASSERT(fieldsArg.isPresent()); CPPUNIT_ASSERT(fieldsArg.isPresent());
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(0), "album=test")); CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(0), "album=test"));
@ -449,7 +450,7 @@ void ArgumentParserTests::testParsing()
// implicit flag still works when argument doesn't denote operation // implicit flag still works when argument doesn't denote operation
parser.resetArgs(); parser.resetArgs();
fieldsArg.setDenotesOperation(false); fieldsArg.setDenotesOperation(false);
parser.parseArgs(6, argv14); parser.parseArgs(6, argv14, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
CPPUNIT_ASSERT(displayTagInfoArg.isPresent()); CPPUNIT_ASSERT(displayTagInfoArg.isPresent());
CPPUNIT_ASSERT(fieldsArg.isPresent()); CPPUNIT_ASSERT(fieldsArg.isPresent());
CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(0), "fields")); CPPUNIT_ASSERT(!strcmp(fieldsArg.values().at(0), "fields"));
@ -480,7 +481,7 @@ void ArgumentParserTests::testCallbacks()
// test whether callback is invoked when argument with callback is specified // test whether callback is invoked when argument with callback is specified
const char *argv[] = { "test", "-t", "val1", "val2" }; const char *argv[] = { "test", "-t", "val1", "val2" };
try { try {
parser.parseArgs(4, argv); parser.parseArgs(4, argv, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
} catch (int i) { } catch (int i) {
CPPUNIT_ASSERT_EQUAL(i, 42); CPPUNIT_ASSERT_EQUAL(i, 42);
} }
@ -488,7 +489,7 @@ void ArgumentParserTests::testCallbacks()
// test whether callback is not invoked when argument with callback is not specified // test whether callback is not invoked when argument with callback is not specified
callbackArg.reset(); callbackArg.reset();
const char *argv2[] = { "test", "-l", "val1", "val2" }; const char *argv2[] = { "test", "-l", "val1", "val2" };
parser.parseArgs(4, argv2); parser.parseArgs(4, argv2, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
} }
#ifndef PLATFORM_WINDOWS #ifndef PLATFORM_WINDOWS
@ -816,7 +817,7 @@ void ArgumentParserTests::testHelp()
"\n" "\n"
"Project website: " APP_URL "\n"); "Project website: " APP_URL "\n");
EscapeCodes::enabled = true; EscapeCodes::enabled = true;
parser.parseArgs(2, argv); parser.parseArgs(2, argv, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
} }
verboseArg.setDenotesOperation(false); verboseArg.setDenotesOperation(false);
@ -846,7 +847,7 @@ void ArgumentParserTests::testHelp()
"Project website: " APP_URL "\n"); "Project website: " APP_URL "\n");
EscapeCodes::enabled = false; EscapeCodes::enabled = false;
parser.resetArgs(); parser.resetArgs();
parser.parseArgs(2, argv); parser.parseArgs(2, argv, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
} }
} }
#endif #endif
@ -942,13 +943,13 @@ void ArgumentParserTests::testValueConversion()
try { try {
occurrence.convertValues<string, unsigned int, double, int, int>(); occurrence.convertValues<string, unsigned int, double, int, int>();
CPPUNIT_FAIL("Expected exception"); CPPUNIT_FAIL("Expected exception");
} catch (const Failure &failure) { } catch (const ParseError &failure) {
CPPUNIT_ASSERT_EQUAL("Expected 5 top-level values to be present but only 4 have been specified."s, string(failure.what())); CPPUNIT_ASSERT_EQUAL("Expected 5 top-level values to be present but only 4 have been specified."s, string(failure.what()));
} }
try { try {
occurrence.convertValues<int>(); occurrence.convertValues<int>();
CPPUNIT_FAIL("Expected exception"); CPPUNIT_FAIL("Expected exception");
} catch (const Failure &failure) { } catch (const ParseError &failure) {
CPPUNIT_ASSERT_EQUAL( CPPUNIT_ASSERT_EQUAL(
"Conversion of top-level value \"foo\" to type \"i\" failed: The character \"f\" is no valid digit."s, string(failure.what())); "Conversion of top-level value \"foo\" to type \"i\" failed: The character \"f\" is no valid digit."s, string(failure.what()));
} }
@ -956,13 +957,13 @@ void ArgumentParserTests::testValueConversion()
try { try {
occurrence.convertValues<string, unsigned int, double, int, int>(); occurrence.convertValues<string, unsigned int, double, int, int>();
CPPUNIT_FAIL("Expected exception"); CPPUNIT_FAIL("Expected exception");
} catch (const Failure &failure) { } catch (const ParseError &failure) {
CPPUNIT_ASSERT_EQUAL("Expected 5 values for argument --test to be present but only 4 have been specified."s, string(failure.what())); CPPUNIT_ASSERT_EQUAL("Expected 5 values for argument --test to be present but only 4 have been specified."s, string(failure.what()));
} }
try { try {
occurrence.convertValues<int>(); occurrence.convertValues<int>();
CPPUNIT_FAIL("Expected exception"); CPPUNIT_FAIL("Expected exception");
} catch (const Failure &failure) { } catch (const ParseError &failure) {
CPPUNIT_ASSERT_EQUAL("Conversion of value \"foo\" (for argument --test) to type \"i\" failed: The character \"f\" is no valid digit."s, CPPUNIT_ASSERT_EQUAL("Conversion of value \"foo\" (for argument --test) to type \"i\" failed: The character \"f\" is no valid digit."s,
string(failure.what())); string(failure.what()));
} }

View File

@ -1,12 +1,12 @@
#include "./testutils.h" #include "./testutils.h"
#include "../application/failure.h"
#include "../conversion/stringbuilder.h" #include "../conversion/stringbuilder.h"
#include "../conversion/stringconversion.h" #include "../conversion/stringconversion.h"
#include "../io/ansiescapecodes.h" #include "../io/ansiescapecodes.h"
#include "../io/misc.h" #include "../io/misc.h"
#include "../io/nativefilestream.h" #include "../io/nativefilestream.h"
#include "../io/path.h" #include "../io/path.h"
#include "../misc/parseerror.h"
#include <cerrno> #include <cerrno>
#include <cstdlib> #include <cstdlib>
@ -146,8 +146,8 @@ TestApplication::TestApplication(int argc, const char *const *argv)
// parse arguments // parse arguments
try { try {
m_parser.parseArgs(argc, argv); m_parser.parseArgs(argc, argv, ParseArgumentBehavior::CheckConstraints | ParseArgumentBehavior::InvokeCallbacks);
} catch (const Failure &failure) { } catch (const ParseError &failure) {
cerr << failure; cerr << failure;
m_valid = false; m_valid = false;
return; return;