diff options
author | David Faure <faure@kde.org> | 2013-08-24 11:15:19 +0200 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2013-08-24 21:33:39 +0200 |
commit | 404598b61366e681100893052fdb394702d3bcbf (patch) | |
tree | b8741268b3960d6f47fd9a562a362e6503cab75c | |
parent | 1411a6f1acfcbea3f31ac461c27cd3e1be87ee1c (diff) | |
download | qtbase-404598b61366e681100893052fdb394702d3bcbf.tar.gz |
Long live QCommandLineParser!
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.
Done-with: Laszlo Papp <lpapp@kde.org>
Change-Id: Ic7bebc10b3f8d8dd06ad0f4bb897c51d566e3b7c
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
15 files changed, 2233 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore index 9e965e9c12..cc6102b64e 100644 --- a/.gitignore +++ b/.gitignore @@ -311,6 +311,7 @@ tests/auto/corelib/io/qresourceengine/qresourceengine tests/auto/corelib/codecs/qtextcodec/echo/echo tests/auto/corelib/plugin/quuid/testProcessUniqueness/testProcessUniqueness tests/auto/corelib/io/qlockfile/qlockfiletesthelper/qlockfile_test_helper +tests/auto/corelib/tools/qcommandlineparser/testhelper/qcommandlineparser_test_helper tests/auto/dbus/qdbusabstractadaptor/qmyserver/qmyserver tests/auto/dbus/qdbusabstractinterface/qpinger/qpinger tests/auto/dbus/qdbusabstractinterface/test/pinger_interface.* diff --git a/src/corelib/doc/snippets/code/src_corelib_tools_qcommandlineoption.cpp b/src/corelib/doc/snippets/code/src_corelib_tools_qcommandlineoption.cpp new file mode 100644 index 0000000000..d4c745215f --- /dev/null +++ b/src/corelib/doc/snippets/code/src_corelib_tools_qcommandlineoption.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2013 David Faure <faure@kde.org> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//! [0] +QCommandLineOption verboseOption("verbose", "Verbose mode. Prints out more information."); +QCommandLineOption outputOption(QStringList() << "o" << "output", "Write generated data into <file>.", "file"); +//! [0] diff --git a/src/corelib/doc/snippets/code/src_corelib_tools_qcommandlineparser.cpp b/src/corelib/doc/snippets/code/src_corelib_tools_qcommandlineparser.cpp new file mode 100644 index 0000000000..569cb6af80 --- /dev/null +++ b/src/corelib/doc/snippets/code/src_corelib_tools_qcommandlineparser.cpp @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2013 David Faure <faure@kde.org> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//! [0] +bool verbose = parser.isSet("verbose"); +//! [0] + +//! [1] +// Usage: image-editor file +// +// Arguments: +// file The file to open. +parser.addPositionalArgument("file", QCoreApplication::translate("main", "The file to open.")); + +// Usage: web-browser [urls...] +// +// Arguments: +// urls URLs to open, optionally. +parser.addPositionalArgument("urls", QCoreApplication::translate("main", "URLs to open, optionally."), "[urls...]"); + +// Usage: cp source destination +// +// Arguments: +// source Source file to copy. +// destination Destination directory. +parser.addPositionalArgument("source", QCoreApplication::translate("main", "Source file to copy.")); +parser.addPositionalArgument("destination", QCoreApplication::translate("main", "Destination directory.")); +//! [1] + +//! [2] +parser.addPositionalArgument("command", "The command to execute."); + +// Call parse() to find out the positional arguments. +parser.parse(QCoreApplication::arguments()); + +const QStringList args = parser.positionalArguments(); +const QString command = args.isEmpty() ? QString() : args.first(); +if (command == "resize") { + parser.clearPositionalArguments(); + parser.addPositionalArgument("resize", "Resize the object to a new size.", "resize [resize_options]"); + parser.addOption(QCommandLineOption("size", "New size.", "new_size")); + parser.process(app); + // ... +} + +This code results in context-dependent help: + +$ tool --help +Usage: tool command + +Arguments: + command The command to execute. + +$ tool resize --help +Usage: tool resize [resize_options] + +Options: + --size <size> New size. + +Arguments: + resize Resize the object to a new size. + +//! [2] + +//! [3] +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + app.setApplicationName("my-copy-program"); + app.setApplicationVersion("1.0"); + + QCommandLineParser parser; + parser.addHelpOption("Test helper"); + parser.addVersionOption(); + parser.addRemainingArgument("source", QCoreApplication::translate("main", "Source file to copy.")); + parser.addRemainingArgument("destination", QCoreApplication::translate("main", "Destination directory.")); + + // A boolean option with a single name (-p) + QCommandLineOption showProgressOption("p", QCoreApplication::translate("main", "Show progress during copy")); + parser.addOption(showProgressOption); + + // A boolean option with multiple names (-f, --force) + QCommandLineOption forceOption(QStringList() << "f" << "force", "Overwrite existing files."); + parser.addOption(forceOption); + + // An option with a value + QCommandLineOption targetDirectoryOption(QStringList() << "t" << "target-directory", + QCoreApplication::translate("main", "Copy all source files into <directory>."), + QCoreApplication::translate("main", "directory")); + parser.addOption(targetDirectoryOption); + + // Process the actual command line arguments given by the user + parser.process(app); + + const QStringList args = parser.remainingArguments(); + // source is args.at(0), destination is args.at(1) + + bool showProgress = parser.isSet(showProgressOption); + bool force = parser.isSet(forceOption); + QString targetDir = parser.value(targetDirectoryOption); + // ... +} + +//! [3] diff --git a/src/corelib/kernel/qcoreapplication.cpp b/src/corelib/kernel/qcoreapplication.cpp index 3460b9e228..bba878d2eb 100644 --- a/src/corelib/kernel/qcoreapplication.cpp +++ b/src/corelib/kernel/qcoreapplication.cpp @@ -589,6 +589,8 @@ void QCoreApplicationPrivate::initLocale() Note that some arguments supplied by the user may have been processed and removed by QCoreApplication. + For more advanced command line option handling, create a QCommandLineParser. + \section1 Locale Settings On Unix/Linux Qt is configured to use the system locale settings by @@ -2060,7 +2062,7 @@ qint64 QCoreApplication::applicationPid() As a result of this, the string given by arguments().at(0) might not be the program name on Windows, depending on how the application was started. - \sa applicationFilePath() + \sa applicationFilePath(), QCommandLineParser */ QStringList QCoreApplication::arguments() diff --git a/src/corelib/tools/qcommandlineoption.cpp b/src/corelib/tools/qcommandlineoption.cpp new file mode 100644 index 0000000000..4f9e166587 --- /dev/null +++ b/src/corelib/tools/qcommandlineoption.cpp @@ -0,0 +1,308 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Laszlo Papp <lpapp@kde.org> +** Copyright (C) 2013 David Faure <faure@kde.org> +** 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 "qset.h" + +QT_BEGIN_NAMESPACE + +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 <file>" 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; +} + +/*! + \fn void QCommandLineOption::swap(QCommandLineOption &other) + + Swaps option \a other with this option. This operation is very + fast and never fails. +*/ + +/*! + Returns the names set for this option. + */ +QStringList QCommandLineOption::names() const +{ + return d->names; +} + +void QCommandLineOptionPrivate::setNames(const QStringList &nameList) +{ + names.clear(); + if (nameList.isEmpty()) + qWarning("Options must have at least one name"); + foreach (const QString &name, nameList) { + if (name.isEmpty()) + qWarning("Option names cannot be empty"); + else if (name.startsWith(QLatin1Char('-'))) + qWarning("Option names cannot start with a '-'"); + else if (name.startsWith(QLatin1Char('/'))) + qWarning("Option names cannot start with a '/'"); + else if (name.contains(QLatin1Char('='))) + qWarning("Option names cannot contain a '='"); + else + names.append(name); + } +} + +/*! + 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 <file>}. + + 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) +{ + d->defaultValues.clear(); + if (!defaultValue.isEmpty()) + d->defaultValues << defaultValue; +} + +/*! + 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; +} + +QT_END_NAMESPACE diff --git a/src/corelib/tools/qcommandlineoption.h b/src/corelib/tools/qcommandlineoption.h new file mode 100644 index 0000000000..7775aae5b6 --- /dev/null +++ b/src/corelib/tools/qcommandlineoption.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Laszlo Papp <lpapp@kde.org> +** 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 <QtCore/qstringlist.h> +#include <QtCore/qshareddata.h> + +QT_BEGIN_NAMESPACE + +class QCommandLineOptionPrivate; + +class Q_CORE_EXPORT 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); +#ifdef Q_COMPILER_RVALUE_REFS + inline QCommandLineOption &operator=(QCommandLineOption &&other) + { qSwap(d, other.d); return *this; } +#endif + + inline void swap(QCommandLineOption &other) + { qSwap(d, other.d); } + + 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<QCommandLineOptionPrivate> d; +}; + +QT_END_NAMESPACE + +#endif // QCOMMANDLINEOPTION_H diff --git a/src/corelib/tools/qcommandlineparser.cpp b/src/corelib/tools/qcommandlineparser.cpp new file mode 100644 index 0000000000..ae6079ab0b --- /dev/null +++ b/src/corelib/tools/qcommandlineparser.cpp @@ -0,0 +1,896 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Laszlo Papp <lpapp@kde.org> +** Copyright (C) 2013 David Faure <faure@kde.org> +** 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 <qcoreapplication.h> +#include <qhash.h> +#include <qvector.h> +#include <stdio.h> +#include <stdlib.h> + +QT_BEGIN_NAMESPACE + +typedef QHash<QString, int> NameHash_t; + +// Special value for "not found" when doing hash lookups. +static const NameHash_t::mapped_type optionNotFound = ~0; + +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<QCommandLineOption> 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<int, QStringList> 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<PositionalArgumentDefinition> 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::mapped_type optionOffset = nameHash.value(optionName, optionNotFound); + if (optionOffset == optionNotFound) { + qWarning("QCommandLineParser: option not defined: \"%s\"", qPrintable(optionName)); + return QStringList(); + } + return commandLineOptionList.at(optionOffset).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 \a{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.cpp 3 + + 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 true if adding the option was successful; otherwise returns 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()) { + foreach (const QString &name, optionNames) { + if (d->nameHash.contains(name)) + return false; + } + + d->commandLineOptionList.append(option); + + const int offset = d->commandLineOptionList.size() - 1; + 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() +{ + d->builtinVersionOption = true; + QCommandLineOption opt(QStringList() << QStringLiteral("v") << QStringLiteral("version"), tr("Displays version information.")); + addOption(opt); + 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: + \code + setApplicationDescription(QCoreApplication::translate("main", "The best application in the world")); + addHelpOption(); + \endcode + + Returns the option instance, which can be used to call isSet(). +*/ +QCommandLineOption QCommandLineParser::addHelpOption() +{ + d->builtinHelpOption = true; + QCommandLineOption opt(QStringList() +#ifdef Q_OS_WIN + << QStringLiteral("?") +#endif + << QStringLiteral("h") + << QStringLiteral("help"), tr("Displays this help.")); + addOption(opt); + return opt; +} + +/*! + Sets the application \a description shown by helpText(). + Most applications don't need to call this directly, addHelpOption() + also sets the application description. +*/ +void QCommandLineParser::setApplicationDescription(const QString &description) +{ + d->description = description; +} + +/*! + Returns the application description set in setApplicationDescription() + or addHelpOption(). +*/ +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 1 + + \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 2 +*/ +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(app) 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 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). + + Return false in case of a parse error (unknown option or missing value); returns 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 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(QStringLiteral(", "))); + return QString(); +} + +/*! + Processes the command line \a arguments. + + This means both parsing them, and handling the builtin options, + \c{--version} if addVersionOption was called, \c{--help} if addHelpOption was called, + as well as giving an error on unknown option names. + In each of these three cases, 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(1); + } + + if (d->builtinVersionOption && isSet(QStringLiteral("version"))) { + printf("%s %s\n", qPrintable(QCoreApplication::applicationName()), qPrintable(QCoreApplication::applicationVersion())); + ::exit(0); + } + + if (d->builtinHelpOption && isSet(QStringLiteral("help"))) + showHelp(0); +} + +/*! + \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 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 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 arguments. + + 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. + + Options that were successfully recognized, and their values, are + removed from the input list. If \c m_bRemoveUnknownLongNames is + \c true, unrecognized options are removed and placed into a list of + unknown option names. Anything left over is placed into a list of + leftover arguments. + */ +bool QCommandLineParserPrivate::parse(const QStringList &args) +{ + needsParsing = false; + bool error = false; + + const QString doubleDashString(QStringLiteral("--")); + 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 true if the option \a name was set, false otherwise. + + This is the recommended way to check for options with no values. + + 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); + 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() + */ + +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() + */ + +QStringList QCommandLineParser::values(const QString &optionName) const +{ + d->checkParsed("values"); + const NameHash_t::mapped_type optionOffset = d->nameHash.value(optionName, optionNotFound); + if (optionOffset != optionNotFound) { + 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 + Returns true if the \a option was set, false otherwise. +*/ +bool QCommandLineParser::isSet(const QCommandLineOption &option) const +{ + return isSet(option.names().first()); +} + +/*! + \overload + Returns the option value found for the given \a option, or + an empty string if not found. +*/ +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. +*/ +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() +*/ +Q_NORETURN 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 = QStringLiteral(" ") + names.leftJustified(longestOptionNameString) + QLatin1Char(' '); + const int leftColumnWidth = text.length(); + const int rightColumnWidth = 79 - leftColumnWidth; + text += description.left(rightColumnWidth) + nl; + for (int n = rightColumnWidth; n < description.length(); n += rightColumnWidth) + text += QStringLiteral(" ").repeated(leftColumnWidth) + description.mid(n, rightColumnWidth) + nl; + 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]"); + } + 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; + foreach (const QCommandLineOption &option, commandLineOptionList) { + QStringList optionNames; + foreach (const QString &optionName, option.names()) { + if (optionName.length() == 1) + optionNames.append(QLatin1Char('-') + optionName); + else + optionNames.append(QStringLiteral("--") + optionName); + } + QString optionNamesString = optionNames.join(QStringLiteral(", ")); + if (!option.valueName().isEmpty()) + optionNamesString += QStringLiteral(" <") + 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; + foreach (const PositionalArgumentDefinition &arg, positionalArgumentDefinitions) { + text += wrapText(arg.name, longestOptionNameString, arg.description); + } + } + return text; +} + +QT_END_NAMESPACE diff --git a/src/corelib/tools/qcommandlineparser.h b/src/corelib/tools/qcommandlineparser.h new file mode 100644 index 0000000000..5a7061f031 --- /dev/null +++ b/src/corelib/tools/qcommandlineparser.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Laszlo Papp <lpapp@kde.org> +** 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 <QtCore/qstringlist.h> + +#include <QtCore/qcoreapplication.h> +#include <QtCore/qcommandlineoption.h> + +QT_BEGIN_NAMESPACE + +class QCommandLineParserPrivate; +class QCoreApplication; + +class Q_CORE_EXPORT 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; + + Q_NORETURN void showHelp(int exitCode = 0); + QString helpText() const; + +private: + Q_DISABLE_COPY(QCommandLineParser) + + QCommandLineParserPrivate * const d; +}; + +QT_END_NAMESPACE + +#endif // QCOMMANDLINEPARSER_H diff --git a/src/corelib/tools/tools.pri b/src/corelib/tools/tools.pri index 40858e5336..8aab53998b 100644 --- a/src/corelib/tools/tools.pri +++ b/src/corelib/tools/tools.pri @@ -12,6 +12,8 @@ HEADERS += \ tools/qcache.h \ tools/qchar.h \ tools/qcollator_p.h \ + tools/qcommandlineoption.h \ + tools/qcommandlineparser.h \ tools/qcontainerfwd.h \ tools/qcryptographichash.h \ tools/qdatetime.h \ @@ -69,6 +71,8 @@ SOURCES += \ tools/qbytearray.cpp \ tools/qbytearraymatcher.cpp \ tools/qcollator.cpp \ + tools/qcommandlineoption.cpp \ + tools/qcommandlineparser.cpp \ tools/qcryptographichash.cpp \ tools/qdatetime.cpp \ tools/qeasingcurve.cpp \ diff --git a/tests/auto/corelib/tools/qcommandlineparser/qcommandlineparser.pro b/tests/auto/corelib/tools/qcommandlineparser/qcommandlineparser.pro new file mode 100644 index 0000000000..a9aedc4c0d --- /dev/null +++ b/tests/auto/corelib/tools/qcommandlineparser/qcommandlineparser.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs + +SUBDIRS += tst_qcommandlineparser.pro testhelper/qcommandlineparser_test_helper.pro diff --git a/tests/auto/corelib/tools/qcommandlineparser/testhelper/qcommandlineparser_test_helper.cpp b/tests/auto/corelib/tools/qcommandlineparser/testhelper/qcommandlineparser_test_helper.cpp new file mode 100644 index 0000000000..c7bd2a5dc9 --- /dev/null +++ b/tests/auto/corelib/tools/qcommandlineparser/testhelper/qcommandlineparser_test_helper.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2013 David Faure <faure@kde.org> +** 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 <QDebug> +#include <QCoreApplication> +#include <QCommandLineParser> + +#include <stdio.h> + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + app.setApplicationVersion("1.0"); + + QCommandLineParser parser; + parser.setApplicationDescription("Test helper"); + parser.addHelpOption(); + parser.addVersionOption(); + parser.addPositionalArgument("parsingMode", "The parsing mode to test."); + parser.addPositionalArgument("command", "The command to execute."); + parser.addOption(QCommandLineOption("load", "Load file from URL.", "url")); + parser.addOption(QCommandLineOption(QStringList() << "o" << "output", "Set output file.", "file")); + parser.addOption(QCommandLineOption("D", "Define macro.", "key=value")); + + // An option with a longer description, to test wrapping + QCommandLineOption noImplicitIncludesOption(QStringList() << QStringLiteral("n") << QStringLiteral("no-implicit-includes")); + noImplicitIncludesOption.setDescription(QStringLiteral("Disable automatic generation of implicit #include-directives.")); + parser.addOption(noImplicitIncludesOption); + + // This program supports different options depending on the "command" (first argument). + // Call parse() to find out the positional arguments. + parser.parse(QCoreApplication::arguments()); + + QStringList args = parser.positionalArguments(); + if (args.isEmpty()) + parser.showHelp(1); + parser.setSingleDashWordOptionMode(QCommandLineParser::SingleDashWordOptionMode(args.takeFirst().toInt())); + const QString command = args.isEmpty() ? QString() : args.first(); + if (command == "resize") { + parser.clearPositionalArguments(); + parser.addPositionalArgument("resize", "Resize the object to a new size.", "resize [resize_options]"); + parser.addOption(QCommandLineOption("size", "New size.", "size")); + parser.process(app); + const QString size = parser.value("size"); + printf("Resizing %s to %s and saving to %s\n", qPrintable(parser.value("load")), qPrintable(size), qPrintable(parser.value("o"))); + } else { + // Call process again, to handle unknown options this time. + parser.process(app); + } + + printf("Positional arguments: %s\n", qPrintable(parser.positionalArguments().join(","))); + + return 0; +} + diff --git a/tests/auto/corelib/tools/qcommandlineparser/testhelper/qcommandlineparser_test_helper.pro b/tests/auto/corelib/tools/qcommandlineparser/testhelper/qcommandlineparser_test_helper.pro new file mode 100644 index 0000000000..dce1ac0d37 --- /dev/null +++ b/tests/auto/corelib/tools/qcommandlineparser/testhelper/qcommandlineparser_test_helper.pro @@ -0,0 +1,6 @@ +CONFIG += console +CONFIG -= app_bundle +QT = core +DESTDIR = ./ + +SOURCES += qcommandlineparser_test_helper.cpp diff --git a/tests/auto/corelib/tools/qcommandlineparser/tst_qcommandlineparser.cpp b/tests/auto/corelib/tools/qcommandlineparser/tst_qcommandlineparser.cpp new file mode 100644 index 0000000000..79ab212d47 --- /dev/null +++ b/tests/auto/corelib/tools/qcommandlineparser/tst_qcommandlineparser.cpp @@ -0,0 +1,531 @@ +/**************************************************************************** +** +** Copyright (C) 2013 David Faure <faure@kde.org> +** 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 <QtTest/QtTest> +#include <QtCore/QCommandLineParser> + +Q_DECLARE_METATYPE(char**) + +class tst_QCommandLineParser : public QObject +{ + Q_OBJECT + +private 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 testProcessNotCalled(); + void testEmptyArgsList(); + void testMissingOptionValue(); + void testStdinArgument_data(); + void testStdinArgument(); + void testSingleDashWordOptionModes_data(); + void testSingleDashWordOptionModes(); + + // QProcess-based tests using qcommandlineparser_test_helper + void testVersionOption(); + void testHelpOption_data(); + void testHelpOption(); +}; + +static char *empty_argv[] = { const_cast<char*>("tst_qcommandlineparser") }; +static int empty_argc = 1; + +Q_DECLARE_METATYPE(QCommandLineParser::SingleDashWordOptionMode) + +void tst_QCommandLineParser::parsingModes_data() +{ + QTest::addColumn<QCommandLineParser::SingleDashWordOptionMode>("parsingMode"); + + QTest::newRow("collapsed") << QCommandLineParser::ParseAsCompactedShortOptions; + QTest::newRow("implicitlylong") << QCommandLineParser::ParseAsLongOptions; +} + +void tst_QCommandLineParser::testInvalidOptions() +{ + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + QTest::ignoreMessage(QtWarningMsg, "Option names cannot start with a '-'"); + parser.addOption(QCommandLineOption(QStringLiteral("-v"), QStringLiteral("Displays version information."))); +} + +void tst_QCommandLineParser::testPositionalArguments() +{ + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + QVERIFY(parser.parse(QStringList() << "tst_qcommandlineparser" << "file.txt")); + QCOMPARE(parser.positionalArguments(), QStringList() << QStringLiteral("file.txt")); +} + +void tst_QCommandLineParser::testBooleanOption_data() +{ + QTest::addColumn<QStringList>("args"); + QTest::addColumn<QStringList>("expectedOptionNames"); + QTest::addColumn<bool>("expectedIsSet"); + + QTest::newRow("set") << (QStringList() << "tst_qcommandlineparser" << "-b") << (QStringList() << "b") << true; + QTest::newRow("unset") << (QStringList() << "tst_qcommandlineparser") << QStringList() << false; +} + +void tst_QCommandLineParser::testBooleanOption() +{ + QFETCH(QStringList, args); + QFETCH(QStringList, expectedOptionNames); + QFETCH(bool, expectedIsSet); + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + QVERIFY(parser.addOption(QCommandLineOption(QStringLiteral("b"), QStringLiteral("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 tst_QCommandLineParser::testMultipleNames_data() +{ + QTest::addColumn<QStringList>("args"); + QTest::addColumn<QStringList>("expectedOptionNames"); + + QTest::newRow("short") << (QStringList() << "tst_qcommandlineparser" << "-v") << (QStringList() << "v"); + QTest::newRow("long") << (QStringList() << "tst_qcommandlineparser" << "--version") << (QStringList() << "version"); + QTest::newRow("not_set") << (QStringList() << "tst_qcommandlineparser") << QStringList(); +} + +void tst_QCommandLineParser::testMultipleNames() +{ + QFETCH(QStringList, args); + QFETCH(QStringList, expectedOptionNames); + QCoreApplication app(empty_argc, empty_argv); + QCommandLineOption option(QStringList() << "v" << "version", QStringLiteral("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 tst_QCommandLineParser::testSingleValueOption_data() +{ + QTest::addColumn<QStringList>("args"); + QTest::addColumn<QStringList>("defaults"); + QTest::addColumn<bool>("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 tst_QCommandLineParser::testSingleValueOption() +{ + QFETCH(QStringList, args); + QFETCH(QStringList, defaults); + QFETCH(bool, expectedIsSet); + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + QCommandLineOption option(QStringList() << "s" << "style", QStringLiteral("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 tst_QCommandLineParser::testValueNotSet() +{ + QCoreApplication app(empty_argc, empty_argv); + // Not set, no default value + QCommandLineParser parser; + QCommandLineOption option(QStringList() << "s" << "style", QStringLiteral("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 tst_QCommandLineParser::testMultipleValuesOption() +{ + QCoreApplication app(empty_argc, empty_argv); + QCommandLineOption option(QStringLiteral("param"), QStringLiteral("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: tst_qcommandlineparser [options]\n" + "\n" + "Options:\n" + " --param <key=value> Pass parameter to the backend.\n"; + + const QString exeName = QCoreApplication::instance()->arguments().first(); // e.g. debug\tst_qcommandlineparser.exe on Windows + expected.replace(QStringLiteral("tst_qcommandlineparser"), exeName); + QCOMPARE(parser.helpText(), expected); +} + +void tst_QCommandLineParser::testUnknownOptionErrorHandling_data() +{ + QTest::addColumn<QCommandLineParser::SingleDashWordOptionMode>("parsingMode"); + QTest::addColumn<QStringList>("args"); + QTest::addColumn<QStringList>("expectedUnknownOptionNames"); + QTest::addColumn<QString>("expectedErrorText"); + + const QStringList args_hello = QStringList() << "tst_qcommandlineparser" << "--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() << "tst_qcommandlineparser" << "-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() << "tst_qcommandlineparser" << "-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 tst_QCommandLineParser::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", QStringLiteral("a boolean option")))); + QCOMPARE(parser.parse(args), expectedErrorText.isEmpty()); + QCOMPARE(parser.unknownOptionNames(), expectedUnknownOptionNames); + QCOMPARE(parser.errorText(), expectedErrorText); +} + +void tst_QCommandLineParser::testDoubleDash_data() +{ + parsingModes_data(); +} + +void tst_QCommandLineParser::testDoubleDash() +{ + QFETCH(QCommandLineParser::SingleDashWordOptionMode, parsingMode); + + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + parser.addOption(QCommandLineOption(QStringList() << "o" << "output", QStringLiteral("Output file"), QStringLiteral("filename"))); + parser.setSingleDashWordOptionMode(parsingMode); + QVERIFY(parser.parse(QStringList() << "tst_qcommandlineparser" << "--output" << "foo")); + QCOMPARE(parser.value("output"), QString("foo")); + QCOMPARE(parser.positionalArguments(), QStringList()); + QCOMPARE(parser.unknownOptionNames(), QStringList()); + QVERIFY(parser.parse(QStringList() << "tst_qcommandlineparser" << "--" << "--output" << "bar" << "-b" << "bleh")); + QCOMPARE(parser.value("output"), QString()); + QCOMPARE(parser.positionalArguments(), QStringList() << "--output" << "bar" << "-b" << "bleh"); + QCOMPARE(parser.unknownOptionNames(), QStringList()); +} + +void tst_QCommandLineParser::testProcessNotCalled() +{ + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + QVERIFY(parser.addOption(QCommandLineOption(QStringLiteral("b"), QStringLiteral("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 tst_QCommandLineParser::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 tst_QCommandLineParser::testMissingOptionValue() +{ + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + parser.addOption(QCommandLineOption(QStringLiteral("option"), QStringLiteral("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 tst_QCommandLineParser::testStdinArgument_data() +{ + parsingModes_data(); +} + +void tst_QCommandLineParser::testStdinArgument() +{ + QFETCH(QCommandLineParser::SingleDashWordOptionMode, parsingMode); + + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + parser.setSingleDashWordOptionMode(parsingMode); + parser.addOption(QCommandLineOption(QStringList() << "i" << "input", QStringLiteral("Input file."), QStringLiteral("filename"))); + parser.addOption(QCommandLineOption("b", QStringLiteral("Boolean option."))); + QVERIFY(parser.parse(QStringList() << "tst_qcommandlineparser" << "--input" << "-")); + QCOMPARE(parser.value("input"), QString("-")); + QCOMPARE(parser.positionalArguments(), QStringList()); + QCOMPARE(parser.unknownOptionNames(), QStringList()); + + QVERIFY(parser.parse(QStringList() << "tst_qcommandlineparser" << "--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() << "tst_qcommandlineparser" << "-")); + QCOMPARE(parser.value("input"), QString()); + QVERIFY(!parser.isSet("b")); + QCOMPARE(parser.positionalArguments(), QStringList() << "-"); + QCOMPARE(parser.unknownOptionNames(), QStringList()); +} + +void tst_QCommandLineParser::testSingleDashWordOptionModes_data() +{ + QTest::addColumn<QCommandLineParser::SingleDashWordOptionMode>("parsingMode"); + QTest::addColumn<QStringList>("commandLine"); + QTest::addColumn<QStringList>("expectedOptionNames"); + QTest::addColumn<QStringList>("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 tst_QCommandLineParser::testSingleDashWordOptionModes() +{ + QFETCH(QCommandLineParser::SingleDashWordOptionMode, parsingMode); + QFETCH(QStringList, commandLine); + QFETCH(QStringList, expectedOptionNames); + QFETCH(QStringList, expectedOptionValues); + + commandLine.prepend("tst_QCommandLineParser"); + + QCoreApplication app(empty_argc, empty_argv); + QCommandLineParser parser; + parser.setSingleDashWordOptionMode(parsingMode); + parser.addOption(QCommandLineOption("a", QStringLiteral("a option."))); + parser.addOption(QCommandLineOption("b", QStringLiteral("b option."))); + parser.addOption(QCommandLineOption(QStringList() << "c" << "abc", QStringLiteral("c option."), QStringLiteral("value"))); + parser.addOption(QCommandLineOption("nn", QStringLiteral("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()); +} + +void tst_QCommandLineParser::testVersionOption() +{ +#ifdef Q_OS_WINCE + QSKIP("Reading and writing to a process is not supported on Qt/CE"); +#endif + QCoreApplication app(empty_argc, empty_argv); + QProcess process; + process.start("testhelper/qcommandlineparser_test_helper", QStringList() << "0" << "--version"); + QVERIFY(process.waitForFinished(5000)); + QCOMPARE(process.exitStatus(), QProcess::NormalExit); + QString output = process.readAll(); +#ifdef Q_OS_WIN + output.replace(QStringLiteral("\r\n"), QStringLiteral("\n")); +#endif + QCOMPARE(output, QString("qcommandlineparser_test_helper 1.0\n")); +} + +void tst_QCommandLineParser::testHelpOption_data() +{ + QTest::addColumn<QCommandLineParser::SingleDashWordOptionMode>("parsingMode"); + QTest::addColumn<QString>("expectedHelpOutput"); + + QString expectedOutput = + "Usage: testhelper/qcommandlineparser_test_helper [options] parsingMode command\n" + "Test helper\n" + "\n" + "Options:\n" + " -h, --help Displays this help.\n" + " -v, --version Displays version information.\n" + " --load <url> Load file from URL.\n" + " -o, --output <file> Set output file.\n" + " -D <key=value> Define macro.\n" + " -n, --no-implicit-includes Disable automatic generation of implicit #include\n" + " -directives.\n" + "\n" + "Arguments:\n" + " parsingMode The parsing mode to test.\n" + " command The command to execute.\n"; +#ifdef Q_OS_WIN + expectedOutput.replace(" -h, --help Displays this help.\n", + " -?, -h, --help Displays this help.\n"); + expectedOutput.replace("testhelper/", "testhelper\\"); +#endif + + QTest::newRow("collapsed") << QCommandLineParser::ParseAsCompactedShortOptions << expectedOutput; + QTest::newRow("long") << QCommandLineParser::ParseAsLongOptions << expectedOutput; +} + +void tst_QCommandLineParser::testHelpOption() +{ +#ifdef Q_OS_WINCE + QSKIP("Reading and writing to a process is not supported on Qt/CE"); +#endif + + QFETCH(QCommandLineParser::SingleDashWordOptionMode, parsingMode); + QFETCH(QString, expectedHelpOutput); + QCoreApplication app(empty_argc, empty_argv); + QProcess process; + process.start("testhelper/qcommandlineparser_test_helper", QStringList() << QString::number(parsingMode) << "--help"); + QVERIFY(process.waitForFinished(5000)); + QCOMPARE(process.exitStatus(), QProcess::NormalExit); + QString output = process.readAll(); +#ifdef Q_OS_WIN + output.replace(QStringLiteral("\r\n"), QStringLiteral("\n")); +#endif + QCOMPARE(output, expectedHelpOutput); + + process.start("testhelper/qcommandlineparser_test_helper", QStringList() << "0" << "resize" << "--help"); + QVERIFY(process.waitForFinished(5000)); + QCOMPARE(process.exitStatus(), QProcess::NormalExit); + output = process.readAll(); +#ifdef Q_OS_WIN + output.replace(QStringLiteral("\r\n"), QStringLiteral("\n")); +#endif + QByteArray expectedResizeHelp = + "Usage: testhelper/qcommandlineparser_test_helper [options] resize [resize_options]\n" + "Test helper\n" + "\n" + "Options:\n" + " -h, --help Displays this help.\n" + " -v, --version Displays version information.\n" + " --load <url> Load file from URL.\n" + " -o, --output <file> Set output file.\n" + " -D <key=value> Define macro.\n" + " -n, --no-implicit-includes Disable automatic generation of implicit #include\n" + " -directives.\n" + " --size <size> New size.\n" + "\n" + "Arguments:\n" + " resize Resize the object to a new size.\n"; +#ifdef Q_OS_WIN + expectedResizeHelp.replace(" -h, --help Displays this help.\n", + " -?, -h, --help Displays this help.\n"); + expectedResizeHelp.replace("testhelper/", "testhelper\\"); +#endif + QCOMPARE(output, QString(expectedResizeHelp)); +} + +QTEST_APPLESS_MAIN(tst_QCommandLineParser) +#include "tst_qcommandlineparser.moc" + diff --git a/tests/auto/corelib/tools/qcommandlineparser/tst_qcommandlineparser.pro b/tests/auto/corelib/tools/qcommandlineparser/tst_qcommandlineparser.pro new file mode 100644 index 0000000000..6d3e6d677f --- /dev/null +++ b/tests/auto/corelib/tools/qcommandlineparser/tst_qcommandlineparser.pro @@ -0,0 +1,4 @@ +CONFIG += testcase parallel_test +TARGET = tst_qcommandlineparser +QT = core testlib +SOURCES = tst_qcommandlineparser.cpp diff --git a/tests/auto/corelib/tools/tools.pro b/tests/auto/corelib/tools/tools.pro index afa5e5b613..fbc1b996f7 100644 --- a/tests/auto/corelib/tools/tools.pro +++ b/tests/auto/corelib/tools/tools.pro @@ -8,6 +8,7 @@ SUBDIRS=\ qbytedatabuffer \ qcache \ qchar \ + qcommandlineparser \ qcontiguouscache \ qcryptographichash \ qdate \ |