QCommandLine 0.2.0

/home/chary_c/Prog/qcommandline/src/qcommandline.cpp

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