phantomjs/src/qcommandline/qcommandline.cpp

536 lines
13 KiB
C++

/* This file is part of QCommandLine
*
* Copyright (C) 2010-2011 Corentin Chary <corentin.chary@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <QtCore/QCoreApplication>
#include <QtCore/QQueue>
#include <QtCore/QVariant>
#include <QtCore/QFileInfo>
#include <QDebug>
#include <iostream>
#include "qcommandline.h"
const QCommandLineConfigEntry QCommandLine::helpEntry = { QCommandLine::Switch, QLatin1Char('h'), QLatin1String("help"), tr("Display this help and exit"), QCommandLine::Optional };
const QCommandLineConfigEntry QCommandLine::versionEntry = { QCommandLine::Switch, QLatin1Char('V'), QLatin1String("version"), tr("Display version and exit"), QCommandLine::Optional };
QCommandLine::QCommandLine(QObject * parent)
: QObject(parent), d(new QCommandLinePrivate)
{
setArguments(QCoreApplication::instance()->arguments());
}
QCommandLine::QCommandLine(const QCoreApplication & app,
const QCommandLineConfig & config,
QObject * parent)
: QObject(parent), d(new QCommandLinePrivate)
{
setArguments(app.arguments());
setConfig(config);
enableHelp(true);
enableVersion(true);
}
QCommandLine::QCommandLine(int argc, char *argv[],
const QCommandLineConfig & config,
QObject * parent)
: QObject(parent), d(new QCommandLinePrivate)
{
setArguments(argc, argv);
setConfig(config);
enableHelp(true);
enableVersion(true);
}
QCommandLine::QCommandLine(const QStringList args,
const QCommandLineConfig & config,
QObject * parent)
: QObject(parent), d(new QCommandLinePrivate)
{
setArguments(args);
setConfig(config);
enableHelp(true);
enableVersion(true);
}
QCommandLine::~QCommandLine()
{
delete d;
}
void
QCommandLine::setConfig(const QCommandLineConfig & config)
{
d->config = config;
}
void
QCommandLine::setConfig(const QCommandLineConfigEntry config[])
{
d->config.clear();
while (config->type) {
d->config << *config;
config++;
}
}
QCommandLineConfig
QCommandLine::config()
{
return d->config;
}
void
QCommandLine::setArguments(int argc, char *argv[])
{
d->args.clear();
for (int i = 0; i < argc; i++)
d->args.append(QLatin1String(argv[i]));
}
void
QCommandLine::setArguments(QStringList args)
{
d->args = args;
}
QStringList
QCommandLine::arguments()
{
return d->args;
}
void
QCommandLine::enableHelp(bool enable)
{
d->help = enable;
}
bool
QCommandLine::helpEnabled()
{
return d->help;
}
void
QCommandLine::enableVersion(bool enable)
{
d->version = enable;
}
bool
QCommandLine::versionEnabled()
{
return d->version;
}
bool
QCommandLine::parse()
{
QMap < QString, QCommandLineConfigEntry > conf;
QMap < QString, QCommandLineConfigEntry > confLong;
QQueue < QCommandLineConfigEntry > params;
QMap < QString, QList < QString > > optionsFound;
QMap < QString, int > switchsFound;
QStringList options, switchs;
QStringList args = d->args;
bool allparam = false;
foreach (QCommandLineConfigEntry entry, d->config) {
#if 0
if (entry.type != QCommandLine::Param && entry.shortName == QLatin1Char('\0'))
qWarning() << QLatin1String("QCommandLine: Empty shortname detected");
if (entry.longName.isEmpty())
qWarning() << QLatin1String("QCommandLine: Empty shortname detected");
if (entry.type != QCommandLine::Param && conf.find(entry.shortName) != conf.end())
qWarning() << QLatin1String("QCommandLine: Duplicated shortname detected ") << entry.shortName;
#endif
if (conf.find(entry.longName) != conf.end())
qWarning() << QLatin1String("QCommandLine: Duplicated longname detected ") << entry.shortName;
if (entry.type == QCommandLine::Param)
params << entry;
else
conf[entry.shortName] = entry;
confLong[entry.longName] = entry;
}
if (d->help) {
conf[helpEntry.shortName] = helpEntry;
confLong[helpEntry.longName] = helpEntry;
}
if (d->version) {
conf[versionEntry.shortName] = versionEntry;
confLong[versionEntry.longName] = versionEntry;
}
for (int i = 1; i < args.size(); ++i) {
QString arg = args[i];
bool param = true, shrt = false, stay = false, forward = false;
/* A '+' was found, all remaining options are params */
if (allparam)
param = true;
else if (arg.startsWith(QLatin1String("--"))) {
param = false;
shrt = false;
arg = arg.mid(2);
} else if (arg.startsWith(QLatin1Char('-')) || arg.startsWith(QLatin1Char('+'))) {
if (arg.startsWith(QLatin1Char('+')))
allparam = true;
param = false;
shrt = true;
/* Handle stacked args like `tar -xzf` */
if (arg.size() > 2) {
args[i] = arg.mid(0, 2);
args.insert(i + 1, arg.mid(0, 1) + arg.mid(2));
arg = arg.mid(1, 1);
} else {
arg = arg.mid(1);
}
}
/* Handle params */
if (param) {
if (!params.size()) {
emit parseError(tr("Unknown param: %1").arg(arg));
return false;
}
QCommandLineConfigEntry & entry = params.first();
if (entry.flags & QCommandLine::Mandatory) {
entry.flags = (QCommandLine::Flags) (entry.flags & ~QCommandLine::Mandatory);
entry.flags = (QCommandLine::Flags) (entry.flags | QCommandLine::Optional);
}
if (entry.flags & QCommandLine::ParameterFence) {
allparam = true;
}
emit paramFound(entry.longName, arg);
if (!(entry.flags & QCommandLine::Multiple))
params.dequeue();
} else { /* Options and switchs* */
QString key;
QString value;
int idx = arg.indexOf(QLatin1Char('='));
if (idx != -1) {
key = arg.mid(0, idx);
value = arg.mid(idx + 1);
} else {
key = arg;
}
QMap < QString, QCommandLineConfigEntry > & c = shrt ? conf : confLong;
if (c.find(key) == c.end()) {
emit parseError(tr("Unknown option: %1").arg(key));
return false;
}
QCommandLineConfigEntry & entry = c[key];
if (entry.type == QCommandLine::Switch) {
if (!switchsFound.contains(entry.longName))
switchs << entry.longName;
if (entry.flags & QCommandLine::Multiple)
switchsFound[entry.longName]++;
else
switchsFound[entry.longName] = 1;
} else {
if (stay) {
emit parseError(tr("Option %1 need a value").arg(key));
return false;
}
if (idx == -1) {
if (i+1 < args.size() && !args[i+1].startsWith(QLatin1Char('-'))) {
value = args[i+1];
forward = true;
} else {
emit parseError(tr("Option %1 need a value").arg(key));
return false;
}
}
if (!optionsFound.contains(entry.longName))
options << entry.longName;
if (!(entry.flags & QCommandLine::Multiple))
optionsFound[entry.longName].clear();
optionsFound[entry.longName].append(value);
}
if (entry.flags & QCommandLine::Mandatory) {
entry.flags = (QCommandLine::Flags) (entry.flags & ~QCommandLine::Mandatory);
entry.flags = (QCommandLine::Flags) (entry.flags | QCommandLine::Optional);
conf[entry.shortName] = entry;
confLong[entry.shortName] = entry;
}
}
/* Stay here, stacked args */
if (stay)
i--;
else if (forward)
i++;
}
foreach (QCommandLineConfigEntry entry, params) {
if (entry.flags & QCommandLine::Mandatory) {
emit parseError(tr("Param %1 is mandatory").arg(entry.longName));
return false;
}
}
foreach (QCommandLineConfigEntry entry, conf.values()) {
if (entry.flags & QCommandLine::Mandatory) {
QString type;
if (entry.type == QCommandLine::Switch)
type = tr("Switch");
if (entry.type == QCommandLine::Option)
type = tr("Option");
emit parseError(tr("%1 %2 is mandatory").arg(type).arg(entry.longName));
return false;
}
}
foreach (QString key, switchs) {
for (int i = 0; i < switchsFound[key]; i++) {
if (d->help && key == helpEntry.longName)
showHelp();
if (d->version && key == versionEntry.longName)
showVersion();
emit switchFound(key);
}
}
foreach (QString key, options) {
foreach (QString opt, optionsFound[key])
emit optionFound(key, opt);
} return true;
}
void
QCommandLine::addOption(const QChar & shortName,
const QString & longName,
const QString & descr,
QCommandLine::Flags flags)
{
QCommandLineConfigEntry entry;
entry.type = QCommandLine::Option;
entry.shortName = shortName;
entry.longName = longName;
entry.descr = descr;
entry.flags = flags;
d->config << entry;
}
void
QCommandLine::addSwitch(const QChar & shortName,
const QString & longName,
const QString & descr,
QCommandLine::Flags flags)
{
QCommandLineConfigEntry entry;
entry.type = QCommandLine::Switch;
entry.shortName = shortName;
entry.longName = longName;
entry.descr = descr;
entry.flags = flags;
d->config << entry;
}
void
QCommandLine::addParam(const QString & name,
const QString & descr,
QCommandLine::Flags flags)
{
QCommandLineConfigEntry entry;
entry.type = QCommandLine::Param;
entry.longName = name;
entry.descr = descr;
entry.flags = flags;
d->config << entry;
}
void
QCommandLine::removeOption(const QString & name)
{
int i;
for (i = 0; i < d->config.size(); ++i) {
if (d->config[i].type == QCommandLine::Option &&
(d->config[i].shortName == name.at(0) || d->config[i].longName == name)) {
d->config.removeAt(i);
return ;
}
}
}
void
QCommandLine::removeSwitch(const QString & name)
{
int i;
for (i = 0; i < d->config.size(); ++i) {
if (d->config[i].type == QCommandLine::Switch &&
(d->config[i].shortName == name.at(0) || d->config[i].longName == name)) {
d->config.removeAt(i);
return ;
}
}
}
void
QCommandLine::removeParam(const QString & name)
{
int i;
for (i = 0; i < d->config.size(); ++i) {
if (d->config[i].type == QCommandLine::Param &&
(d->config[i].shortName == name.at(0) || d->config[i].longName == name)) {
d->config.removeAt(i);
return ;
}
}
}
QString
QCommandLine::help(bool logo)
{
QString h;
if (logo)
h = version() + QLatin1String("\n");
h = QLatin1String("Usage:\n ");
/* Executable name */
if (!d->args.isEmpty())
h += QFileInfo(d->args[0]).baseName();
else
h += QCoreApplication::applicationName();
h.append(QLatin1String(" [switchs] [options]"));
/* Arguments, short */
foreach (QCommandLineConfigEntry entry, d->config) {
if (entry.type == QCommandLine::Option) {
if (entry.flags & QCommandLine::Mandatory)
h.append(QLatin1String(" --") + entry.longName + QLatin1String("=<val>"));
}
if (entry.type == QCommandLine::Param) {
h.append(QLatin1String(" "));
if (entry.flags & QCommandLine::Optional)
h.append(QLatin1String("["));
h.append(entry.longName);
if (entry.flags & QCommandLine::Multiple)
h.append(QLatin1String(" [") + entry.longName + QLatin1String(" [...]]"));
if (entry.flags & QCommandLine::Optional)
h.append(QLatin1String("]"));
}
}
h.append(QLatin1String("\n\n"));
h.append(QLatin1String("Options:\n"));
QStringList vals;
QStringList descrs;
int max = 0;
foreach (QCommandLineConfigEntry entry, d->config) {
QString val;
if (entry.type == QCommandLine::Option) {
if (entry.shortName != QLatin1Char('\0'))
val = QLatin1String("-") + QString(entry.shortName) + QLatin1Char(',');
val += QLatin1String("--") + entry.longName + QLatin1String("=<val>");
}
if (entry.type == QCommandLine::Switch)
val = QLatin1String("-") + QString(entry.shortName) + QLatin1String(",--") + entry.longName;
#if 0
if (entry.type == QCommandLine::Param)
val = entry.longName;
#endif
if (val.size() > max)
max = val.size();
vals.append(val);
descrs.append(entry.descr + QLatin1String("\n"));
}
for (int i = 0; i < vals.size(); ++i) {
if (vals[i].isEmpty()) continue;
h.append(QLatin1String(" "));
h.append(vals[i]);
h.append(QString(QLatin1String(" ")).repeated(max - vals[i].size() + 2));
h.append(descrs[i]);
}
#if 0
h.append(tr("\nMandatory arguments to long options are mandatory for short options too.\n"));
#endif
return h;
}
QString
QCommandLine::version()
{
QString v;
v = QCoreApplication::applicationName() + QLatin1Char(' ');
v += QCoreApplication::applicationVersion();
if (!QCoreApplication::organizationDomain().isEmpty()
|| !QCoreApplication::organizationName().isEmpty())
v = v + QLatin1String(" - ") +
QCoreApplication::organizationDomain() + QLatin1String(" ") +
QCoreApplication::organizationDomain();
return v + QLatin1Char('\n');
}
void
QCommandLine::showHelp(bool quit, int returnCode)
{
std::cerr << qPrintable(help());
if (quit) {
// Can't call QApplication::exit() here, because we may be called before app.exec()
exit(returnCode);
}
}
void
QCommandLine::showVersion(bool quit, int returnCode)
{
std::cerr << qPrintable(version());
if (quit) {
exit(returnCode);
}
}