00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "AppUtils/AppCmdLine.h"
00023
00024
00025
00026
00027 #include <algorithm>
00028 #include <functional>
00029 #include <iterator>
00030 #include <fstream>
00031 #include <iomanip>
00032 #include <set>
00033 #include <boost/lexical_cast.hpp>
00034
00035
00036
00037
00038 #include "AppUtils/AppCmdArgBase.h"
00039 #include "AppUtils/AppCmdExceptions.h"
00040 #include "AppUtils/AppCmdOptBase.h"
00041 #include "AppUtils/AppCmdOptBool.h"
00042 #include "AppUtils/AppCmdOptList.h"
00043 #include "AppUtils/AppCmdWordWrap.h"
00044 using std::ios;
00045 using std::ostream;
00046 using std::setw;
00047
00048
00049
00050
00051
00052 namespace {
00053
00054 bool
00055 isHelpOption(const std::string& optname)
00056 {
00057 return optname == "help" or optname == "h" or optname == "?";
00058 }
00059
00060
00061 AppUtils::AppCmdOptBool helpOpt("h,?,help", "print help message");
00062
00063 }
00064
00065
00066
00067
00068
00069 namespace AppUtils {
00070
00071
00072
00073
00074 AppCmdLine::AppCmdLine(const std::string& argv0)
00075 : AppCmdOptGroup("General options")
00076 , _groups()
00077 , _positionals()
00078 , _argv0(argv0)
00079 , _optionsFile(0)
00080 , _argv()
00081 , _helpWanted(false)
00082 , _iter()
00083 , _nWordsLeft(0)
00084 {
00085 this->addOption(::helpOpt);
00086 }
00087
00088
00089 AppCmdLine::~AppCmdLine()
00090 {
00091 }
00092
00093
00094 void
00095 AppCmdLine::addGroup(AppCmdOptGroup& group)
00096 {
00097 _groups.push_back(&group);
00098 }
00099
00100
00101
00102
00103
00104
00105 void
00106 AppCmdLine::addArgument(AppCmdArgBase& arg)
00107 {
00108 _positionals.push_back(&arg);
00109 }
00110
00111
00112
00113
00114
00115
00116
00117 void
00118 AppCmdLine::setOptionsFile(AppCmdOptList<std::string>& option)
00119 {
00120
00121 if (_optionsFile) {
00122 throw AppCmdException("options file option already defined, cannot re-define");
00123 }
00124
00125
00126 _optionsFile = &option;
00127 }
00128
00129
00130
00131
00132
00133 void
00134 AppCmdLine::parse(int argc, char* argv[])
00135 {
00136 _argv.clear();
00137 _argv.insert(_argv.end(), argv + 1, argv + argc);
00138
00139 doParse();
00140 }
00141 void
00142 AppCmdLine::parse(int argc, const char* argv[])
00143 {
00144 _argv.clear();
00145 _argv.insert(_argv.end(), argv + 1, argv + argc);
00146
00147 doParse();
00148 }
00149
00150
00151
00152
00153
00154
00155 bool
00156 AppCmdLine::helpWanted() const
00157 {
00158 return _helpWanted;
00159 }
00160
00161
00162
00163
00164 void
00165 AppCmdLine::usage(std::ostream& out) const
00166 {
00167 out.setf(ios::left, ios::adjustfield);
00168
00169
00170 OptionsList options = this->options();
00171 for (GroupsList::const_iterator git = _groups.begin(); git != _groups.end(); ++ git) {
00172 const OptionsList& groupOptions = (*git)->options();
00173 options.insert(options.end(), groupOptions.begin(), groupOptions.end());
00174 }
00175
00176 out << "\nUsage: " << _argv0;
00177 if (!options.empty()) {
00178 out << " [options]";
00179 }
00180 for (PositionalsList::const_iterator it = _positionals.begin(); it != _positionals.end(); ++it) {
00181 bool required = (*it)->isRequired();
00182 out << (required ? " " : " [") << (*it)->name() << ((*it)->maxWords() > 1 ? " ..." : "") << (required ? "" : "]");
00183 }
00184 out << '\n';
00185
00186 if (!options.empty()) {
00187
00188
00189 size_t optLen = 0;
00190 size_t nameLen = 0;
00191 for (OptionsList::const_iterator it = options.begin(); it != options.end(); ++it) {
00192 const std::vector<std::string>& optnames = (*it)->options();
00193 size_t moptLen = optnames.size() - 1;
00194 for (std::vector<std::string>::const_iterator oit = optnames.begin(); oit != optnames.end(); ++ oit) {
00195 moptLen += oit->size() + 1;
00196 if (oit->size() > 1) {
00197 moptLen += 1;
00198 }
00199 }
00200 size_t thisNameLen = (*it)->name().size();
00201 if (optLen < moptLen) optLen = moptLen;
00202 if (nameLen < thisNameLen) nameLen = thisNameLen;
00203 }
00204
00205 if (_groups.empty()) {
00206 formatOptGroup(out, "Available options", this->options(), optLen, nameLen);
00207 } else {
00208 formatOptGroup(out, this->groupName(), this->options(), optLen, nameLen);
00209 for (GroupsList::const_iterator git = _groups.begin(); git != _groups.end(); ++ git) {
00210 formatOptGroup(out, (*git)->groupName(), (*git)->options(), optLen, nameLen);
00211 }
00212 }
00213 }
00214
00215 if (!_positionals.empty()) {
00216
00217 size_t nameLen = 0;
00218 for (PositionalsList::const_iterator it = _positionals.begin(); it != _positionals.end(); ++it) {
00219 if (nameLen < (*it)->name().size()) nameLen = (*it)->name().size();
00220 }
00221
00222 AppCmdWordWrap ww;
00223 const int width = ww.pageWidth();
00224 const int width2 = width/2;
00225 int descrLen = width - nameLen - 7;
00226 bool nl = false;
00227 if (descrLen < width2) {
00228 descrLen = width2;
00229 nl = true;
00230 }
00231
00232
00233 out << "\n Positional parameters:\n";
00234 for (PositionalsList::const_iterator it = _positionals.begin(); it != _positionals.end(); ++it) {
00235
00236 out << " " << setw(nameLen) << (*it)->name().c_str() << " - ";
00237
00238 std::vector<std::string> descr = ww.wrap((*it)->description(), descrLen);
00239 for (std::vector<std::string>::const_iterator it = descr.begin(); it != descr.end(); ++ it) {
00240 if (it == descr.begin() and nl) out << '\n';
00241 if (it != descr.begin() or nl) out << setw(width-descrLen) << "";
00242 out << *it << '\n';
00243 }
00244 }
00245 }
00246
00247 out << "\n";
00248 out.setf(ios::right, ios::adjustfield);
00249 }
00250
00251
00252
00253
00254 std::string
00255 AppCmdLine::cmdline() const
00256 {
00257 std::string cmdl = _argv0;
00258 for (StringList::const_iterator i = _argv.begin(); i != _argv.end(); ++i) {
00259 const std::string& arg = *i;
00260 if (arg.find_first_of(" \t\n\"") != std::string::npos) {
00261 cmdl += " '";
00262 cmdl += arg;
00263 cmdl += "'";
00264 } else {
00265 cmdl += " ";
00266 cmdl += arg;
00267 }
00268 }
00269 return cmdl;
00270 }
00271
00272
00273 void
00274 AppCmdLine::doParse()
00275 {
00276 _helpWanted = 0;
00277
00278
00279 OptionsList options = this->options();
00280 for (GroupsList::const_iterator git = _groups.begin(); git != _groups.end(); ++ git) {
00281 const OptionsList& groupOptions = (*git)->options();
00282 options.insert(options.end(), groupOptions.begin(), groupOptions.end());
00283 }
00284
00285
00286 if (_optionsFile) {
00287 if (std::find(options.begin(), options.end(), _optionsFile) == options.end()) {
00288
00289 this->addOption(*_optionsFile);
00290 options.push_back(_optionsFile);
00291 }
00292 }
00293
00294
00295 std::set<std::string> allNames;
00296 for (OptionsList::const_iterator oit = options.begin(); oit != options.end(); ++ oit) {
00297 const std::vector<std::string>& optnames = (*oit)->options();
00298 for (std::vector<std::string>::const_iterator it = optnames.begin(); it != optnames.end(); ++it) {
00299 if (allNames.count(*it)) {
00300 throw AppCmdOptDefinedException(*it);
00301 }
00302 allNames.insert(*it);
00303 }
00304 }
00305 allNames.clear();
00306
00307
00308
00309 for (PositionalsList::const_iterator it = _positionals.begin(); it != _positionals.end(); ++ it) {
00310
00311 if (it != _positionals.begin()) {
00312
00313 AppCmdArgBase* arg = *it;
00314 AppCmdArgBase* argPrev = *(it-1);
00315
00316
00317 if (arg->isRequired() and not argPrev->isRequired()) {
00318 throw AppCmdArgOrderException(arg->name());
00319 }
00320
00321 }
00322 }
00323
00324
00325 std::for_each(options.begin(), options.end(), std::mem_fun(&AppCmdOptBase::reset));
00326 std::for_each(_positionals.begin(), _positionals.end(), std::mem_fun(&AppCmdArgBase::reset));
00327
00328
00329 parseOptions(options);
00330 if (_helpWanted) {
00331 return;
00332 }
00333
00334
00335 parseOptionsFile(options);
00336
00337
00338 parseArgs();
00339 }
00340
00341
00342 void
00343 AppCmdLine::parseOptions(const OptionsList& options)
00344 {
00345 _iter = _argv.begin();
00346 _nWordsLeft = _argv.size();
00347 while (_iter != _argv.end()) {
00348
00349 const std::string& word = *_iter;
00350
00351 if (word == "--") {
00352
00353
00354 ++_iter;
00355 break;
00356
00357 } else if (word.size() > 2 && word[0] == '-' && word[1] == '-') {
00358
00359
00360 const std::string optname(word, 2, word.find('=') - 2);
00361
00362
00363 if (optname.size() < 2) {
00364 throw AppCmdOptUnknownException(optname);
00365 }
00366
00367
00368 if (::isHelpOption(optname)) {
00369 _helpWanted = true;
00370 break;
00371 }
00372
00373
00374 AppCmdOptBase* option = findOpt(optname, options);
00375 if (!option) {
00376 throw AppCmdOptUnknownException(optname);
00377 }
00378
00379
00380 std::string value;
00381 if (option->hasArgument()) {
00382
00383 std::string::size_type eqpos = word.find('=');
00384 if (eqpos != std::string::npos) {
00385 value = std::string(word, eqpos + 1);
00386 } else {
00387 ++_iter;
00388 --_nWordsLeft;
00389 value = *_iter;
00390 }
00391 }
00392
00393
00394 option->setValue(value);
00395
00396 } else if (word.size() > 1 && word[0] == '-') {
00397
00398
00399 std::string optname(1, word[1]);
00400
00401
00402 if (::isHelpOption(optname)) {
00403 _helpWanted = true;
00404 return;
00405 }
00406
00407
00408 AppCmdOptBase* option = findOpt(optname, options);
00409 if (!option) {
00410 throw AppCmdOptUnknownException(optname);
00411 }
00412
00413 if (option->hasArgument()) {
00414
00415
00416 std::string value;
00417 if (word.size() == 2) {
00418 ++_iter;
00419 --_nWordsLeft;
00420 value = *_iter;
00421 } else {
00422 value = std::string(word, 2);
00423 }
00424
00425 option->setValue(value);
00426
00427 } else {
00428
00429
00430
00431
00432 option->setValue("");
00433
00434
00435 for (size_t i = 2; i < word.size(); ++i) {
00436 std::string optname(1, word[i]);
00437
00438 if (::isHelpOption(optname)) {
00439 _helpWanted = true;
00440 return;
00441 }
00442 AppCmdOptBase* option = findOpt(optname, options);
00443 if (!option) {
00444 throw AppCmdOptUnknownException(optname);
00445 }
00446 if (option->hasArgument()) {
00447
00448 throw AppCmdException(
00449 std::string("option with argument (-") + optname
00450 + ") cannot be mixed with other options: " + word);
00451 }
00452
00453 option->setValue("");
00454 }
00455
00456 }
00457
00458 } else {
00459
00460
00461
00462 break;
00463
00464 }
00465
00466 ++_iter;
00467 --_nWordsLeft;
00468
00469 }
00470
00471 }
00472
00473
00474 void
00475 AppCmdLine::parseOptionsFile(const OptionsList& options)
00476 {
00477 if (not _optionsFile) return;
00478
00479
00480
00481
00482 std::set < std::string > changedOptions;
00483 for (OptionsList::const_iterator it = options.begin(); it != options.end(); ++it) {
00484 if ((*it)->valueChanged()) {
00485 const std::vector<std::string>& optnames = (*it)->options();
00486 changedOptions.insert(optnames.begin(), optnames.end());
00487 }
00488 }
00489
00490 typedef AppCmdOptList<std::string>::const_iterator OFIter;
00491 for (OFIter ofiter = _optionsFile->begin(); ofiter != _optionsFile->end(); ++ofiter) {
00492
00493
00494 std::string optFile = *ofiter;
00495 if (optFile.empty()) {
00496
00497 return;
00498 }
00499
00500
00501 std::ifstream istream(optFile.c_str());
00502 if (not istream) {
00503
00504 throw AppCmdException("failed to open options file: " + optFile);
00505 }
00506
00507
00508 std::string line;
00509 unsigned int nlines = 0;
00510 while (std::getline(istream, line)) {
00511 nlines++;
00512
00513
00514 std::string::size_type fchar = line.find_first_not_of(" \t");
00515 if (fchar == std::string::npos) {
00516
00517
00518 continue;
00519 } else if (line[fchar] == '#') {
00520
00521
00522 continue;
00523 }
00524
00525
00526 std::string::size_type optend = line.find_first_of(" \t=", fchar);
00527 std::string optname(line, fchar, optend);
00528
00529
00530 AppCmdOptBase* option = findOpt(optname, options);
00531 if (!option) {
00532 throw AppCmdException("Error parsing options file: option '" + optname + "' is unknown");
00533 }
00534
00535
00536 if (changedOptions.find(optname) != changedOptions.end()) {
00537 continue;
00538 }
00539
00540
00541
00542
00543 std::string optval;
00544 if (optend != std::string::npos) {
00545 std::string::size_type pos1 = line.find('=', optend);
00546
00547 if (pos1 != std::string::npos) {
00548 pos1 = line.find_first_not_of(" \t", pos1 + 1);
00549
00550 if (pos1 != std::string::npos) {
00551 std::string::size_type pos2 = line.find_last_not_of(" \t");
00552
00553 if (pos2 != std::string::npos) {
00554 optval = std::string(line, pos1, pos2 - pos1 + 1);
00555 } else {
00556 optval = std::string(line, pos1);
00557 }
00558
00559 }
00560 }
00561
00562 }
00563
00564
00565 option->setValue(optval);
00566
00567
00568 }
00569
00570
00571 if (not istream.eof()) {
00572 throw AppCmdException(
00573 "failure when reading options file, at or around line " + boost::lexical_cast < std::string > (nlines));
00574 }
00575
00576 }
00577 }
00578
00579
00580 void
00581 AppCmdLine::parseArgs()
00582 {
00583 int nPosLeft = _positionals.size();
00584 for (PositionalsList::const_iterator it = _positionals.begin(); it != _positionals.end(); ++it) {
00585
00586
00587 --nPosLeft;
00588
00589 if (_iter == _argv.end()) {
00590
00591 bool ok = !(*it)->isRequired();
00592 if (!ok) {
00593 throw AppCmdArgListTooShort();
00594 }
00595 return;
00596 }
00597
00598
00599 size_t nWordsToGive = 1;
00600 if ((*it)->maxWords() > 1) {
00601
00602 if (_nWordsLeft <= nPosLeft) {
00603
00604 throw AppCmdArgListTooShort();
00605 }
00606 nWordsToGive = _nWordsLeft - nPosLeft;
00607 }
00608
00609 StringList::const_iterator w_end = _iter;
00610 std::advance(w_end, nWordsToGive);
00611
00612 int consumed = (*it)->setValue(_iter, w_end);
00613 std::advance(_iter, consumed);
00614 _nWordsLeft -= consumed;
00615
00616 }
00617
00618 if (_iter != _argv.end()) {
00619
00620 throw AppCmdArgListTooLong();
00621 }
00622
00623 }
00624
00625
00626 AppCmdOptBase*
00627 AppCmdLine::findOpt(const std::string& opt, const OptionsList& options) const
00628 {
00629 for (OptionsList::const_iterator it = options.begin(); it != options.end(); ++it) {
00630 const std::vector<std::string>& optnames = (*it)->options();
00631 if (std::find(optnames.begin(), optnames.end(), opt) != optnames.end()) {
00632 return *it;
00633 }
00634 }
00635 return 0;
00636 }
00637
00638 void
00639 AppCmdLine::formatOptGroup(std::ostream& out, const std::string& groupName, const OptionsList& options, size_t optLen,
00640 size_t nameLen) const
00641 {
00642 AppCmdWordWrap ww;
00643 const int width = ww.pageWidth();
00644 const int width2 = width/2;
00645 int descrLen = width - optLen - nameLen - 9;
00646 bool nl = false;
00647 if (descrLen < width2) {
00648 descrLen = width2;
00649 nl = true;
00650 }
00651
00652 out << "\n " << groupName << ":\n";
00653 for (OptionsList::const_iterator it = options.begin(); it != options.end(); ++it) {
00654 std::string fopt;
00655
00656
00657 const std::vector<std::string>& optnames = (*it)->options();
00658 for (std::vector<std::string>::const_iterator oit = optnames.begin(); oit != optnames.end(); ++ oit) {
00659 if (not fopt.empty()) fopt += "|";
00660 fopt += "-";
00661 if (oit->size() > 1) fopt += "-";
00662 fopt += *oit;
00663 }
00664
00665
00666 std::vector<std::string> descr = ww.wrap((*it)->description(), descrLen);
00667 out << " {" << setw(optLen) << fopt.c_str() << "} " << setw(nameLen) << (*it)->name().c_str() << " ";
00668 for (std::vector<std::string>::const_iterator it = descr.begin(); it != descr.end(); ++ it) {
00669 if (it == descr.begin() and nl) out << '\n';
00670 if (it != descr.begin() or nl) out << setw(width-descrLen) << "";
00671 out << *it << '\n';
00672 }
00673 }
00674 }
00675
00676 }