Skip to content

Commit

Permalink
QCommandLineParser: add --help-all, to show Qt options as well
Browse files Browse the repository at this point in the history
Sample output at http://www.kdab.com/~dfaure/2019/help-all-example.txt

Fixes: QTBUG-41802
Change-Id: I7a3350200761d41481fcb10ec4328e96e548d246
Reviewed-by: André Hartmann <[email protected]>
Reviewed-by: Thiago Macieira <[email protected]>
  • Loading branch information
dfaure-kdab committed May 15, 2019
1 parent b75e6e0 commit 341c8b9
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 19 deletions.
7 changes: 7 additions & 0 deletions src/corelib/kernel/qcoreapplication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,13 @@ bool QCoreApplicationPrivate::checkInstance(const char *function)
return b;
}

void QCoreApplicationPrivate::addQtOptions(QList<QCommandLineOption> *options)
{
options->append(QCommandLineOption(QStringLiteral("qmljsdebugger"),
QStringLiteral("Activates the QML/JS debugger with a specified port. The value must be of format port:1234[,block]. \"block\" makes the application wait for a connection."),
QStringLiteral("value")));
}

void QCoreApplicationPrivate::processCommandLineArguments()
{
int j = argc ? 1 : 0;
Expand Down
1 change: 1 addition & 0 deletions src/corelib/kernel/qcoreapplication.h
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ public Q_SLOTS:
#endif
friend Q_CORE_EXPORT QString qAppName();
friend class QClassFactory;
friend class QCommandLineParserPrivate;
};

#ifdef QT_NO_DEPRECATED
Expand Down
3 changes: 3 additions & 0 deletions src/corelib/kernel/qcoreapplication_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
//

#include "QtCore/qcoreapplication.h"
#include "QtCore/qcommandlineoption.h"
#include "QtCore/qtranslator.h"
#if QT_CONFIG(settings)
#include "QtCore/qsettings.h"
Expand Down Expand Up @@ -104,6 +105,8 @@ class Q_CORE_EXPORT QCoreApplicationPrivate

static bool checkInstance(const char *method);

virtual void addQtOptions(QList<QCommandLineOption> *options);

#ifndef QT_NO_QOBJECT
bool sendThroughApplicationEventFilters(QObject *, QEvent *);
static bool sendThroughObjectEventFilters(QObject *, QEvent *);
Expand Down
46 changes: 32 additions & 14 deletions src/corelib/tools/qcommandlineparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "qcommandlineparser.h"

#include <qcoreapplication.h>
#include <private/qcoreapplication_p.h>
#include <qhash.h>
#include <qvector.h>
#include <qdebug.h>
Expand Down Expand Up @@ -70,11 +71,12 @@ class QCommandLineParserPrivate
bool parse(const QStringList &args);
void checkParsed(const char *method);
QStringList aliases(const QString &name) const;
QString helpText() const;
QString helpText(bool includeQtOptions) const;
bool registerFoundOption(const QString &optionName);
bool parseOptionValue(const QString &optionName, const QString &argument,
QStringList::const_iterator *argumentIterator,
QStringList::const_iterator argsEnd);
Q_NORETURN void showHelp(int exitCode, bool includeQtOptions);

//! Error text set when parse() returns false
QString errorText;
Expand Down Expand Up @@ -419,7 +421,9 @@ QCommandLineOption QCommandLineParser::addVersionOption()

