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