QCommandLine 0.3.0
|
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