diff --git a/COPYING b/COPYING index 50d8c47bf..7042fa115 100644 --- a/COPYING +++ b/COPYING @@ -25,6 +25,8 @@ Copyright: 2010-2012, Felix Geyer 2012, Intel Corporation 2012, Nokia Corporation and/or its subsidiary(-ies) 2000-2008, Tom Sato + 2013, Laszlo Papp + 2013, David Faure License: GPL-2 or GPL-3 Files: cmake/GNUInstallDirs.cmake diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b53aa639f..d57153e5f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -27,7 +27,6 @@ set(keepassx_SOURCES autotype/WildcardMatcher.cpp autotype/WindowSelectComboBox.cpp autotype/test/AutoTypeTestInterface.h - core/ArgumentParser.cpp core/AutoTypeAssociations.cpp core/Config.cpp core/Database.cpp @@ -50,6 +49,8 @@ set(keepassx_SOURCES core/TimeInfo.cpp core/Tools.cpp core/Uuid.cpp + core/qcommandlineoption.cpp + core/qcommandlineparser.cpp crypto/Crypto.cpp crypto/CryptoHash.cpp crypto/Random.cpp diff --git a/src/core/qcommandlineoption.cpp b/src/core/qcommandlineoption.cpp new file mode 100644 index 000000000..5f2f70fbf --- /dev/null +++ b/src/core/qcommandlineoption.cpp @@ -0,0 +1,307 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Laszlo Papp +** Copyright (C) 2013 David Faure +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcommandlineoption.h" + +#include + +class QCommandLineOptionPrivate : public QSharedData +{ +public: + inline QCommandLineOptionPrivate() + { } + + void setNames(const QStringList &nameList); + + //! The list of names used for this option. + QStringList names; + + //! The documentation name for the value, if one is expected + //! Example: "-o " means valueName == "file" + QString valueName; + + //! The description used for this option. + QString description; + + //! The list of default values used for this option. + QStringList defaultValues; +}; + +/*! + \since 5.2 + \class QCommandLineOption + \brief The QCommandLineOption class defines a possible command-line option. + \inmodule QtCore + \ingroup shared + \ingroup tools + + This class is used to describe an option on the command line. It allows + different ways of defining the same option with multiple aliases possible. + It is also used to describe how the option is used - it may be a flag (e.g. \c{-v}) + or take an argument (e.g. \c{-o file}). + + Examples: + \snippet code/src_corelib_tools_qcommandlineoption.cpp 0 + + \sa QCommandLineParser +*/ + +/*! + Constructs a command line option object with the given arguments. + + The name of the option is set to \a name. + The name can be either short or long. If the name is one character in + length, it is considered a short name. Option names must not be empty, + must not start with a dash or a slash character, must not contain a \c{=} + and cannot be repeated. + + The description is set to \a description. It is customary to add a "." + at the end of the description. + + In addition, the \a valueName can be set if the option expects a value. + The default value for the option is set to \a defaultValue. + + \sa setDescription(), setValueName(), setDefaultValues() +*/ +QCommandLineOption::QCommandLineOption(const QString &name, const QString &description, + const QString &valueName, + const QString &defaultValue) + : d(new QCommandLineOptionPrivate) +{ + d->setNames(QStringList(name)); + setValueName(valueName); + setDescription(description); + setDefaultValue(defaultValue); +} + +/*! + Constructs a command line option object with the given arguments. + + This overload allows to set multiple names for the option, for instance + \c{o} and \c{output}. + + The names of the option are set to \a names. + The names can be either short or long. Any name in the list that is one + character in length is a short name. Option names must not be empty, + must not start with a dash or a slash character, must not contain a \c{=} + and cannot be repeated. + + The description is set to \a description. It is customary to add a "." + at the end of the description. + + In addition, the \a valueName can be set if the option expects a value. + The default value for the option is set to \a defaultValue. + + \sa setDescription(), setValueName(), setDefaultValues() +*/ +QCommandLineOption::QCommandLineOption(const QStringList &names, const QString &description, + const QString &valueName, + const QString &defaultValue) + : d(new QCommandLineOptionPrivate) +{ + d->setNames(names); + setValueName(valueName); + setDescription(description); + setDefaultValue(defaultValue); +} + +/*! + Constructs a QCommandLineOption object that is a copy of the QCommandLineOption + object \a other. + + \sa operator=() +*/ +QCommandLineOption::QCommandLineOption(const QCommandLineOption &other) + : d(other.d) +{ +} + +/*! + Destroys the command line option object. +*/ +QCommandLineOption::~QCommandLineOption() +{ +} + +/*! + Makes a copy of the \a other object and assigns it to this QCommandLineOption + object. +*/ +QCommandLineOption &QCommandLineOption::operator=(const QCommandLineOption &other) +{ + d = other.d; + return *this; +} + +/*! + Returns the names set for this option. + */ +QStringList QCommandLineOption::names() const +{ + return d->names; +} + +void QCommandLineOptionPrivate::setNames(const QStringList &nameList) +{ + QStringList newNames; + newNames.reserve(nameList.size()); + if (nameList.isEmpty()) + qWarning("QCommandLineOption: Options must have at least one name"); + Q_FOREACH (const QString &name, nameList) { + if (name.isEmpty()) { + qWarning("QCommandLineOption: Option names cannot be empty"); + } else { + const QChar c = name.at(0); + if (c == QLatin1Char('-')) + qWarning("QCommandLineOption: Option names cannot start with a '-'"); + else if (c == QLatin1Char('/')) + qWarning("QCommandLineOption: Option names cannot start with a '/'"); + else if (name.contains(QLatin1Char('='))) + qWarning("QCommandLineOption: Option names cannot contain a '='"); + else + newNames.append(name); + } + } + // commit + names.swap(newNames); +} + +/*! + Sets the name of the expected value, for the documentation, to \a valueName. + + Options without a value assigned have a boolean-like behavior: + either the user specifies --option or they don't. + + Options with a value assigned need to set a name for the expected value, + for the documentation of the option in the help output. An option with names \c{o} and \c{output}, + and a value name of \c{file} will appear as \c{-o, --output }. + + Call QCommandLineParser::argument() if you expect the option to be present + only once, and QCommandLineParser::arguments() if you expect that option + to be present multiple times. + + \sa valueName() + */ +void QCommandLineOption::setValueName(const QString &valueName) +{ + d->valueName = valueName; +} + +/*! + Returns the name of the expected value. + + If empty, the option doesn't take a value. + + \sa setValueName() + */ +QString QCommandLineOption::valueName() const +{ + return d->valueName; +} + +/*! + Sets the description used for this option to \a description. + + It is customary to add a "." at the end of the description. + + The description is used by QCommandLineParser::showHelp(). + + \sa description() + */ +void QCommandLineOption::setDescription(const QString &description) +{ + d->description = description; +} + +/*! + Returns the description set for this option. + + \sa setDescription() + */ +QString QCommandLineOption::description() const +{ + return d->description; +} + +/*! + Sets the default value used for this option to \a defaultValue. + + The default value is used if the user of the application does not specify + the option on the command line. + + If \a defaultValue is empty, the option has no default values. + + \sa defaultValues() setDefaultValues() + */ +void QCommandLineOption::setDefaultValue(const QString &defaultValue) +{ + QStringList newDefaultValues; + if (!defaultValue.isEmpty()) { + newDefaultValues.reserve(1); + newDefaultValues << defaultValue; + } + // commit: + d->defaultValues.swap(newDefaultValues); +} + +/*! + Sets the list of default values used for this option to \a defaultValues. + + The default values are used if the user of the application does not specify + the option on the command line. + + \sa defaultValues() setDefaultValue() + */ +void QCommandLineOption::setDefaultValues(const QStringList &defaultValues) +{ + d->defaultValues = defaultValues; +} + +/*! + Returns the default values set for this option. + + \sa setDefaultValues() + */ +QStringList QCommandLineOption::defaultValues() const +{ + return d->defaultValues; +} diff --git a/src/core/qcommandlineoption.h b/src/core/qcommandlineoption.h new file mode 100644 index 000000000..b97d44c33 --- /dev/null +++ b/src/core/qcommandlineoption.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Laszlo Papp +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOMMANDLINEOPTION_H +#define QCOMMANDLINEOPTION_H + +#include +#include + +class QCommandLineOptionPrivate; + +class QCommandLineOption +{ +public: + explicit QCommandLineOption(const QString &name, const QString &description = QString(), + const QString &valueName = QString(), + const QString &defaultValue = QString()); + explicit QCommandLineOption(const QStringList &names, const QString &description = QString(), + const QString &valueName = QString(), + const QString &defaultValue = QString()); + QCommandLineOption(const QCommandLineOption &other); + + ~QCommandLineOption(); + + QCommandLineOption &operator=(const QCommandLineOption &other); + + QStringList names() const; + + void setValueName(const QString &name); + QString valueName() const; + + void setDescription(const QString &description); + QString description() const; + + void setDefaultValue(const QString &defaultValue); + void setDefaultValues(const QStringList &defaultValues); + QStringList defaultValues() const; + +private: + QSharedDataPointer d; +}; + +#endif // QCOMMANDLINEOPTION_H diff --git a/src/core/qcommandlineparser.cpp b/src/core/qcommandlineparser.cpp new file mode 100644 index 000000000..f97d78a3d --- /dev/null +++ b/src/core/qcommandlineparser.cpp @@ -0,0 +1,944 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Laszlo Papp +** Copyright (C) 2013 David Faure +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcommandlineparser.h" + +#include +#include +#include +#include +#include + +typedef QHash NameHash_t; + +class QCommandLineParserPrivate +{ +public: + inline QCommandLineParserPrivate() + : singleDashWordOptionMode(QCommandLineParser::ParseAsCompactedShortOptions), + builtinVersionOption(false), + builtinHelpOption(false), + needsParsing(true) + { } + + bool parse(const QStringList &args); + void checkParsed(const char *method); + QStringList aliases(const QString &name) const; + QString helpText() const; + bool registerFoundOption(const QString &optionName); + bool parseOptionValue(const QString &optionName, const QString &argument, + QStringList::const_iterator *argumentIterator, + QStringList::const_iterator argsEnd); + + //! Error text set when parse() returns false + QString errorText; + + //! The command line options used for parsing + QList commandLineOptionList; + + //! Hash mapping option names to their offsets in commandLineOptionList and optionArgumentList. + NameHash_t nameHash; + + //! Option values found (only for options with a value) + QHash optionValuesHash; + + //! Names of options found on the command line. + QStringList optionNames; + + //! Arguments which did not belong to any option. + QStringList positionalArgumentList; + + //! Names of options which were unknown. + QStringList unknownOptionNames; + + //! Application description + QString description; + + //! Documentation for positional arguments + struct PositionalArgumentDefinition + { + QString name; + QString description; + QString syntax; + }; + QVector positionalArgumentDefinitions; + + //! The parsing mode for "-abc" + QCommandLineParser::SingleDashWordOptionMode singleDashWordOptionMode; + + //! Whether addVersionOption was called + bool builtinVersionOption; + + //! Whether addHelpOption was called + bool builtinHelpOption; + + //! True if parse() needs to be called + bool needsParsing; +}; + +QStringList QCommandLineParserPrivate::aliases(const QString &optionName) const +{ + const NameHash_t::const_iterator it = nameHash.find(optionName); + if (it == nameHash.end()) { + qWarning("QCommandLineParser: option not defined: \"%s\"", qPrintable(optionName)); + return QStringList(); + } + return commandLineOptionList.at(*it).names(); +} + +/*! + \since 5.2 + \class QCommandLineParser + \inmodule QtCore + \ingroup tools + + \brief The QCommandLineParser class provides a means for handling the + command line options. + + QCoreApplication provides the command-line arguments as a simple list of strings. + QCommandLineParser provides the ability to define a set of options, parse the + command-line arguments, and store which options have actually been used, as + well as option values. + + Any argument that isn't an option (i.e. doesn't start with a \c{-}) is stored + as a "positional argument". + + The parser handles short names, long names, more than one name for the same + option, and option values. + + Options on the command line are recognized as starting with a single or + double \c{-} character(s). + The option \c{-} (single dash alone) is a special case, often meaning standard + input, and not treated as an option. The parser will treat everything after the + option \c{--} (double dash) as positional arguments. + + Short options are single letters. The option \c{v} would be specified by + passing \c{-v} on the command line. In the default parsing mode, short options + can be written in a compact form, for instance \c{-abc} is equivalent to \c{-a -b -c}. + The parsing mode for can be set to ParseAsLongOptions, in which case \c{-abc} + will be parsed as the long option \c{abc}. + + Long options are more than one letter long and cannot be compacted together. + The long option \c{verbose} would be passed as \c{--verbose} or \c{-verbose}. + + Passing values to options can be done using the assignment operator: \c{-v=value} + \c{--verbose=value}, or a space: \c{-v value} \c{--verbose value}, i.e. the next + argument is used as value (even if it starts with a \c{-}). + + The parser does not support optional values - if an option is set to + require a value, one must be present. If such an option is placed last + and has no value, the option will be treated as if it had not been + specified. + + The parser does not automatically support negating or disabling long options + by using the format \c{--disable-option} or \c{--no-option}. However, it is + possible to handle this case explicitly by making an option with \c{no-option} + as one of its names, and handling the option explicitly. + + Example: + \snippet code/src_corelib_tools_qcommandlineparser_main.cpp 0 + + Known limitation: the parsing of Qt options inside QCoreApplication and subclasses + happens before QCommandLineParser exists, so it can't take it into account. This + means any option value that looks like a builtin Qt option, will be treated by + QCoreApplication as a builtin Qt option. Example: \c{--profile -reverse} will + lead to QGuiApplication seeing the -reverse option set, and removing it from + QCoreApplication::arguments() before QCommandLineParser defines the \c{profile} + option and parses the command line. + + \sa QCommandLineOption, QCoreApplication +*/ + +/*! + Constructs a command line parser object. +*/ +QCommandLineParser::QCommandLineParser() + : d(new QCommandLineParserPrivate) +{ +} + +/*! + Destroys the command line parser object. +*/ +QCommandLineParser::~QCommandLineParser() +{ + delete d; +} + +/*! + \enum QCommandLineParser::SingleDashWordOptionMode + + This enum describes the way the parser interprets command-line + options that use a single dash followed by multiple letters, as as \c{-abc}. + + \value ParseAsCompactedShortOptions \c{-abc} is interpreted as \c{-a -b -c}, + i.e. as three short options that have been compacted on the command-line, + if none of the options take a value. If \c{a} takes a value, then it + is interpreted as \c{-a bc}, i.e. the short option \c{a} followed by the value \c{bc}. + This is typically used in tools that behave like compilers, in order + to handle options such as \c{-DDEFINE=VALUE} or \c{-I/include/path}. + This is the default parsing mode. New applications are recommended to + use this mode. + + \value ParseAsLongOptions \c{-abc} is interpreted as \c{--abc}, + i.e. as the long option named \c{abc}. This is how Qt's own tools + (uic, rcc...) have always been parsing arguments. This mode should be + used for preserving compatibility in applications that were parsing + arguments in such a way. + + \sa setSingleDashWordOptionMode() +*/ + +/*! + Sets the parsing mode to \a singleDashWordOptionMode. + This must be called before process() or parse(). +*/ +void QCommandLineParser::setSingleDashWordOptionMode(QCommandLineParser::SingleDashWordOptionMode singleDashWordOptionMode) +{ + d->singleDashWordOptionMode = singleDashWordOptionMode; +} + +/*! + Adds the option \a option to look for while parsing. + + Returns \c true if adding the option was successful; otherwise returns \c false. + + Adding the option fails if there is no name attached to the option, or + the option has a name that clashes with an option name added before. + */ +bool QCommandLineParser::addOption(const QCommandLineOption &option) +{ + QStringList optionNames = option.names(); + + if (!optionNames.isEmpty()) { + Q_FOREACH (const QString &name, optionNames) { + if (d->nameHash.contains(name)) + return false; + } + + d->commandLineOptionList.append(option); + + const int offset = d->commandLineOptionList.size() - 1; + Q_FOREACH (const QString &name, optionNames) + d->nameHash.insert(name, offset); + + return true; + } + + return false; +} + +/*! + Adds the \c{-v} / \c{--version} option, which displays the version string of the application. + + This option is handled automatically by QCommandLineParser. + + You can set the actual version string by using QCoreApplication::setApplicationVersion(). + + Returns the option instance, which can be used to call isSet(). +*/ +QCommandLineOption QCommandLineParser::addVersionOption() +{ + QCommandLineOption opt(QStringList() << "v" << "version", tr("Displays version information.")); + addOption(opt); + d->builtinVersionOption = true; + return opt; +} + +/*! + Adds the help option (\c{-h}, \c{--help} and \c{-?} on Windows) + This option is handled automatically by QCommandLineParser. + + Remember to use setApplicationDescription to set the application description, + which will be displayed when this option is used. + + Example: + \snippet code/src_corelib_tools_qcommandlineparser_main.cpp 0 + + Returns the option instance, which can be used to call isSet(). +*/ +QCommandLineOption QCommandLineParser::addHelpOption() +{ + QCommandLineOption opt(QStringList() +#ifdef Q_OS_WIN + << "?" +#endif + << "h" + << "help", tr("Displays this help.")); + addOption(opt); + d->builtinHelpOption = true; + return opt; +} + +/*! + Sets the application \a description shown by helpText(). +*/ +void QCommandLineParser::setApplicationDescription(const QString &description) +{ + d->description = description; +} + +/*! + Returns the application description set in setApplicationDescription(). +*/ +QString QCommandLineParser::applicationDescription() const +{ + return d->description; +} + +/*! + Defines an additional argument to the application, for the benefit of the help text. + + The argument \a name and \a description will appear under the \c{Arguments:} section + of the help. If \a syntax is specified, it will be appended to the Usage line, otherwise + the \a name will be appended. + + Example: + \snippet code/src_corelib_tools_qcommandlineparser.cpp 2 + + \sa addHelpOption(), helpText() +*/ +void QCommandLineParser::addPositionalArgument(const QString &name, const QString &description, const QString &syntax) +{ + QCommandLineParserPrivate::PositionalArgumentDefinition arg; + arg.name = name; + arg.description = description; + arg.syntax = syntax.isEmpty() ? name : syntax; + d->positionalArgumentDefinitions.append(arg); +} + +/*! + Clears the definitions of additional arguments from the help text. + + This is only needed for the special case of tools which support multiple commands + with different options. Once the actual command has been identified, the options + for this command can be defined, and the help text for the command can be adjusted + accordingly. + + Example: + \snippet code/src_corelib_tools_qcommandlineparser.cpp 3 +*/ +void QCommandLineParser::clearPositionalArguments() +{ + d->positionalArgumentDefinitions.clear(); +} + +/*! + Parses the command line \a arguments. + + Most programs don't need to call this, a simple call to process() is enough. + + parse() is more low-level, and only does the parsing. The application will have to + take care of the error handling, using errorText() if parse() returns \c false. + This can be useful for instance to show a graphical error message in graphical programs. + + Calling parse() instead of process() can also be useful in order to ignore unknown + options temporarily, because more option definitions will be provided later on + (depending on one of the arguments), before calling process(). + + Don't forget that \a arguments must start with the name of the executable (ignored, though). + + Returns \c false in case of a parse error (unknown option or missing value); returns \c true otherwise. + + \sa process() +*/ +bool QCommandLineParser::parse(const QStringList &arguments) +{ + return d->parse(arguments); +} + +/*! + Returns a translated error text for the user. + This should only be called when parse() returns \c false. +*/ +QString QCommandLineParser::errorText() const +{ + if (!d->errorText.isEmpty()) + return d->errorText; + if (d->unknownOptionNames.count() == 1) + return tr("Unknown option '%1'.").arg(d->unknownOptionNames.first()); + if (d->unknownOptionNames.count() > 1) + return tr("Unknown options: %1.").arg(d->unknownOptionNames.join(", ")); + return QString(); +} + +/*! + Processes the command line \a arguments. + + In addition to parsing the options (like parse()), this function also handles the builtin + options and handles errors. + + The builtin options are \c{--version} if addVersionOption was called and \c{--help} if addHelpOption was called. + + When invoking one of these options, or when an error happens (for instance an unknown option was + passed), the current process will then stop, using the exit() function. + + \sa QCoreApplication::arguments(), parse() + */ +void QCommandLineParser::process(const QStringList &arguments) +{ + if (!d->parse(arguments)) { + fprintf(stderr, "%s\n", qPrintable(errorText())); + ::exit(EXIT_FAILURE); + } + + if (d->builtinVersionOption && isSet("version")) { + printf("%s %s\n", qPrintable(QCoreApplication::applicationName()), qPrintable(QCoreApplication::applicationVersion())); + ::exit(EXIT_SUCCESS); + } + + if (d->builtinHelpOption && isSet("help")) + showHelp(EXIT_SUCCESS); +} + +/*! + \overload + + The command line is obtained from the QCoreApplication instance \a app. + */ +void QCommandLineParser::process(const QCoreApplication &app) +{ + // QCoreApplication::arguments() is static, but the app instance must exist so we require it as parameter + Q_UNUSED(app); + process(QCoreApplication::arguments()); +} + +void QCommandLineParserPrivate::checkParsed(const char *method) +{ + if (needsParsing) + qWarning("QCommandLineParser: call process() or parse() before %s", method); +} + +/*! + \internal + Looks up the option \a optionName (found on the command line) and register it as found. + Returns \c true on success. + */ +bool QCommandLineParserPrivate::registerFoundOption(const QString &optionName) +{ + if (nameHash.contains(optionName)) { + optionNames.append(optionName); + return true; + } else { + unknownOptionNames.append(optionName); + return false; + } +} + +/*! + \internal + \brief Parse the value for a given option, if it was defined to expect one. + + The value is taken from the next argument, or after the equal sign in \a argument. + + \param optionName the short option name + \param argument the argument from the command line currently parsed. Only used for -k=value parsing. + \param argumentIterator iterator to the currently parsed argument. Incremented if the next argument contains the value. + \param argsEnd args.end(), to check if ++argumentIterator goes out of bounds + Returns \c true on success. + */ +bool QCommandLineParserPrivate::parseOptionValue(const QString &optionName, const QString &argument, + QStringList::const_iterator *argumentIterator, QStringList::const_iterator argsEnd) +{ + const QLatin1Char assignChar('='); + const NameHash_t::const_iterator nameHashIt = nameHash.constFind(optionName); + if (nameHashIt != nameHash.constEnd()) { + const int assignPos = argument.indexOf(assignChar); + const NameHash_t::mapped_type optionOffset = *nameHashIt; + const bool withValue = !commandLineOptionList.at(optionOffset).valueName().isEmpty(); + if (withValue) { + if (assignPos == -1) { + ++(*argumentIterator); + if (*argumentIterator == argsEnd) { + errorText = QCommandLineParser::tr("Missing value after '%1'.").arg(argument); + return false; + } + optionValuesHash[optionOffset].append(*(*argumentIterator)); + } else { + optionValuesHash[optionOffset].append(argument.mid(assignPos + 1)); + } + } else { + if (assignPos != -1) { + errorText = QCommandLineParser::tr("Unexpected value after '%1'.").arg(argument.left(assignPos)); + return false; + } + } + } + return true; +} + +/*! + \internal + + Parse the list of arguments \a args, and fills in + optionNames, optionValuesHash, unknownOptionNames, positionalArguments, and errorText. + + Any results from a previous parse operation are removed. + + The parser will not look for further options once it encounters the option + \c{--}; this does not include when \c{--} follows an option that requires a value. + */ +bool QCommandLineParserPrivate::parse(const QStringList &args) +{ + needsParsing = false; + bool error = false; + + const QString doubleDashString("--"); + const QLatin1Char dashChar('-'); + const QLatin1Char assignChar('='); + + bool doubleDashFound = false; + errorText.clear(); + positionalArgumentList.clear(); + optionNames.clear(); + unknownOptionNames.clear(); + optionValuesHash.clear(); + + if (args.isEmpty()) { + qWarning("QCommandLineParser: argument list cannot be empty, it should contain at least the executable name"); + return false; + } + + QStringList::const_iterator argumentIterator = args.begin(); + ++argumentIterator; // skip executable name + + for (; argumentIterator != args.end() ; ++argumentIterator) { + QString argument = *argumentIterator; + + if (doubleDashFound) { + positionalArgumentList.append(argument); + } else if (argument.startsWith(doubleDashString)) { + if (argument.length() > 2) { + QString optionName = argument.mid(2).section(assignChar, 0, 0); + if (registerFoundOption(optionName)) { + if (!parseOptionValue(optionName, argument, &argumentIterator, args.end())) + error = true; + } else { + error = true; + } + } else { + doubleDashFound = true; + } + } else if (argument.startsWith(dashChar)) { + if (argument.size() == 1) { // single dash ("stdin") + positionalArgumentList.append(argument); + continue; + } + switch (singleDashWordOptionMode) { + case QCommandLineParser::ParseAsCompactedShortOptions: + { + QString optionName; + bool valueFound = false; + for (int pos = 1 ; pos < argument.size(); ++pos) { + optionName = argument.mid(pos, 1); + if (!registerFoundOption(optionName)) { + error = true; + } else { + const NameHash_t::const_iterator nameHashIt = nameHash.constFind(optionName); + Q_ASSERT(nameHashIt != nameHash.constEnd()); // checked by registerFoundOption + const NameHash_t::mapped_type optionOffset = *nameHashIt; + const bool withValue = !commandLineOptionList.at(optionOffset).valueName().isEmpty(); + if (withValue) { + if (pos + 1 < argument.size()) { + if (argument.at(pos + 1) == assignChar) + ++pos; + optionValuesHash[optionOffset].append(argument.mid(pos + 1)); + valueFound = true; + } + break; + } + if (pos + 1 < argument.size() && argument.at(pos + 1) == assignChar) + break; + } + } + if (!valueFound && !parseOptionValue(optionName, argument, &argumentIterator, args.end())) + error = true; + break; + } + case QCommandLineParser::ParseAsLongOptions: + { + const QString optionName = argument.mid(1).section(assignChar, 0, 0); + if (registerFoundOption(optionName)) { + if (!parseOptionValue(optionName, argument, &argumentIterator, args.end())) + error = true; + } else { + error = true; + } + break; + } + } + } else { + positionalArgumentList.append(argument); + } + if (argumentIterator == args.end()) + break; + } + return !error; +} + +/*! + Checks whether the option \a name was passed to the application. + + Returns \c true if the option \a name was set, false otherwise. + + The name provided can be any long or short name of any option that was + added with \c addOption(). All the options names are treated as being + equivalent. If the name is not recognized or that option was not present, + false is returned. + + Example: + \snippet code/src_corelib_tools_qcommandlineparser.cpp 0 + */ + +bool QCommandLineParser::isSet(const QString &name) const +{ + d->checkParsed("isSet"); + if (d->optionNames.contains(name)) + return true; + const QStringList aliases = d->aliases(name); + Q_FOREACH (const QString &optionName, d->optionNames) { + if (aliases.contains(optionName)) + return true; + } + return false; +} + +/*! + Returns the option value found for the given option name \a optionName, or + an empty string if not found. + + The name provided can be any long or short name of any option that was + added with \c addOption(). All the option names are treated as being + equivalent. If the name is not recognized or that option was not present, an + empty string is returned. + + For options found by the parser, the last value found for + that option is returned. If the option wasn't specified on the command line, + the default value is returned. + + An empty string is returned if the option does not take a value. + + \sa values(), QCommandLineOption::setDefaultValue(), QCommandLineOption::setDefaultValues() + */ + +QString QCommandLineParser::value(const QString &optionName) const +{ + d->checkParsed("value"); + const QStringList valueList = values(optionName); + + if (!valueList.isEmpty()) + return valueList.last(); + + return QString(); +} + +/*! + Returns a list of option values found for the given option name \a + optionName, or an empty list if not found. + + The name provided can be any long or short name of any option that was + added with \c addOption(). All the options names are treated as being + equivalent. If the name is not recognized or that option was not present, an + empty list is returned. + + For options found by the parser, the list will contain an entry for + each time the option was encountered by the parser. If the option wasn't + specified on the command line, the default values are returned. + + An empty list is returned if the option does not take a value. + + \sa value(), QCommandLineOption::setDefaultValue(), QCommandLineOption::setDefaultValues() + */ + +QStringList QCommandLineParser::values(const QString &optionName) const +{ + d->checkParsed("values"); + const NameHash_t::const_iterator it = d->nameHash.constFind(optionName); + if (it != d->nameHash.constEnd()) { + const int optionOffset = *it; + QStringList values = d->optionValuesHash.value(optionOffset); + if (values.isEmpty()) + values = d->commandLineOptionList.at(optionOffset).defaultValues(); + return values; + } + + qWarning("QCommandLineParser: option not defined: \"%s\"", qPrintable(optionName)); + return QStringList(); +} + +/*! + \overload + Checks whether the \a option was passed to the application. + + Returns \c true if the \a option was set, false otherwise. + + This is the recommended way to check for options with no values. + + Example: + \snippet code/src_corelib_tools_qcommandlineparser.cpp 1 +*/ +bool QCommandLineParser::isSet(const QCommandLineOption &option) const +{ + // option.names() might be empty if the constructor failed + return !option.names().isEmpty() && isSet(option.names().first()); +} + +/*! + \overload + Returns the option value found for the given \a option, or + an empty string if not found. + + For options found by the parser, the last value found for + that option is returned. If the option wasn't specified on the command line, + the default value is returned. + + An empty string is returned if the option does not take a value. + + \sa values(), QCommandLineOption::setDefaultValue(), QCommandLineOption::setDefaultValues() +*/ +QString QCommandLineParser::value(const QCommandLineOption &option) const +{ + return value(option.names().first()); +} + +/*! + \overload + Returns a list of option values found for the given \a option, + or an empty list if not found. + + For options found by the parser, the list will contain an entry for + each time the option was encountered by the parser. If the option wasn't + specified on the command line, the default values are returned. + + An empty list is returned if the option does not take a value. + + \sa value(), QCommandLineOption::setDefaultValue(), QCommandLineOption::setDefaultValues() +*/ +QStringList QCommandLineParser::values(const QCommandLineOption &option) const +{ + return values(option.names().first()); +} + +/*! + Returns a list of positional arguments. + + These are all of the arguments that were not recognized as part of an + option. + */ + +QStringList QCommandLineParser::positionalArguments() const +{ + d->checkParsed("positionalArguments"); + return d->positionalArgumentList; +} + +/*! + Returns a list of option names that were found. + + This returns a list of all the recognized option names found by the + parser, in the order in which they were found. For any long options + that were in the form {--option=value}, the value part will have been + dropped. + + The names in this list do not include the preceding dash characters. + Names may appear more than once in this list if they were encountered + more than once by the parser. + + Any entry in the list can be used with \c value() or with + \c values() to get any relevant option values. + */ + +QStringList QCommandLineParser::optionNames() const +{ + d->checkParsed("optionNames"); + return d->optionNames; +} + +/*! + Returns a list of unknown option names. + + This list will include both long an short name options that were not + recognized. For any long options that were in the form {--option=value}, + the value part will have been dropped and only the long name is added. + + The names in this list do not include the preceding dash characters. + Names may appear more than once in this list if they were encountered + more than once by the parser. + + \sa optionNames() + */ + +QStringList QCommandLineParser::unknownOptionNames() const +{ + d->checkParsed("unknownOptionNames"); + return d->unknownOptionNames; +} + +/*! + Displays the help information, and exits the application. + This is automatically triggered by the --help option, but can also + be used to display the help when the user is not invoking the + application correctly. + The exit code is set to \a exitCode. It should be set to 0 if the + user requested to see the help, and to any other value in case of + an error. + + \sa helpText() +*/ +void QCommandLineParser::showHelp(int exitCode) +{ + fprintf(stdout, "%s", qPrintable(d->helpText())); + ::exit(exitCode); +} + +/*! + Returns a string containing the complete help information. + + \sa showHelp() +*/ +QString QCommandLineParser::helpText() const +{ + return d->helpText(); +} + +static QString wrapText(const QString &names, int longestOptionNameString, const QString &description) +{ + const QLatin1Char nl('\n'); + QString text = QString(" ") + names.leftJustified(longestOptionNameString) + QLatin1Char(' '); + const int indent = text.length(); + int lineStart = 0; + int lastBreakable = -1; + const int max = 79 - indent; + int x = 0; + const int len = description.length(); + + for (int i = 0; i < len; ++i) { + ++x; + const QChar c = description.at(i); + if (c.isSpace()) + lastBreakable = i; + + int breakAt = -1; + int nextLineStart = -1; + if (x > max && lastBreakable != -1) { + // time to break and we know where + breakAt = lastBreakable; + nextLineStart = lastBreakable + 1; + } else if ((x > max - 1 && lastBreakable == -1) || i == len - 1) { + // time to break but found nowhere [-> break here], or end of last line + breakAt = i + 1; + nextLineStart = breakAt; + } else if (c == nl) { + // forced break + breakAt = i; + nextLineStart = i + 1; + } + + if (breakAt != -1) { + const int numChars = breakAt - lineStart; + //qDebug() << "breakAt=" << description.at(breakAt) << "breakAtSpace=" << breakAtSpace << lineStart << "to" << breakAt << description.mid(lineStart, numChars); + if (lineStart > 0) + text += QString(indent, QLatin1Char(' ')); + text += description.mid(lineStart, numChars) + nl; + x = 0; + lastBreakable = -1; + lineStart = nextLineStart; + if (lineStart < len && description.at(lineStart).isSpace()) + ++lineStart; // don't start a line with a space + i = lineStart; + } + } + + return text; +} + +QString QCommandLineParserPrivate::helpText() const +{ + const QLatin1Char nl('\n'); + QString text; + const QString exeName = QCoreApplication::instance()->arguments().first(); + QString usage = exeName; + if (!commandLineOptionList.isEmpty()) { + usage += QLatin1Char(' '); + usage += QCommandLineParser::tr("[options]"); + } + Q_FOREACH (const PositionalArgumentDefinition &arg, positionalArgumentDefinitions) { + usage += QLatin1Char(' '); + usage += arg.syntax; + } + text += QCommandLineParser::tr("Usage: %1").arg(usage) + nl; + if (!description.isEmpty()) + text += description + nl; + text += nl; + if (!commandLineOptionList.isEmpty()) + text += QCommandLineParser::tr("Options:") + nl; + QStringList optionNameList; + int longestOptionNameString = 0; + Q_FOREACH (const QCommandLineOption &option, commandLineOptionList) { + QStringList optionNames; + Q_FOREACH (const QString &optionName, option.names()) { + if (optionName.length() == 1) + optionNames.append(QLatin1Char('-') + optionName); + else + optionNames.append(QString("--") + optionName); + } + QString optionNamesString = optionNames.join(", "); + if (!option.valueName().isEmpty()) + optionNamesString += QString(" <") + option.valueName() + QLatin1Char('>'); + optionNameList.append(optionNamesString); + longestOptionNameString = qMax(longestOptionNameString, optionNamesString.length()); + } + ++longestOptionNameString; + for (int i = 0; i < commandLineOptionList.count(); ++i) { + const QCommandLineOption &option = commandLineOptionList.at(i); + text += wrapText(optionNameList.at(i), longestOptionNameString, option.description()); + } + if (!positionalArgumentDefinitions.isEmpty()) { + if (!commandLineOptionList.isEmpty()) + text += nl; + text += QCommandLineParser::tr("Arguments:") + nl; + Q_FOREACH (const PositionalArgumentDefinition &arg, positionalArgumentDefinitions) { + text += wrapText(arg.name, longestOptionNameString, arg.description); + } + } + return text; +} diff --git a/src/core/qcommandlineparser.h b/src/core/qcommandlineparser.h new file mode 100644 index 000000000..eeecf2daa --- /dev/null +++ b/src/core/qcommandlineparser.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Laszlo Papp +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOMMANDLINEPARSER_H +#define QCOMMANDLINEPARSER_H + +#include +#include + +#include "qcommandlineoption.h" + +class QCommandLineParserPrivate; +class QCoreApplication; + +class QCommandLineParser +{ + Q_DECLARE_TR_FUNCTIONS(QCommandLineParser) +public: + QCommandLineParser(); + ~QCommandLineParser(); + + enum SingleDashWordOptionMode { + ParseAsCompactedShortOptions, + ParseAsLongOptions + }; + void setSingleDashWordOptionMode(SingleDashWordOptionMode parsingMode); + + bool addOption(const QCommandLineOption &commandLineOption); + + QCommandLineOption addVersionOption(); + QCommandLineOption addHelpOption(); + void setApplicationDescription(const QString &description); + QString applicationDescription() const; + void addPositionalArgument(const QString &name, const QString &description, const QString &syntax = QString()); + void clearPositionalArguments(); + + void process(const QStringList &arguments); + void process(const QCoreApplication &app); + + bool parse(const QStringList &arguments); + QString errorText() const; + + bool isSet(const QString &name) const; + QString value(const QString &name) const; + QStringList values(const QString &name) const; + + bool isSet(const QCommandLineOption &option) const; + QString value(const QCommandLineOption &option) const; + QStringList values(const QCommandLineOption &option) const; + + QStringList positionalArguments() const; + QStringList optionNames() const; + QStringList unknownOptionNames() const; + + void showHelp(int exitCode = 0); + QString helpText() const; + +private: + Q_DISABLE_COPY(QCommandLineParser) + + QCommandLineParserPrivate * const d; +}; + +#endif // QCOMMANDLINEPARSER_H diff --git a/src/main.cpp b/src/main.cpp index 786546c71..c00e8f7ad 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,8 +17,8 @@ #include -#include "core/ArgumentParser.h" #include "core/Config.h" +#include "core/qcommandlineparser.h" #include "core/Tools.h" #include "crypto/Crypto.h" #include "gui/Application.h" @@ -36,11 +36,30 @@ int main(int argc, char** argv) Crypto::init(); - const QStringList args = app.arguments(); - QHash argumentMap = ArgumentParser::parseArguments(args); + QCommandLineParser parser; + parser.setApplicationDescription(QCoreApplication::translate("main", "KeePassX - cross-platform password manager")); + parser.addPositionalArgument("filename", QCoreApplication::translate("main", "filename of the password database to open (*.kdbx)")); - if (!argumentMap.value("config").isEmpty()) { - Config::createConfigFromFile(argumentMap.value("config")); + QCommandLineOption configOption("config", + QCoreApplication::translate("main", "path to a custom config file"), + "config"); + QCommandLineOption passwordOption("password", + QCoreApplication::translate("main", "password of the database (DANGEROUS!)"), + "password"); + QCommandLineOption keyfileOption("keyfile", + QCoreApplication::translate("main", "key file of the database"), + "keyfile"); + + parser.addHelpOption(); + parser.addOption(configOption); + parser.addOption(passwordOption); + parser.addOption(keyfileOption); + + parser.process(app); + const QStringList args = parser.positionalArguments(); + + if (parser.isSet(configOption)) { + Config::createConfigFromFile(parser.value(configOption)); } #ifdef Q_OS_MAC @@ -53,9 +72,11 @@ int main(int argc, char** argv) QObject::connect(&app, SIGNAL(openFile(QString)), &mainWindow, SLOT(openDatabase(QString))); - QString filename(argumentMap.value("filename")); - if (!filename.isEmpty() && QFile::exists(filename)) { - mainWindow.openDatabase(filename, argumentMap.value("password"), QString()); + if (!args.isEmpty()) { + QString filename = args[0]; + if (!filename.isEmpty() && QFile::exists(filename)) { + mainWindow.openDatabase(filename, parser.value(passwordOption), parser.value(keyfileOption)); + } } if (config()->get("OpenPreviousDatabasesOnStartup").toBool()) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6ff137d59..8df0050aa 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -159,7 +159,7 @@ set_target_properties(testautotype PROPERTIES ENABLE_EXPORTS ON) add_unit_test(NAME testentry SOURCES TestEntry.cpp MOCS TestEntry.h LIBS ${TEST_LIBRARIES}) -add_unit_test(NAME testargumentparser SOURCES TestArgumentParser.cpp MOCS TestArgumentParser.h +add_unit_test(NAME testqcommandlineparser SOURCES TestQCommandLineParser.cpp MOCS TestQCommandLineParser.h LIBS ${TEST_LIBRARIES}) add_unit_test(NAME testrandom SOURCES TestRandom.cpp MOCS TestRandom.h diff --git a/tests/TestQCommandLineParser.cpp b/tests/TestQCommandLineParser.cpp new file mode 100644 index 000000000..d487862c0 --- /dev/null +++ b/tests/TestQCommandLineParser.cpp @@ -0,0 +1,416 @@ +/**************************************************************************** +** +** Copyright (C) 2013 David Faure +** Contact: http://www.qt-project.org/legal +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "TestQCommandLineParser.h" + +#include + +#include "tests.h" +#include "core/qcommandlineparser.h" + +Q_DECLARE_METATYPE(char**) + +static char *empty_argv[] = { 0 }; +static int empty_argc = 1; + +void TestQCommandLineParser::initTestCase() +{ + Q_ASSERT(!empty_argv[0]); + empty_argv[0] = const_cast("TestQCommandLineParser"); +} + +Q_DECLARE_METATYPE(QCommandLineParser::SingleDashWordOptionMode) + +void TestQCommandLineParser::parsingModes_data() +{ + QTest::addColumn("parsingMode"); + + QTest::newRow("collapsed") << QCommandLineParser::ParseAsCompactedShortOptions; + QTest::newRow("implicitlylong") << QCommandLineParser::ParseAsLongOptions; +} + +void TestQCommandLineParser::testInvalidOptions() +{ + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + QTest::ignoreMessage(QtWarningMsg, "QCommandLineOption: Option names cannot start with a '-'"); + parser.addOption(QCommandLineOption("-v", "Displays version information.")); +} + +void TestQCommandLineParser::testPositionalArguments() +{ + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + QVERIFY(parser.parse(QStringList() << "TestQCommandLineParser" << "file.txt")); + QCOMPARE(parser.positionalArguments(), QStringList() << "file.txt"); +} + +void TestQCommandLineParser::testBooleanOption_data() +{ + QTest::addColumn("args"); + QTest::addColumn("expectedOptionNames"); + QTest::addColumn("expectedIsSet"); + + QTest::newRow("set") << (QStringList() << "TestQCommandLineParser" << "-b") << (QStringList() << "b") << true; + QTest::newRow("unset") << (QStringList() << "TestQCommandLineParser") << QStringList() << false; +} + +void TestQCommandLineParser::testBooleanOption() +{ + QFETCH(QStringList, args); + QFETCH(QStringList, expectedOptionNames); + QFETCH(bool, expectedIsSet); + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + QVERIFY(parser.addOption(QCommandLineOption("b", "a boolean option"))); + QVERIFY(parser.parse(args)); + QCOMPARE(parser.optionNames(), expectedOptionNames); + QCOMPARE(parser.isSet("b"), expectedIsSet); + QCOMPARE(parser.values("b"), QStringList()); + QCOMPARE(parser.positionalArguments(), QStringList()); + // Should warn on typos + QTest::ignoreMessage(QtWarningMsg, "QCommandLineParser: option not defined: \"c\""); + QVERIFY(!parser.isSet("c")); +} + +void TestQCommandLineParser::testMultipleNames_data() +{ + QTest::addColumn("args"); + QTest::addColumn("expectedOptionNames"); + + QTest::newRow("short") << (QStringList() << "TestQCommandLineParser" << "-v") << (QStringList() << "v"); + QTest::newRow("long") << (QStringList() << "TestQCommandLineParser" << "--version") << (QStringList() << "version"); + QTest::newRow("not_set") << (QStringList() << "TestQCommandLineParser") << QStringList(); +} + +void TestQCommandLineParser::testMultipleNames() +{ + QFETCH(QStringList, args); + QFETCH(QStringList, expectedOptionNames); + QCoreApplication app(empty_argc, empty_argv); + QCommandLineOption option(QStringList() << "v" << "version", "Show version information"); + QCOMPARE(option.names(), QStringList() << "v" << "version"); + QCommandLineParser parser; + QVERIFY(parser.addOption(option)); + QVERIFY(parser.parse(args)); + QCOMPARE(parser.optionNames(), expectedOptionNames); + const bool expectedIsSet = !expectedOptionNames.isEmpty(); + QCOMPARE(parser.isSet("v"), expectedIsSet); + QCOMPARE(parser.isSet("version"), expectedIsSet); +} + +void TestQCommandLineParser::testSingleValueOption_data() +{ + QTest::addColumn("args"); + QTest::addColumn("defaults"); + QTest::addColumn("expectedIsSet"); + + QTest::newRow("short") << (QStringList() << "tst" << "-s" << "oxygen") << QStringList() << true; + QTest::newRow("long") << (QStringList() << "tst" << "--style" << "oxygen") << QStringList() << true; + QTest::newRow("longequal") << (QStringList() << "tst" << "--style=oxygen") << QStringList() << true; + QTest::newRow("default") << (QStringList() << "tst") << (QStringList() << "oxygen") << false; +} + +void TestQCommandLineParser::testSingleValueOption() +{ + QFETCH(QStringList, args); + QFETCH(QStringList, defaults); + QFETCH(bool, expectedIsSet); + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + QCommandLineOption option(QStringList() << "s" << "style", "style name", "styleName"); + option.setDefaultValues(defaults); + QVERIFY(parser.addOption(option)); + for (int mode = 0; mode < 2; ++mode) { + parser.setSingleDashWordOptionMode(QCommandLineParser::SingleDashWordOptionMode(mode)); + QVERIFY(parser.parse(args)); + QCOMPARE(parser.isSet("s"), expectedIsSet); + QCOMPARE(parser.isSet("style"), expectedIsSet); + QCOMPARE(parser.isSet(option), expectedIsSet); + QCOMPARE(parser.value("s"), QString("oxygen")); + QCOMPARE(parser.value("style"), QString("oxygen")); + QCOMPARE(parser.values("s"), QStringList() << "oxygen"); + QCOMPARE(parser.values("style"), QStringList() << "oxygen"); + QCOMPARE(parser.values(option), QStringList() << "oxygen"); + QCOMPARE(parser.positionalArguments(), QStringList()); + } + // Should warn on typos + QTest::ignoreMessage(QtWarningMsg, "QCommandLineParser: option not defined: \"c\""); + QVERIFY(parser.values("c").isEmpty()); +} + +void TestQCommandLineParser::testValueNotSet() +{ + QCoreApplication app(empty_argc, empty_argv); + // Not set, no default value + QCommandLineParser parser; + QCommandLineOption option(QStringList() << "s" << "style", "style name"); + option.setValueName("styleName"); + QVERIFY(parser.addOption(option)); + QVERIFY(parser.parse(QStringList() << "tst")); + QCOMPARE(parser.optionNames(), QStringList()); + QVERIFY(!parser.isSet("s")); + QVERIFY(!parser.isSet("style")); + QCOMPARE(parser.value("s"), QString()); + QCOMPARE(parser.value("style"), QString()); + QCOMPARE(parser.values("s"), QStringList()); + QCOMPARE(parser.values("style"), QStringList()); +} + +void TestQCommandLineParser::testMultipleValuesOption() +{ + QCoreApplication app(empty_argc, empty_argv); + QCommandLineOption option("param", "Pass parameter to the backend."); + option.setValueName("key=value"); + QCommandLineParser parser; + QVERIFY(parser.addOption(option)); + { + QVERIFY(parser.parse(QStringList() << "tst" << "--param" << "key1=value1")); + QVERIFY(parser.isSet("param")); + QCOMPARE(parser.values("param"), QStringList() << "key1=value1"); + QCOMPARE(parser.value("param"), QString("key1=value1")); + } + { + QVERIFY(parser.parse(QStringList() << "tst" << "--param" << "key1=value1" << "--param" << "key2=value2")); + QVERIFY(parser.isSet("param")); + QCOMPARE(parser.values("param"), QStringList() << "key1=value1" << "key2=value2"); + QCOMPARE(parser.value("param"), QString("key2=value2")); + } + + QString expected = + "Usage: TestQCommandLineParser [options]\n" + "\n" + "Options:\n" + " --param Pass parameter to the backend.\n"; + + const QString exeName = QCoreApplication::instance()->arguments().first(); // e.g. debug\tst_qcommandlineparser.exe on Windows + expected.replace("TestQCommandLineParser", exeName); + QCOMPARE(parser.helpText(), expected); +} + +void TestQCommandLineParser::testUnknownOptionErrorHandling_data() +{ + QTest::addColumn("parsingMode"); + QTest::addColumn("args"); + QTest::addColumn("expectedUnknownOptionNames"); + QTest::addColumn("expectedErrorText"); + + const QStringList args_hello = QStringList() << "TestQCommandLineParser" << "--hello"; + const QString error_hello("Unknown option 'hello'."); + QTest::newRow("unknown_name_collapsed") << QCommandLineParser::ParseAsCompactedShortOptions << args_hello << QStringList("hello") << error_hello; + QTest::newRow("unknown_name_long") << QCommandLineParser::ParseAsLongOptions << args_hello << QStringList("hello") << error_hello; + + const QStringList args_value = QStringList() << "TestQCommandLineParser" << "-b=1"; + QTest::newRow("bool_with_value_collapsed") << QCommandLineParser::ParseAsCompactedShortOptions << args_value << QStringList() << QString("Unexpected value after '-b'."); + QTest::newRow("bool_with_value_long") << QCommandLineParser::ParseAsLongOptions << args_value << QStringList() << QString("Unexpected value after '-b'."); + + const QStringList args_dash_long = QStringList() << "TestQCommandLineParser" << "-bool"; + const QString error_bool("Unknown options: o, o, l."); + QTest::newRow("unknown_name_long_collapsed") << QCommandLineParser::ParseAsCompactedShortOptions << args_dash_long << (QStringList() << "o" << "o" << "l") << error_bool; +} + +void TestQCommandLineParser::testUnknownOptionErrorHandling() +{ + QFETCH(QCommandLineParser::SingleDashWordOptionMode, parsingMode); + QFETCH(QStringList, args); + QFETCH(QStringList, expectedUnknownOptionNames); + QFETCH(QString, expectedErrorText); + + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + parser.setSingleDashWordOptionMode(parsingMode); + QVERIFY(parser.addOption(QCommandLineOption(QStringList() << "b" << "bool", "a boolean option"))); + QCOMPARE(parser.parse(args), expectedErrorText.isEmpty()); + QCOMPARE(parser.unknownOptionNames(), expectedUnknownOptionNames); + QCOMPARE(parser.errorText(), expectedErrorText); +} + +void TestQCommandLineParser::testDoubleDash_data() +{ + parsingModes_data(); +} + +void TestQCommandLineParser::testDoubleDash() +{ + QFETCH(QCommandLineParser::SingleDashWordOptionMode, parsingMode); + + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + parser.addOption(QCommandLineOption(QStringList() << "o" << "output", "Output file", "filename")); + parser.setSingleDashWordOptionMode(parsingMode); + QVERIFY(parser.parse(QStringList() << "TestQCommandLineParser" << "--output" << "foo")); + QCOMPARE(parser.value("output"), QString("foo")); + QCOMPARE(parser.positionalArguments(), QStringList()); + QCOMPARE(parser.unknownOptionNames(), QStringList()); + QVERIFY(parser.parse(QStringList() << "TestQCommandLineParser" << "--" << "--output" << "bar" << "-b" << "bleh")); + QCOMPARE(parser.value("output"), QString()); + QCOMPARE(parser.positionalArguments(), QStringList() << "--output" << "bar" << "-b" << "bleh"); + QCOMPARE(parser.unknownOptionNames(), QStringList()); +} + +void TestQCommandLineParser::testDefaultValue() +{ + QCommandLineOption opt("name", "desc", + "valueName", "default"); + QCOMPARE(opt.defaultValues(), QStringList("default")); + opt.setDefaultValue(""); + QCOMPARE(opt.defaultValues(), QStringList()); + opt.setDefaultValue("default"); + QCOMPARE(opt.defaultValues(), QStringList("default")); +} + +void TestQCommandLineParser::testProcessNotCalled() +{ + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + QVERIFY(parser.addOption(QCommandLineOption("b", "a boolean option"))); + QTest::ignoreMessage(QtWarningMsg, "QCommandLineParser: call process() or parse() before isSet"); + QVERIFY(!parser.isSet("b")); + QTest::ignoreMessage(QtWarningMsg, "QCommandLineParser: call process() or parse() before values"); + QCOMPARE(parser.values("b"), QStringList()); +} + +void TestQCommandLineParser::testEmptyArgsList() +{ + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + QTest::ignoreMessage(QtWarningMsg, "QCommandLineParser: argument list cannot be empty, it should contain at least the executable name"); + QVERIFY(!parser.parse(QStringList())); // invalid call, argv[0] is missing +} + +void TestQCommandLineParser::testMissingOptionValue() +{ + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + parser.addOption(QCommandLineOption("option", "An option", "value")); + QVERIFY(!parser.parse(QStringList() << "argv0" << "--option")); // the user forgot to pass a value for --option + QCOMPARE(parser.value("option"), QString()); + QCOMPARE(parser.errorText(), QString("Missing value after '--option'.")); +} + +void TestQCommandLineParser::testStdinArgument_data() +{ + parsingModes_data(); +} + +void TestQCommandLineParser::testStdinArgument() +{ + QFETCH(QCommandLineParser::SingleDashWordOptionMode, parsingMode); + + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + parser.setSingleDashWordOptionMode(parsingMode); + parser.addOption(QCommandLineOption(QStringList() << "i" << "input", "Input file.", "filename")); + parser.addOption(QCommandLineOption("b", "Boolean option.")); + QVERIFY(parser.parse(QStringList() << "TestQCommandLineParser" << "--input" << "-")); + QCOMPARE(parser.value("input"), QString("-")); + QCOMPARE(parser.positionalArguments(), QStringList()); + QCOMPARE(parser.unknownOptionNames(), QStringList()); + + QVERIFY(parser.parse(QStringList() << "TestQCommandLineParser" << "--input" << "-" << "-b" << "arg")); + QCOMPARE(parser.value("input"), QString("-")); + QVERIFY(parser.isSet("b")); + QCOMPARE(parser.positionalArguments(), QStringList() << "arg"); + QCOMPARE(parser.unknownOptionNames(), QStringList()); + + QVERIFY(parser.parse(QStringList() << "TestQCommandLineParser" << "-")); + QCOMPARE(parser.value("input"), QString()); + QVERIFY(!parser.isSet("b")); + QCOMPARE(parser.positionalArguments(), QStringList() << "-"); + QCOMPARE(parser.unknownOptionNames(), QStringList()); +} + +void TestQCommandLineParser::testSingleDashWordOptionModes_data() +{ + QTest::addColumn("parsingMode"); + QTest::addColumn("commandLine"); + QTest::addColumn("expectedOptionNames"); + QTest::addColumn("expectedOptionValues"); + + QTest::newRow("collapsed") << QCommandLineParser::ParseAsCompactedShortOptions << (QStringList() << "-abc" << "val") + << (QStringList() << "a" << "b" << "c") << (QStringList() << QString() << QString() << "val"); + QTest::newRow("collapsed_with_equalsign_value") << QCommandLineParser::ParseAsCompactedShortOptions << (QStringList() << "-abc=val") + << (QStringList() << "a" << "b" << "c") << (QStringList() << QString() << QString() << "val"); + QTest::newRow("collapsed_explicit_longoption") << QCommandLineParser::ParseAsCompactedShortOptions << QStringList("--nn") + << QStringList("nn") << QStringList(); + QTest::newRow("collapsed_longoption_value") << QCommandLineParser::ParseAsCompactedShortOptions << (QStringList() << "--abc" << "val") + << QStringList("abc") << QStringList("val"); + QTest::newRow("compiler") << QCommandLineParser::ParseAsCompactedShortOptions << QStringList("-cab") + << QStringList("c") << QStringList("ab"); + QTest::newRow("compiler_with_space") << QCommandLineParser::ParseAsCompactedShortOptions << (QStringList() << "-c" << "val") + << QStringList("c") << QStringList("val"); + + QTest::newRow("implicitlylong") << QCommandLineParser::ParseAsLongOptions << (QStringList() << "-abc" << "val") + << QStringList("abc") << QStringList("val"); + QTest::newRow("implicitlylong_equal") << QCommandLineParser::ParseAsLongOptions << (QStringList() << "-abc=val") + << QStringList("abc") << QStringList("val"); + QTest::newRow("implicitlylong_longoption") << QCommandLineParser::ParseAsLongOptions << (QStringList() << "--nn") + << QStringList("nn") << QStringList(); + QTest::newRow("implicitlylong_longoption_value") << QCommandLineParser::ParseAsLongOptions << (QStringList() << "--abc" << "val") + << QStringList("abc") << QStringList("val"); + QTest::newRow("implicitlylong_with_space") << QCommandLineParser::ParseAsCompactedShortOptions << (QStringList() << "-c" << "val") + << QStringList("c") << QStringList("val"); +} + +void TestQCommandLineParser::testSingleDashWordOptionModes() +{ + QFETCH(QCommandLineParser::SingleDashWordOptionMode, parsingMode); + QFETCH(QStringList, commandLine); + QFETCH(QStringList, expectedOptionNames); + QFETCH(QStringList, expectedOptionValues); + + commandLine.prepend("TestQCommandLineParser"); + + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + parser.setSingleDashWordOptionMode(parsingMode); + parser.addOption(QCommandLineOption("a", "a option.")); + parser.addOption(QCommandLineOption("b", "b option.")); + parser.addOption(QCommandLineOption(QStringList() << "c" << "abc", "c option.", "value")); + parser.addOption(QCommandLineOption("nn", "nn option.")); + QVERIFY(parser.parse(commandLine)); + QCOMPARE(parser.optionNames(), expectedOptionNames); + for (int i = 0; i < expectedOptionValues.count(); ++i) + QCOMPARE(parser.value(parser.optionNames().at(i)), expectedOptionValues.at(i)); + QCOMPARE(parser.unknownOptionNames(), QStringList()); +} + +QTEST_GUILESS_MAIN(TestQCommandLineParser) diff --git a/tests/TestQCommandLineParser.h b/tests/TestQCommandLineParser.h new file mode 100644 index 000000000..46214cd80 --- /dev/null +++ b/tests/TestQCommandLineParser.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2013 David Faure +** Contact: http://www.qt-project.org/legal +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef KEEPASSX_TESTQCOMMANDLINEPARSER_H +#define KEEPASSX_TESTQCOMMANDLINEPARSER_H + +#include + +class TestQCommandLineParser : public QObject +{ + Q_OBJECT + +public Q_SLOTS: + void initTestCase(); + +private Q_SLOTS: + void parsingModes_data(); + + // In-process tests + void testInvalidOptions(); + void testPositionalArguments(); + void testBooleanOption_data(); + void testBooleanOption(); + void testMultipleNames_data(); + void testMultipleNames(); + void testSingleValueOption_data(); + void testSingleValueOption(); + void testValueNotSet(); + void testMultipleValuesOption(); + void testUnknownOptionErrorHandling_data(); + void testUnknownOptionErrorHandling(); + void testDoubleDash_data(); + void testDoubleDash(); + void testDefaultValue(); + void testProcessNotCalled(); + void testEmptyArgsList(); + void testMissingOptionValue(); + void testStdinArgument_data(); + void testStdinArgument(); + void testSingleDashWordOptionModes_data(); + void testSingleDashWordOptionModes(); +}; + +#endif // KEEPASSX_TESTQCOMMANDLINEPARSER_H