/*!
Adds the help option (\c{-h}, \c{--help} and \c{-?} on Windows)
This option is handled automatically by QCommandLineParser.
as well as an option \c{--help-all} to include Qt-specific options in the output.
These options are handled automatically by QCommandLineParser.
Remember to use setApplicationDescription to set the application description,
which will be displayed when this option is used.
Expand All @@ -436,8 +440,10 @@ QCommandLineOption QCommandLineParser::addHelpOption()
<< QStringLiteral("?")
#endif
<< QStringLiteral("h")
<< QStringLiteral("help"), tr("Displays this help."));
<< QStringLiteral("help"), tr("Displays help on commandline options."));
addOption(opt);
QCommandLineOption optHelpAll(QStringLiteral("help-all"), tr("Displays help including Qt specific options."));
addOption(optHelpAll);
d->builtinHelpOption = true;
return opt;
}
Expand Down Expand Up @@ -581,7 +587,8 @@ static void showParserMessage(const QString &message, MessageType type)
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.
The builtin options are \c{--version} if addVersionOption was called and
\c{--help} / \c{--help-all} 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.
Expand All @@ -600,7 +607,10 @@ void QCommandLineParser::process(const QStringList &arguments)
showVersion();

if (d->builtinHelpOption && isSet(QStringLiteral("help")))
showHelp(EXIT_SUCCESS);
d->showHelp(EXIT_SUCCESS, false);

if (d->builtinHelpOption && isSet(QStringLiteral("help-all")))
d->showHelp(EXIT_SUCCESS, true);
}

/*!
Expand Down Expand Up @@ -1033,7 +1043,12 @@ Q_NORETURN void QCommandLineParser::showVersion()
*/
Q_NORETURN void QCommandLineParser::showHelp(int exitCode)
{
showParserMessage(d->helpText(), UsageMessage);
d->showHelp(exitCode, false);
}

Q_NORETURN void QCommandLineParserPrivate::showHelp(int exitCode, bool includeQtOptions)
{
showParserMessage(helpText(includeQtOptions), UsageMessage);
qt_call_post_routines();
::exit(exitCode);
}
Expand All @@ -1045,7 +1060,7 @@ Q_NORETURN void QCommandLineParser::showHelp(int exitCode)
*/
QString QCommandLineParser::helpText() const
{
return d->helpText();
return d->helpText(false);
}

static QString wrapText(const QString &names, int longestOptionNameString, const QString &description)
Expand Down Expand Up @@ -1103,26 +1118,29 @@ static QString wrapText(const QString &names, int longestOptionNameString, const
return text;
}

