QCommandLine 0.3.0
/home/iksaif/dev/qcommandline/src/qcommandline.cpp
00001 /* This file is part of QCommandLine
00002  *
00003  * Copyright (C) 2010-2011 Corentin Chary <corentin.chary@gmail.com>
00004  *
00005  * This library is free software; you can redistribute it and/or
00006  * modify it under the terms of the GNU Library General Public
00007  * License as published by the Free Software Foundation; either
00008  * version 2 of the License, or (at your option) any later version.
00009  *
00010  * This library is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013  * Library General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU Library General Public License
00016  * along with this library; see the file COPYING.LIB.  If not, write to
00017  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00018  * Boston, MA 02110-1301, USA.
00019  */
00020 
00021 #include <QtCore/QCoreApplication>
00022 #include <QtCore/QQueue>
00023 #include <QtCore/QVariant>
00024 #include <QtCore/QFileInfo>
00025 #include <QDebug>
00026 #include <iostream>
00027 
00028 #include "qcommandline.h"
00029 
00030 const QCommandLineConfigEntry QCommandLine::helpEntry = { QCommandLine::Switch, QLatin1Char('h'), QLatin1String("help"), tr("Display this help and exit"), QCommandLine::Optional };
00031 
00032 const QCommandLineConfigEntry QCommandLine::versionEntry = { QCommandLine::Switch, QLatin1Char('V'), QLatin1String("version"), tr("Display version and exit"), QCommandLine::Optional };
00033 
00034 QCommandLine::QCommandLine(QObject * parent)
00035   : QObject(parent), d(new QCommandLinePrivate)
00036 {
00037   setArguments(QCoreApplication::instance()->arguments());
00038 }
00039 
00040 QCommandLine::QCommandLine(const QCoreApplication & app,
00041                            const QCommandLineConfig & config,
00042                            QObject * parent)
00043   : QObject(parent), d(new QCommandLinePrivate)
00044 {
00045   setArguments(app.arguments());
00046   setConfig(config);
00047   enableHelp(true);
00048   enableVersion(true);
00049 }
00050 
00051 QCommandLine::QCommandLine(int argc, char *argv[],
00052                            const QCommandLineConfig & config,
00053                            QObject * parent)
00054   : QObject(parent), d(new QCommandLinePrivate)
00055 {
00056   setArguments(argc, argv);
00057   setConfig(config);
00058   enableHelp(true);
00059   enableVersion(true);
00060 }
00061 
00062 QCommandLine::QCommandLine(const QStringList args,
00063                            const QCommandLineConfig & config,
00064                            QObject * parent)
00065   : QObject(parent), d(new QCommandLinePrivate)
00066 {
00067   setArguments(args);
00068   setConfig(config);
00069   enableHelp(true);
00070   enableVersion(true);
00071 }
00072 
00073 QCommandLine::~QCommandLine()
00074 {
00075   delete d;
00076 }
00077 
00078 void
00079 QCommandLine::setConfig(const QCommandLineConfig & config)
00080 {
00081   d->config = config;
00082 }
00083 
00084 void
00085 QCommandLine::setConfig(const QCommandLineConfigEntry config[])
00086 {
00087   d->config.clear();
00088 
00089   while (config->type) {
00090     d->config << *config;
00091     config++;
00092   }
00093 }
00094 
00095 QCommandLineConfig
00096 QCommandLine::config()
00097 {
00098   return d->config;
00099 }
00100 
00101 void
00102 QCommandLine::setArguments(int argc, char *argv[])
00103 {
00104   d->args.clear();
00105   for (int i = 0; i < argc; i++)
00106     d->args.append(QLatin1String(argv[i]));
00107 }
00108 
00109 void
00110 QCommandLine::setArguments(QStringList args)
00111 {
00112   d->args = args;
00113 }
00114 
00115 QStringList
00116 QCommandLine::arguments()
00117 {
00118   return d->args;
00119 }
00120 
00121 void
00122 QCommandLine::enableHelp(bool enable)
00123 {
00124   d->help = enable;
00125 }
00126 
00127 bool
00128 QCommandLine::helpEnabled()
00129 {
00130   return d->help;
00131 }
00132 
00133 void
00134 QCommandLine::enableVersion(bool enable)
00135 {
00136   d->version = enable;
00137 }
00138 
00139 bool
00140 QCommandLine::versionEnabled()
00141 {
00142   return d->version;
00143 }
00144 
00145 bool
00146 QCommandLine::parse()
00147 {
00148   QMap < QString, QCommandLineConfigEntry > conf;
00149   QMap < QString, QCommandLineConfigEntry > confLong;
00150   QQueue < QCommandLineConfigEntry > params;
00151   QMap < QString, QList < QString > > optionsFound;
00152   QMap < QString, int > switchsFound;
00153   QStringList options, switchs;
00154   QStringList args = d->args;
00155 
00156   bool allparam = false;
00157 
00158   foreach (QCommandLineConfigEntry entry, d->config) {
00159     if (entry.type != QCommandLine::Param && entry.shortName == QLatin1Char('\0'))
00160       qWarning() << QLatin1String("QCommandLine: Empty shortname detected");
00161     if (entry.longName.isEmpty())
00162       qWarning() << QLatin1String("QCommandLine: Empty shortname detected");
00163     if (entry.type != QCommandLine::Param && conf.find(entry.shortName) != conf.end())
00164       qWarning() << QLatin1String("QCommandLine: Duplicated shortname detected ") << entry.shortName;
00165     if (conf.find(entry.longName) != conf.end())
00166       qWarning() << QLatin1String("QCommandLine: Duplicated longname detected ") << entry.shortName;
00167 
00168     if (entry.type == QCommandLine::Param)
00169       params << entry;
00170     else
00171       conf[entry.shortName] = entry;
00172     confLong[entry.longName] = entry;
00173   }
00174 
00175   if (d->help) {
00176     conf[helpEntry.shortName] = helpEntry;
00177     confLong[helpEntry.longName] = helpEntry;
00178   }
00179 
00180   if (d->version) {
00181     conf[versionEntry.shortName] = versionEntry;
00182     confLong[versionEntry.longName] = versionEntry;
00183   }
00184 
00185   for (int i = 1; i < args.size(); ++i) {
00186     QString arg = args[i];
00187     bool param = true, shrt = false, stay = false, forward = false;
00188 
00189     /* A '+' was found, all remaining options are params */
00190     if (allparam)
00191       param = true;
00192     else if (arg.startsWith(QLatin1String("--"))) {
00193       param = false;
00194       shrt = false;
00195       arg = arg.mid(2);
00196     } else if (arg.startsWith(QLatin1Char('-')) || arg.startsWith(QLatin1Char('+'))) {
00197       if (arg.startsWith(QLatin1Char('+')))
00198         allparam = true;
00199       param = false;
00200       shrt = true;
00201       /* Handle stacked args like `tar -xzf` */
00202       if (arg.size() > 2) {
00203         args[i] = arg.mid(0, 2);
00204         args.insert(i + 1, arg.mid(0, 1) + arg.mid(2));
00205         arg = arg.mid(1, 1);
00206       } else {
00207         arg = arg.mid(1);
00208       }
00209     }
00210 
00211     /* Handle params */
00212     if (param) {
00213       if (!params.size()) {
00214         emit parseError(tr("Unknown param: %1").arg(arg));
00215         return false;
00216       }
00217 
00218       QCommandLineConfigEntry & entry = params.first();
00219 
00220       if (entry.flags & QCommandLine::Mandatory) {
00221         entry.flags = (QCommandLine::Flags) (entry.flags & ~QCommandLine::Mandatory);
00222         entry.flags = (QCommandLine::Flags) (entry.flags | QCommandLine::Optional);
00223       }
00224 
00225       emit paramFound(entry.longName, arg);
00226 
00227       if (!(entry.flags & QCommandLine::Multiple))
00228         params.dequeue();
00229 
00230     } else { /* Options and switchs* */
00231       QString key;
00232       QString value;
00233       int idx = arg.indexOf(QLatin1Char('='));
00234 
00235       if (idx != -1) {
00236         key = arg.mid(0, idx);
00237         value = arg.mid(idx + 1);
00238       } else {
00239         key = arg;
00240       }
00241 
00242       QMap < QString, QCommandLineConfigEntry > & c = shrt ? conf : confLong;
00243 
00244       if (c.find(key) == c.end()) {
00245         emit parseError(tr("Unknown option: %1").arg(key));
00246         return false;
00247       }
00248 
00249       QCommandLineConfigEntry & entry = c[key];
00250 
00251       if (entry.type == QCommandLine::Switch) {
00252         if (!switchsFound.contains(entry.longName))
00253           switchs << entry.longName;
00254         if (entry.flags & QCommandLine::Multiple)
00255           switchsFound[entry.longName]++;
00256         else
00257           switchsFound[entry.longName] = 1;
00258       } else {
00259         if (stay) {
00260           emit parseError(tr("Option %1 need a value").arg(key));
00261           return false;
00262         }
00263 
00264         if (idx == -1) {
00265           if (i+1 < args.size() && !args[i+1].startsWith(QLatin1Char('-'))) {
00266             value = args[i+1];
00267             forward = true;
00268           } else {
00269             emit parseError(tr("Option %1 need a value").arg(key));
00270             return false;
00271           }
00272         }
00273 
00274         if (!optionsFound.contains(entry.longName))
00275           options << entry.longName;
00276         if (!(entry.flags & QCommandLine::Multiple))
00277           optionsFound[entry.longName].clear();
00278         optionsFound[entry.longName].append(value);
00279       }
00280 
00281       if (entry.flags & QCommandLine::Mandatory) {
00282         entry.flags = (QCommandLine::Flags) (entry.flags & ~QCommandLine::Mandatory);
00283         entry.flags = (QCommandLine::Flags) (entry.flags | QCommandLine::Optional);
00284         conf[entry.shortName] = entry;
00285         confLong[entry.shortName] = entry;
00286       }
00287     }
00288     /* Stay here, stacked args */
00289     if (stay)
00290       i--;
00291     else if (forward)
00292       i++;
00293   }
00294 
00295   foreach (QCommandLineConfigEntry entry, params) {
00296     if (entry.flags & QCommandLine::Mandatory) {
00297       emit parseError(tr("Param %1 is mandatory").arg(entry.longName));
00298       return false;
00299     }
00300   }
00301 
00302   foreach (QCommandLineConfigEntry entry, conf.values()) {
00303     if (entry.flags & QCommandLine::Mandatory) {
00304       QString type;
00305 
00306       if (entry.type == QCommandLine::Switch)
00307         type = tr("Switch");
00308       if (entry.type == QCommandLine::Option)
00309         type = tr("Option");
00310 
00311       emit parseError(tr("%1 %2 is mandatory").arg(type).arg(entry.longName));
00312       return false;
00313     }
00314   }
00315 
00316   foreach (QString key, switchs) {
00317     for (int i = 0; i < switchsFound[key]; i++) {
00318       if (d->help && key == helpEntry.longName)
00319         showHelp();
00320       if (d->version && key == versionEntry.longName)
00321         showVersion();
00322       emit switchFound(key);
00323     }
00324   }
00325 
00326   foreach (QString key, options) {
00327     foreach (QString opt, optionsFound[key])
00328       emit optionFound(key, opt);
00329   }  return true;
00330 }
00331 
00332 void
00333 QCommandLine::addOption(const QChar & shortName,
00334                         const QString & longName,
00335                         const QString & descr,
00336                         QCommandLine::Flags flags)
00337 {
00338   QCommandLineConfigEntry entry;
00339 
00340   entry.type = QCommandLine::Option;
00341   entry.shortName = shortName;
00342   entry.longName = longName;
00343   entry.descr = descr;
00344   entry.flags = flags;
00345   d->config << entry;
00346 }
00347 
00348 void
00349 QCommandLine::addSwitch(const QChar & shortName,
00350                         const QString & longName,
00351                         const QString & descr,
00352                         QCommandLine::Flags flags)
00353 {
00354   QCommandLineConfigEntry entry;
00355 
00356   entry.type = QCommandLine::Switch;
00357   entry.shortName = shortName;
00358   entry.longName = longName;
00359   entry.descr = descr;
00360   entry.flags = flags;
00361   d->config << entry;
00362 }
00363 
00364 void
00365 QCommandLine::addParam(const QString & name,
00366                        const QString & descr,
00367                        QCommandLine::Flags flags)
00368 {
00369   QCommandLineConfigEntry entry;
00370 
00371   entry.type = QCommandLine::Param;
00372   entry.longName = name;
00373   entry.descr = descr;
00374   entry.flags = flags;
00375   d->config << entry;
00376 }
00377 
00378 void
00379 QCommandLine::removeOption(const QString & name)
00380 {
00381   int i;
00382 
00383   for (i = 0; i < d->config.size(); ++i) {
00384     if (d->config[i].type == QCommandLine::Option &&
00385         (d->config[i].shortName == name.at(0) || d->config[i].longName == name)) {
00386       d->config.removeAt(i);
00387       return ;
00388     }
00389   }
00390 }
00391 
00392 void
00393 QCommandLine::removeSwitch(const QString & name)
00394 {
00395   int i;
00396 
00397   for (i = 0; i < d->config.size(); ++i) {
00398     if (d->config[i].type == QCommandLine::Switch &&
00399         (d->config[i].shortName == name.at(0) || d->config[i].longName == name)) {
00400       d->config.removeAt(i);
00401       return ;
00402     }
00403   }
00404 }
00405 
00406 void
00407 QCommandLine::removeParam(const QString & name)
00408 {
00409   int i;
00410 
00411   for (i = 0; i < d->config.size(); ++i) {
00412     if (d->config[i].type == QCommandLine::Param &&
00413         (d->config[i].shortName == name.at(0) || d->config[i].longName == name)) {
00414       d->config.removeAt(i);
00415       return ;
00416     }
00417   }
00418 }
00419 
00420 
00421 QString
00422 QCommandLine::help(bool logo)
00423 {
00424   QString h;
00425 
00426   if (logo)
00427     h = version() + QLatin1String("\n");
00428   h = QLatin1String("Usage:\n   ");
00429   /* Executable name */
00430   if (!d->args.isEmpty())
00431     h += QFileInfo(d->args[0]).baseName();
00432   else
00433     h += QCoreApplication::applicationName();
00434   h.append(QLatin1String(" [switchs] [options]"));
00435   /* Arguments, short */
00436   foreach (QCommandLineConfigEntry entry, d->config) {
00437     if (entry.type == QCommandLine::Option) {
00438       if (entry.flags & QCommandLine::Mandatory)
00439         h.append(QLatin1String(" --") + entry.longName + QLatin1String("=<val>"));
00440     }
00441     if (entry.type == QCommandLine::Param) {
00442       h.append(QLatin1String(" "));
00443       if (entry.flags & QCommandLine::Optional)
00444         h.append(QLatin1String("["));
00445       h.append(entry.longName);
00446       if (entry.flags & QCommandLine::Multiple)
00447         h.append(QLatin1String(" [") + entry.longName + QLatin1String(" [...]]"));
00448       if (entry.flags & QCommandLine::Optional)
00449         h.append(QLatin1String("]"));
00450     }
00451   }
00452   h.append(QLatin1String("\n\n"));
00453 
00454   h.append(QLatin1String("Options:\n"));
00455 
00456   QStringList vals;
00457   QStringList descrs;
00458   int max = 0;
00459 
00460   foreach (QCommandLineConfigEntry entry, d->config) {
00461     QString val;
00462 
00463     if (entry.type == QCommandLine::Option)
00464       val = QLatin1String("-") + QString(entry.shortName) +
00465         QLatin1String(",--") + entry.longName + QLatin1String("=<val>");
00466     if (entry.type == QCommandLine::Switch)
00467       val = QLatin1String("-") + QString(entry.shortName) + QLatin1String(",--") + entry.longName;
00468     if (entry.type == QCommandLine::Param)
00469       val = entry.longName;
00470 
00471     if (val.size() > max)
00472       max = val.size();
00473 
00474     vals.append(val);
00475     descrs.append(entry.descr + QLatin1String("\n"));
00476   }
00477 
00478   for (int i = 0; i < vals.size(); ++i) {
00479     h.append(QLatin1String("  "));
00480     h.append(vals[i]);
00481     h.append(QString(QLatin1String(" ")).repeated(max - vals[i].size() + 2));
00482     h.append(descrs[i]);
00483   }
00484 
00485   h.append(tr("\nMandatory arguments to long options are mandatory for short options too.\n"));
00486 
00487   return h;
00488 }
00489 
00490 QString
00491 QCommandLine::version()
00492 {
00493   QString v;
00494 
00495   v = QCoreApplication::applicationName() + QLatin1Char(' ');
00496   v += QCoreApplication::applicationVersion();
00497   if (!QCoreApplication::organizationDomain().isEmpty()
00498       || !QCoreApplication::organizationName().isEmpty())
00499     v = v + QLatin1String(" - ") +
00500       QCoreApplication::organizationDomain() + QLatin1String(" ") +
00501       QCoreApplication::organizationDomain();
00502   return v + QLatin1Char('\n');
00503 }
00504 
00505 void
00506 QCommandLine::showHelp(bool quit, int returnCode)
00507 {
00508   std::cerr << qPrintable(help());
00509   if (quit) {
00510     // Can't call QApplication::exit() here, because we may be called before app.exec()
00511     exit(returnCode);
00512   }
00513 }
00514 
00515 void
00516 QCommandLine::showVersion(bool quit, int returnCode)
00517 {
00518   std::cerr << qPrintable(version());
00519   if (quit) {
00520     exit(returnCode);
00521   }
00522 }
00523