AppUtils/src/AppCmdLine.cpp

Go to the documentation of this file.
00001 //--------------------------------------------------------------------------
00002 // File and Version Information:
00003 //      $Id: AppCmdLine.cpp 6565 2013-07-23 15:49:51Z salnikov@SLAC.STANFORD.EDU $
00004 //
00005 // Description:
00006 //      Class AppCmdLine
00007 //
00008 // Environment:
00009 //      Software developed for the BaBar Detector at the SLAC B-Factory.
00010 //
00011 // Author List:
00012 //      Andy Salnikov           originator
00013 //
00014 // Copyright Information:
00015 //      Copyright (C) 2003      SLAC
00016 //
00017 //------------------------------------------------------------------------
00018 
00019 //-----------------------
00020 // This Class's Header --
00021 //-----------------------
00022 #include "AppUtils/AppCmdLine.h"
00023 
00024 //---------------
00025 // C++ Headers --
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 // Collaborating Class Headers --
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 // Local Macros, Typedefs, Structures, Unions and Forward Declarations --
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 // special help option used internally
00061 AppUtils::AppCmdOptBool helpOpt("h,?,help", "print help message");
00062 
00063 }
00064 
00065 //              ----------------------------------------
00066 //              -- Public Function Member Definitions --
00067 //              ----------------------------------------
00068 
00069 namespace AppUtils {
00070 
00071 /*
00072  *  Constructor.
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 // Destructor
00089 AppCmdLine::~AppCmdLine()
00090 {
00091 }
00092 
00093 // Add options group to the parser.
00094 void
00095 AppCmdLine::addGroup(AppCmdOptGroup& group)
00096 {
00097   _groups.push_back(&group);
00098 }
00099 
00100 /*
00101  *  Add one more positional argument. The argument supplied is not copied,
00102  *  only its address is remembered. The lifetime of the argument should extend
00103  *  to the parse() method of this class.
00104  */
00105 void
00106 AppCmdLine::addArgument(AppCmdArgBase& arg)
00107 {
00108   _positionals.push_back(&arg);
00109 }
00110 
00111 /*
00112  *  Add option which will specify the name of the options file.
00113  *  Only one options file is allowed per parser, attempt to add one
00114  *  more will result in exception. The lifetime of the argument should
00115  *  extend to the parse() method of this class.
00116  */
00117 void
00118 AppCmdLine::setOptionsFile(AppCmdOptList<std::string>& option)
00119 {
00120   // second attempt will fail
00121   if (_optionsFile) {
00122     throw AppCmdException("options file option already defined, cannot re-define");
00123   }
00124 
00125   // remember it
00126   _optionsFile = &option;
00127 }
00128 
00129 /*
00130  *  Parse function examines command line and sets the corresponding arguments.
00131  *  If it returns false then you should not expect anything, just exit.
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  *  Returns true if the "help" option was specified on the command line.
00152  *  Always check its return value after calling parse() when it returns true,
00153  *  if the help option is given then parse() stops without checking anything else.
00154  */
00155 bool
00156 AppCmdLine::helpWanted() const
00157 {
00158   return _helpWanted;
00159 }
00160 
00161 /*
00162  *  Prints usage information
00163  */
00164 void
00165 AppCmdLine::usage(std::ostream& out) const
00166 {
00167   out.setf(ios::left, ios::adjustfield);
00168 
00169   // build full list of options from all groups
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     // calculate max length of the options strings (-o|--option)
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;  // all separating '|'s
00194       for (std::vector<std::string>::const_iterator oit = optnames.begin(); oit != optnames.end(); ++ oit) {
00195         moptLen += oit->size() + 1; // '-x'
00196         if (oit->size() > 1) {
00197           moptLen += 1; // '--xxx'
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; // "....name.-.description....."
00226     bool nl = false;
00227     if (descrLen < width2) {
00228       descrLen = width2;
00229       nl = true;
00230     }
00231 
00232     // wrap description, print it on separate lines
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  * Get the complete command line
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 /// real parsing happens in this method
00273 void
00274 AppCmdLine::doParse()
00275 {
00276   _helpWanted = 0;
00277 
00278   // build full list of options from all groups
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   // if options-file option was set but it was not added to any group add it now to the parser
00286   if (_optionsFile) {
00287     if (std::find(options.begin(), options.end(), _optionsFile) == options.end()) {
00288       // define a regular option
00289       this->addOption(*_optionsFile);
00290       options.push_back(_optionsFile);
00291     }
00292   }
00293 
00294   // check for option name conflicts
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   // check for arguments order
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       // cannot have required after non-required
00317       if (arg->isRequired() and not argPrev->isRequired()) {
00318         throw AppCmdArgOrderException(arg->name());
00319       }
00320 
00321     }
00322   }
00323 
00324   // reset all options and arguments to their default values
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   // get options from command line
00329   parseOptions(options);
00330   if (_helpWanted) {
00331     return;
00332   }
00333 
00334   // get options from an options file if any
00335   parseOptionsFile(options);
00336 
00337   // get remaining args
00338   parseArgs();
00339 }
00340 
00341 /// parse options
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       // should stop here
00354       ++_iter;
00355       break;
00356 
00357     } else if (word.size() > 2 && word[0] == '-' && word[1] == '-') {
00358 
00359       // long option takes everything before '='
00360       const std::string optname(word, 2, word.find('=') - 2);
00361 
00362       // long options should be longer than one character
00363       if (optname.size() < 2) {
00364         throw AppCmdOptUnknownException(optname);
00365       }
00366 
00367       // if --help is provided stop parsing
00368       if (::isHelpOption(optname)) {
00369         _helpWanted = true;
00370         break;
00371       }
00372 
00373       // find option with this name
00374       AppCmdOptBase* option = findOpt(optname, options);
00375       if (!option) {
00376         throw AppCmdOptUnknownException(optname);
00377       }
00378 
00379       // option argument value (only for options with arguments)
00380       std::string value;
00381       if (option->hasArgument()) {
00382         // take everything after the '=' or next word
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       // now give it to option, this may throw
00394       option->setValue(value);
00395 
00396     } else if (word.size() > 1 && word[0] == '-') {
00397 
00398       // should be short option or options
00399       std::string optname(1, word[1]);
00400 
00401       // stop on -h
00402       if (::isHelpOption(optname)) {
00403         _helpWanted = true;
00404         return;
00405       }
00406 
00407       // find option with this short name
00408       AppCmdOptBase* option = findOpt(optname, options);
00409       if (!option) {
00410         throw AppCmdOptUnknownException(optname);
00411       }
00412 
00413       if (option->hasArgument()) {
00414 
00415         // option expects argument, it is either the rest of this word or next word
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         // this may throw
00425         option->setValue(value);
00426 
00427       } else {
00428 
00429         // option without argument, but the word may be collection of options, like -vvqs
00430 
00431         // this may throw (but should not)
00432         option->setValue("");
00433 
00434         // scan remaining characters which should all be single-char options with no argument
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             // do not allow mixture
00448             throw AppCmdException(
00449                 std::string("option with argument (-") + optname
00450                     + ") cannot be mixed with other options: " + word);
00451           }
00452           // this may throw (but should not)
00453           option->setValue("");
00454         }
00455 
00456       }
00457 
00458     } else {
00459 
00460       // not an option, stop here. Note that '-' by itself is considered
00461       // as an argument.
00462       break;
00463 
00464     }
00465 
00466     ++_iter;
00467     --_nWordsLeft;
00468 
00469   }
00470 
00471 }
00472 
00473 /// parse options file
00474 void
00475 AppCmdLine::parseOptionsFile(const OptionsList& options)
00476 {
00477   if (not _optionsFile) return;
00478 
00479   // build the list of options that were modified on the command line,
00480   // we do not want to change these again as command line overrides
00481   // options file contents.
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     // find the name of the options file
00494     std::string optFile = *ofiter;
00495     if (optFile.empty()) {
00496       // no file name given
00497       return;
00498     }
00499 
00500     // open the file
00501     std::ifstream istream(optFile.c_str());
00502     if (not istream) {
00503       // failed to open file
00504       throw AppCmdException("failed to open options file: " + optFile);
00505     }
00506 
00507     // read all the lines from the file
00508     std::string line;
00509     unsigned int nlines = 0;
00510     while (std::getline(istream, line)) {
00511       nlines++;
00512 
00513       // skip comments
00514       std::string::size_type fchar = line.find_first_not_of(" \t");
00515       if (fchar == std::string::npos) {
00516         // empty line
00517         //std::cout << "line " << nlines << ": empty\n" ;
00518         continue;
00519       } else if (line[fchar] == '#') {
00520         // comment
00521         //std::cout << "line " << nlines << ": comment\n" ;
00522         continue;
00523       }
00524 
00525       // get option name
00526       std::string::size_type optend = line.find_first_of(" \t=", fchar);
00527       std::string optname(line, fchar, optend);
00528 
00529       // find option with this long name
00530       AppCmdOptBase* option = findOpt(optname, options);
00531       if (!option) {
00532         throw AppCmdException("Error parsing options file: option '" + optname + "' is unknown");
00533       }
00534 
00535       // if it was changed on command line do not change it again
00536       if (changedOptions.find(optname) != changedOptions.end()) {
00537         continue;
00538       }
00539 
00540       //std::cout << "line " << nlines << ": option '" << optname << "'\n" ;
00541 
00542       // get option value if any
00543       std::string optval;
00544       if (optend != std::string::npos) {
00545         std::string::size_type pos1 = line.find('=', optend);
00546         //std::cout << "line " << nlines << ": pos1 = " << pos1 << "\n" ;
00547         if (pos1 != std::string::npos) {
00548           pos1 = line.find_first_not_of(" \t", pos1 + 1);
00549           //std::cout << "line " << nlines << ": pos1 = " << pos1 << "\n" ;
00550           if (pos1 != std::string::npos) {
00551             std::string::size_type pos2 = line.find_last_not_of(" \t");
00552             //std::cout << "line " << nlines << ": pos2 = " << pos2 << "\n" ;
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             //std::cout << "line " << nlines << ": value '" << optval << "'\n" ;
00559           }
00560         }
00561 
00562       }
00563 
00564       // set the option
00565       option->setValue(optval);
00566       //std::cout << "line " << nlines << ": '" << optname << "' = '" << optval << "'\n" ;
00567 
00568     }
00569 
00570     // check the status of the file, must be at EOF
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 /// parse arguments
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     // number of positional args left after the current one
00587     --nPosLeft;
00588 
00589     if (_iter == _argv.end()) {
00590       // no data left
00591       bool ok = !(*it)->isRequired();
00592       if (!ok) {
00593         throw AppCmdArgListTooShort();
00594       }
00595       return;
00596     }
00597 
00598     // determine how many words we could give to next argument
00599     size_t nWordsToGive = 1;
00600     if ((*it)->maxWords() > 1) {
00601       // but can get more
00602       if (_nWordsLeft <= nPosLeft) {
00603         // too few words left
00604         throw AppCmdArgListTooShort();
00605       }
00606       nWordsToGive = _nWordsLeft - nPosLeft;
00607     }
00608 
00609     StringList::const_iterator w_end = _iter;
00610     std::advance(w_end, nWordsToGive);
00611     // this can throw
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     // not whole line is consumed
00620     throw AppCmdArgListTooLong();
00621   }
00622 
00623 }
00624 
00625 /// find option with the long name
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; // "....{options}.name..description....."
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     // format all options
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     // wrap description, print it on separate lines
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 } // namespace AppUtils

Generated on 19 Dec 2016 for PSDMSoftware by  doxygen 1.4.7