QString QCommandLineParserPrivate::helpText() const
QString QCommandLineParserPrivate::helpText(bool includeQtOptions) const
{
const QLatin1Char nl('\n');
QString text;
QString usage;
usage += QCoreApplication::instance()->arguments().constFirst(); // executable name
if (!commandLineOptionList.isEmpty())
QList<QCommandLineOption> options = commandLineOptionList;
if (includeQtOptions)
QCoreApplication::instance()->d_func()->addQtOptions(&options);
if (!options.isEmpty())
usage += QLatin1Char(' ') + QCommandLineParser::tr("[options]");
for (const PositionalArgumentDefinition &arg : positionalArgumentDefinitions)
usage += QLatin1Char(' ') + arg.syntax;
text += QCommandLineParser::tr("Usage: %1").arg(usage) + nl;
if (!description.isEmpty())
text += description + nl;
text += nl;
if (!commandLineOptionList.isEmpty())
if (!options.isEmpty())
text += QCommandLineParser::tr("Options:") + nl;
QStringList optionNameList;
optionNameList.reserve(commandLineOptionList.size());
optionNameList.reserve(options.size());
int longestOptionNameString = 0;
for (const QCommandLineOption &option : commandLineOptionList) {
for (const QCommandLineOption &option : qAsConst(options)) {
if (option.flags() & QCommandLineOption::HiddenFromHelp)
continue;
const QStringList optionNames = option.names();
Expand All @@ -1141,14 +1159,14 @@ QString QCommandLineParserPrivate::helpText() const
}
++longestOptionNameString;
auto optionNameIterator = optionNameList.cbegin();
for (const QCommandLineOption &option : commandLineOptionList) {
for (const QCommandLineOption &option : qAsConst(options)) {
if (option.flags() & QCommandLineOption::HiddenFromHelp)
continue;
text += wrapText(*optionNameIterator, longestOptionNameString, option.description());
++optionNameIterator;
}
if (!positionalArgumentDefinitions.isEmpty()) {
if (!commandLineOptionList.isEmpty())
if (!options.isEmpty())
text += nl;
text += QCommandLineParser::tr("Arguments:") + nl;
for (const PositionalArgumentDefinition &arg : positionalArgumentDefinitions)
Expand Down
52 changes: 52 additions & 0 deletions src/gui/kernel/qguiapplication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,58 @@ static void init_plugins(const QList<QByteArray> &pluginList)
}
}

void QGuiApplicationPrivate::addQtOptions(QList<QCommandLineOption> *options)
{
QCoreApplicationPrivate::addQtOptions(options);

#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
const QByteArray sessionType = qgetenv("XDG_SESSION_TYPE");
const bool x11 = sessionType == "x11";
// Technically the x11 aliases are only available if platformName is "xcb", but we can't know that here.
#else
const bool x11 = false;
#endif

options->append(QCommandLineOption(QStringLiteral("platform"),
QGuiApplication::tr("QPA plugin. See QGuiApplication documentation for available options for each plugin."), QStringLiteral("platformName[:options]")));
options->append(QCommandLineOption(QStringLiteral("platformpluginpath"),
QGuiApplication::tr("Path to the platform plugins."), QStringLiteral("path")));
options->append(QCommandLineOption(QStringLiteral("platformtheme"),
QGuiApplication::tr("Platform theme."), QStringLiteral("theme")));
options->append(QCommandLineOption(QStringLiteral("plugin"),
QGuiApplication::tr("Additional plugins to load, can be specified multiple times."), QStringLiteral("plugin")));
options->append(QCommandLineOption(QStringLiteral("qwindowgeometry"),
QGuiApplication::tr("Window geometry for the main window, using the X11-syntax, like 100x100+50+50."), QStringLiteral("geometry")));
options->append(QCommandLineOption(QStringLiteral("qwindowicon"),
QGuiApplication::tr("Default window icon."), QStringLiteral("icon")));
options->append(QCommandLineOption(QStringLiteral("qwindowtitle"),
QGuiApplication::tr("Title of the first window."), QStringLiteral("title")));
options->append(QCommandLineOption(QStringLiteral("reverse"),
QGuiApplication::tr("Sets the application's layout direction to Qt::RightToLeft (debugging helper).")));
options->append(QCommandLineOption(QStringLiteral("session"),
QGuiApplication::tr("Restores the application from an earlier session."), QStringLiteral("session")));

if (x11) {
options->append(QCommandLineOption(QStringLiteral("display"),
QGuiApplication::tr("Display name, overrides $DISPLAY."), QStringLiteral("display")));
options->append(QCommandLineOption(QStringLiteral("name"),
QGuiApplication::tr("Instance name according to ICCCM 4.1.2.5."), QStringLiteral("name")));
options->append(QCommandLineOption(QStringLiteral("nograb"),
QGuiApplication::tr("Disable mouse grabbing (useful in debuggers).")));
options->append(QCommandLineOption(QStringLiteral("dograb"),
QGuiApplication::tr("Force mouse grabbing (even when running in a debugger).")));
options->append(QCommandLineOption(QStringLiteral("visual"),
QGuiApplication::tr("ID of the X11 Visual to use."), QStringLiteral("id")));
// Not using the "QStringList names" solution for those aliases, because it makes the first column too wide
options->append(QCommandLineOption(QStringLiteral("geometry"),
QGuiApplication::tr("Alias for --windowgeometry."), QStringLiteral("geometry")));
options->append(QCommandLineOption(QStringLiteral("icon"),
QGuiApplication::tr("Alias for --windowicon."), QStringLiteral("icon")));
options->append(QCommandLineOption(QStringLiteral("title"),
QGuiApplication::tr("Alias for --windowtitle."), QStringLiteral("title")));
}
}

void QGuiApplicationPrivate::createPlatformIntegration()
{
QHighDpiScaling::initHighDpiScaling();
Expand Down
1 change: 1 addition & 0 deletions src/gui/kernel/qguiapplication_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class Q_GUI_EXPORT QGuiApplicationPrivate : public QCoreApplicationPrivate
virtual void notifyLayoutDirectionChange();
virtual void notifyActiveWindowChange(QWindow *previous);

void addQtOptions(QList<QCommandLineOption> *options) override;
virtual bool shouldQuit() override;

bool shouldQuitInternal(const QWindowList &processedWindows);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ private slots:
void testHelpOption();
void testQuoteEscaping();
void testUnknownOption();
void testHelpAll_data();
void testHelpAll();
};

static char *empty_argv[] = { 0 };
Expand Down Expand Up @@ -542,7 +544,8 @@ void tst_QCommandLineParser::testVersionOption()

static const char expectedOptionsHelp[] =
"Options:\n"
" -h, --help Displays this help.\n"
" -h, --help Displays help on commandline options.\n"
" --help-all Displays help including Qt specific options.\n"
" -v, --version Displays version information.\n"
" --load <url> Load file from URL.\n"
" -o, --output <file> Set output file.\n"
Expand Down Expand Up @@ -576,8 +579,8 @@ void tst_QCommandLineParser::testHelpOption_data()
" 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(" -h, --help Displays help on commandline options.\n",
" -?, -h, --help Displays help on commandline options.\n");
expectedOutput.replace("testhelper/", "testhelper\\");
#endif

Expand Down Expand Up @@ -625,8 +628,8 @@ void tst_QCommandLineParser::testHelpOption()
"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(" -h, --help Displays help on commandline options.\n",
" -?, -h, --help Displays help on commandline options.\n");
expectedResizeHelp.replace("testhelper/", "testhelper\\");
#endif
QCOMPARE(output, QString(expectedResizeHelp));
Expand Down Expand Up @@ -680,6 +683,60 @@ void tst_QCommandLineParser::testUnknownOption()
#endif // QT_CONFIG(process)
}

void tst_QCommandLineParser::testHelpAll_data()
{
QTest::addColumn<QCommandLineParser::SingleDashWordOptionMode>("parsingMode");
QTest::addColumn<QString>("expectedHelpOutput");

QString expectedOutput = QString::fromLatin1(
"Usage: testhelper/qcommandlineparser_test_helper [options] parsingMode command\n"
"Test helper\n"
"\n")
+ QString::fromLatin1(expectedOptionsHelp) +
QString::fromLatin1(
" --qmljsdebugger <value> Activates the QML/JS debugger with a specified\n"
" port. The value must be of format\n"
" port:1234[,block]. \"block\" makes the application\n"
" wait for a connection.\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 help on commandline options.\n",
" -?, -h, --help Displays help on commandline options.\n");
expectedOutput.replace("testhelper/", "testhelper\\");
#endif

QTest::newRow("collapsed") << QCommandLineParser::ParseAsCompactedShortOptions << expectedOutput;
QTest::newRow("long") << QCommandLineParser::ParseAsLongOptions << expectedOutput;
}

void tst_QCommandLineParser::testHelpAll()
{
#if !QT_CONFIG(process)
QSKIP("This test requires QProcess support");
#else
#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED)
QSKIP("Deploying executable applications to file system on Android not supported.");
#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-all");
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.split('\n'), expectedHelpOutput.split('\n')); // easier to debug than the next line, on failure
QCOMPARE(output, expectedHelpOutput);
#endif // QT_CONFIG(process)
}

QTEST_APPLESS_MAIN(tst_QCommandLineParser)
#include "tst_qcommandlineparser.moc"

0 comments on commit 341c8b9

Please sign in to comment.