optionParser.cpp

Go to the documentation of this file.
00001 
00014 #include <algorithm>
00015 #include <sstream>
00016 #include <dlrCommon/exception.h>
00017 #include <dlrUtilities/optionParser.h>
00018 #include <dlrUtilities/stringManipulation.h>
00019 
00020 namespace dlr {
00021 
00022   namespace utilities {
00023   
00024     // Default constructor.
00025     OptionParser::
00026     OptionParser(bool allowExtraArguments,
00027                  bool allowStackedShortOptions,
00028                  bool allowOptionishArguments)
00029       : m_allowExtraArguments(allowExtraArguments),
00030         m_allowOptionishArguments(allowOptionishArguments),
00031         m_allowStackedShortOptions(allowStackedShortOptions),
00032         m_errorBehavior(ThrowOnError),
00033         m_exitCode(0),
00034         m_extraArgumentValues(),
00035         m_handleHelp(false),
00036         m_numberOfPosArgRequired(0),
00037         m_numberOfPosArgParsed(0),
00038         m_optionCounts(),
00039         m_optionDescriptions(),
00040         m_optionValues(),
00041         m_positionalArgumentNames(),
00042         m_positionalArgumentDefaultValues(),
00043         m_positionalArgumentDocs(),
00044         m_programName()
00045     {
00046       // Empty.
00047     }
00048 
00049 
00050     // Constructor.
00051     OptionParser::
00052     OptionParser(int exitCode,
00053                  bool handleMinusMinusHelp,
00054                  bool printUsage,
00055                  bool allowExtraArguments,
00056                  bool allowStackedShortOptions,
00057                  bool allowOptionishArguments)
00058       : m_allowExtraArguments(allowExtraArguments),
00059         m_allowOptionishArguments(allowOptionishArguments),
00060         m_allowStackedShortOptions(allowStackedShortOptions),
00061         m_errorBehavior(printUsage ? UsageAndExitOnError : ExitOnError),
00062         m_exitCode(exitCode),
00063         m_extraArgumentValues(),
00064         m_handleHelp(handleMinusMinusHelp),
00065         m_numberOfPosArgRequired(0),
00066         m_numberOfPosArgParsed(0),
00067         m_optionCounts(),
00068         m_optionDescriptions(),
00069         m_optionValues(),
00070         m_positionalArgumentNames(),
00071         m_positionalArgumentDefaultValues(),
00072         m_positionalArgumentDocs(),
00073         m_programName()
00074     {
00075       // Empty.
00076     }
00077 
00078     
00079     // Destructor.
00080     OptionParser::
00081     ~OptionParser()
00082     {
00083       // Empty.
00084     }
00085 
00086 
00087     void
00088     OptionParser::
00089     addOption(const std::string& name,
00090               const std::string& shortVersion,
00091               const std::string& longVersion,
00092               const std::string& docString,
00093               bool allowPartialMatch)
00094     {
00095       if(m_optionDescriptions.find(name) != m_optionDescriptions.end()) {
00096         std::ostringstream message;
00097         message << "Option \"" << name << "\" has already been specified.";
00098         DLR_THROW(StateException, "OptionParser::addOption()",
00099                   message.str().c_str());
00100       }
00101       OptionDescription newOption(
00102         name, shortVersion, longVersion, false, allowPartialMatch, docString);
00103       m_optionDescriptions[name] = newOption;
00104       m_optionCounts[name] = 0;
00105     }
00106 
00107 
00108     void
00109     OptionParser::
00110     addOptionWithValue(const std::string& name,
00111                        const std::string& shortVersion,
00112                        const std::string& longVersion,
00113                        const std::string& defaultValue,
00114                        const std::string& docString,
00115                        bool requireArgument,
00116                        bool allowPartialMatch,
00117                        bool allowOptionishValue)
00118     {
00119       if(requireArgument == false) {
00120         DLR_THROW(NotImplementedException,
00121                   "OptionParser::addOptionWithValue()",
00122                   "Options with optional arguments not yet supported.");
00123       }
00124 
00125       if(m_optionDescriptions.find(name) != m_optionDescriptions.end()) {
00126         std::ostringstream message;
00127         message << "Option \"" << name << "\" has already been specified.";
00128         DLR_THROW(StateException, "OptionParser::addOptionWithValue()",
00129                   message.str().c_str());
00130       }
00131       OptionDescription newOption(
00132         name, shortVersion, longVersion, true, allowPartialMatch, docString,
00133         defaultValue);
00134       m_optionDescriptions[name] = newOption;
00135       m_optionCounts[name] = 0;
00136     }
00137 
00138     
00139     void
00140     OptionParser::
00141     addPositionalArgument(const std::string& name,
00142                           const std::string& docString,
00143                           bool isRequired,
00144                           const std::string& defaultValue)
00145     {
00146       m_positionalArgumentNames.push_back(name);
00147       m_positionalArgumentDocs.push_back(docString);
00148       m_positionalArgumentDefaultValues.push_back(defaultValue);
00149       if(isRequired) {
00150         m_numberOfPosArgRequired = m_positionalArgumentNames.size();
00151       }
00152     }
00153 
00154     
00155     size_t
00156     OptionParser::
00157     getCount(const std::string& name) const
00158     {
00159       std::map<std::string, size_t>::const_iterator iter =
00160         m_optionCounts.find(name);
00161       if(iter == m_optionCounts.end()) {
00162         std::ostringstream message;
00163         message << "Invalid option name: " << name << ".";
00164         DLR_THROW(ValueException, "OptionParser::getCount()",
00165                   message.str().c_str());
00166       }
00167       return iter->second;
00168     }
00169 
00170     
00171     std::vector<std::string>
00172     OptionParser::
00173     getExtraPositionalArguments()
00174     {
00175       return m_extraArgumentValues;
00176     }
00177 
00178     
00179     std::string
00180     OptionParser::
00181     getOptionsDescription()
00182     {
00183       std::ostringstream optionsStream;
00184       if(!m_optionDescriptions.empty()) {
00185         std::map<std::string, OptionDescription>::const_iterator iter =
00186           m_optionDescriptions.begin();
00187         while(iter != m_optionDescriptions.end()) {
00188           optionsStream << iter->second << "\n";
00189           ++iter;
00190         }
00191       }
00192       return optionsStream.str();
00193     }
00194 
00195     
00196     std::string
00197     OptionParser::
00198     getUsage()
00199     {
00200       std::string indentString = "        ";
00201       std::ostringstream usageStream;
00202       usageStream << "Usage:\n"
00203                   << "  " << m_programName << " ";
00204       if(!m_optionDescriptions.empty()) {
00205         usageStream << "[options] ";
00206       }
00207       size_t argIndex = 0;
00208       while(argIndex < m_numberOfPosArgRequired) {
00209         usageStream << m_positionalArgumentNames[argIndex] << " ";
00210         ++argIndex;
00211       }
00212       while(argIndex < m_positionalArgumentNames.size()) {
00213         usageStream << "[" << m_positionalArgumentNames[argIndex] << "] ";
00214         ++argIndex;
00215       }
00216       if(!m_positionalArgumentNames.empty()) {
00217         usageStream << "\n\nPositional arguments:\n";
00218         for(argIndex = 0; argIndex < m_numberOfPosArgRequired; ++argIndex) {
00219           usageStream
00220             << "  " << m_positionalArgumentNames[argIndex] << "\n"
00221             << wrapString(indentString + m_positionalArgumentDocs[argIndex],
00222                           indentString)
00223             << "\n";
00224         }
00225         while(argIndex < m_positionalArgumentNames.size()) {
00226           usageStream
00227             << "  " << m_positionalArgumentNames[argIndex] << "\n"
00228             << wrapString(indentString + m_positionalArgumentDocs[argIndex],
00229                           indentString) << "\n"
00230             << wrapString((indentString + "Default value: \""
00231                            + m_positionalArgumentDefaultValues[argIndex]),
00232                           indentString) << "\""
00233             << "\n";
00234           ++argIndex;          
00235         }
00236       }
00237 
00238       if(!m_optionDescriptions.empty()) {
00239         usageStream << "\nOptions:\n"
00240                     << this->getOptionsDescription();
00241       }
00242       
00243       return usageStream.str();
00244     }
00245     
00246     
00247     std::string
00248     OptionParser::
00249     getValue(const std::string& name)
00250     {
00251       return this->getValue(name, -1);
00252     }
00253 
00254     
00255     std::string
00256     OptionParser::
00257     getValue(const std::string& name, int valueIndex)
00258     {
00259       // Look for the specified name in the recorded option/argument values.
00260       std::map< std::string, std::vector<std::string> >::const_iterator vIter =
00261         m_optionValues.find(name);
00262       if(vIter != m_optionValues.end()) {
00263         // Found an appropriate entry in the recorded values.
00264         if(valueIndex >= static_cast<int>((vIter->second).size())) {
00265           std::ostringstream message;
00266           message << "Option/argument " << name << " was specified "
00267                   << (vIter->second).size() << " times, "
00268                   << "yet getValue() was called with valueIndex set to "
00269                   << valueIndex << ".";
00270           DLR_THROW(IndexException, "OptionParser::getValue()",
00271                     message.str().c_str());
00272         }
00273         if(valueIndex < 0
00274            || valueIndex >= static_cast<int>((vIter->second).size())) {
00275           valueIndex = (vIter->second).size() - 1;
00276         }
00277         return (vIter->second)[valueIndex];
00278       }
00279 
00280       // Nothing appropriate found in the recorded values.  Check to
00281       // see if the specified name corresponds to an option, and if
00282       // so, return the default value for that option.
00283       std::map<std::string, OptionDescription>::const_iterator odIter =
00284         m_optionDescriptions.find(name);
00285       if(odIter != m_optionDescriptions.end()) {
00286         return (odIter->second).getDefaultValue();
00287       }
00288 
00289       // No appropriate option found.  Check to see if the specified
00290       // name corresponds to a positional argument, and if so, return
00291       // the default value for that positional argument.
00292       std::vector<std::string>::const_iterator panIter =
00293         std::find(m_positionalArgumentNames.begin(),
00294                   m_positionalArgumentNames.end(),
00295                   name);
00296       if(panIter != m_positionalArgumentNames.end()) {
00297         size_t argIndex = panIter - m_positionalArgumentNames.begin();
00298         return m_positionalArgumentDefaultValues[argIndex];
00299       }
00300       
00301       // // No appropriate positional argument found.  Return the empty
00302       // // string.
00303       // return "";
00304 
00305       // No appropriate positional argument found.
00306       std::ostringstream message;
00307       message << "The value of Option/argument " << name << " was requested, "
00308               << "but no option or argument with this name exists.";
00309       DLR_THROW(ValueException, "OptionParser::getValue()",
00310                 message.str().c_str());
00311     }
00312 
00313     
00314     void
00315     OptionParser::
00316     parseCommandLine(int argc, char* argv[])
00317     {
00318       // An enum representing the possible states of the parser below.
00319       enum ParseState {
00320         DLR_OP_GETTING_NEW_ARG,
00321         DLR_OP_IDENTIFYING_ARG,
00322         DLR_OP_RECORDING_POS_ARG,
00323         DLR_OP_RECORDING_OPTION,
00324         DLR_OP_RECORDING_VALUE,
00325         DLR_OP_FINISHING_SHORT_OPTION,
00326         DLR_OP_FINISHING_LONG_OPTION,
00327         DLR_OP_FINISHED,
00328         DLR_OP_ERROR
00329       };
00330 
00331       if(argc < 1) {
00332         DLR_THROW(ValueException, "OptionParser::parseCommandLine()",
00333                   "Argument argc must be greater than or equal to 1.");
00334       }
00335 
00336       // Reset state for new parse.
00337       m_optionValues.clear();
00338       std::map<std::string, size_t>::iterator iter = m_optionCounts.begin(); 
00339       while(iter != m_optionCounts.end()) {
00340         iter->second = 0;
00341         ++iter;
00342       }
00343       m_extraArgumentValues.clear();
00344 
00345       // Record "special" arguments.
00346       m_programName = argv[0];
00347 
00348       // Parse the remaining command line, catching any errors.
00349       try {
00350         int argIndex = 1;
00351         std::string currentArgument = "";
00352         OptionDescription optionDescription;
00353         size_t typedLength;
00354         bool isShortMatch;
00355         std::ostringstream errorMessage;
00356 
00357         ParseState currentState = DLR_OP_GETTING_NEW_ARG;
00358         while(currentState != DLR_OP_FINISHED) {
00359           switch(currentState) {
00360           case DLR_OP_GETTING_NEW_ARG:
00361             if(argIndex >= argc) {
00362               currentState = DLR_OP_FINISHED;
00363             } else {
00364               currentArgument = argv[argIndex];
00365               ++argIndex;
00366               currentState = DLR_OP_IDENTIFYING_ARG;
00367             }
00368             break;
00369           case DLR_OP_IDENTIFYING_ARG:
00370             // Special case for --help arguments.
00371             if(m_handleHelp && (currentArgument == "--help")) {
00372               std::cout << this->getUsage() << std::endl;
00373               exit(0);
00374             }
00375 
00376             // See if any of the user-specified options match.
00377             {
00378               bool optionFound = this->findOptionDescription(
00379                 currentArgument, optionDescription, typedLength, isShortMatch);
00380               if(!optionFound) {
00381                 currentState = DLR_OP_RECORDING_POS_ARG;
00382               } else {
00383                 currentState = DLR_OP_RECORDING_OPTION;
00384               }
00385             }
00386             break;
00387           case DLR_OP_RECORDING_POS_ARG:
00388             // This argument doesn't match any of our options.  It must
00389             // be a positional argument.
00390             if(!m_allowOptionishArguments && currentArgument[0] == '-') {
00391               errorMessage << "Unrecognized option \"" << currentArgument
00392                            << "\".";
00393               currentState = DLR_OP_ERROR;
00394             } else {
00395               this->recordPositionalArgument(currentArgument);
00396               currentState = DLR_OP_GETTING_NEW_ARG;
00397             }
00398             break;
00399           case DLR_OP_RECORDING_OPTION:
00400             // Increment the count for this option.
00401             ++((this->m_optionCounts.find(optionDescription.getName()))
00402                ->second);
00403 
00404             // And make sure we do the appropriate followup.
00405             if(optionDescription.requiresValue()) {
00406               currentState = DLR_OP_RECORDING_VALUE;
00407             } else if(isShortMatch) {
00408               currentState = DLR_OP_FINISHING_SHORT_OPTION;
00409             } else {
00410               currentState = DLR_OP_FINISHING_LONG_OPTION;
00411             }
00412             break;
00413           case DLR_OP_RECORDING_VALUE:
00414             // This argument does match one of our options.
00415             if(typedLength < currentArgument.size()) {
00416               this->m_optionValues[optionDescription.getName()].push_back(
00417                 currentArgument.substr(typedLength, std::string::npos));
00418               currentState = DLR_OP_GETTING_NEW_ARG;
00419             } else if(argIndex < argc) {
00420               this->m_optionValues[optionDescription.getName()].push_back(
00421                 argv[argIndex]);
00422               ++argIndex;
00423               currentState = DLR_OP_GETTING_NEW_ARG;
00424             } else {
00425               errorMessage << "Missing value for option \"" << currentArgument
00426                            << "\".";
00427               currentState = DLR_OP_ERROR;
00428             }
00429             break;
00430           case DLR_OP_FINISHING_SHORT_OPTION:
00431             if(typedLength >= currentArgument.size()) {
00432               currentState = DLR_OP_GETTING_NEW_ARG;
00433             } else if(m_allowStackedShortOptions
00434                       && typedLength >= 2
00435                       && currentArgument[0] == '-') {
00436               currentArgument =
00437                 "-" + currentArgument.substr(typedLength, std::string::npos);
00438               currentState = DLR_OP_IDENTIFYING_ARG;                
00439             } else {
00440               errorMessage
00441                 << "Option \""
00442                 << currentArgument.substr(0, typedLength)
00443                 << "\" does not take a value, yet was specified as \""
00444                 << currentArgument << ".";
00445               currentState = DLR_OP_ERROR;
00446             }
00447             break;
00448           case DLR_OP_FINISHING_LONG_OPTION:
00449             if(typedLength >= currentArgument.size()) {
00450               currentState = DLR_OP_GETTING_NEW_ARG;
00451             } else {
00452               errorMessage
00453                 << "Option \""
00454                 << currentArgument.substr(0, typedLength)
00455                 << "\" does not take a value, yet was specified as \""
00456                 << currentArgument << ".";
00457               currentState = DLR_OP_ERROR;
00458             }
00459             break;
00460           case DLR_OP_ERROR:
00461             DLR_THROW(IOException, "OptionParser::parseCommandLine()",
00462                       errorMessage.str().c_str());
00463             break;
00464           default:
00465             // Should never get here.
00466             break;
00467           }
00468         }
00469 
00470         // Make sure we got the right number of positional arguments.
00471         if(m_numberOfPosArgParsed < m_numberOfPosArgRequired) {
00472           std::ostringstream message;
00473           message << "Expected " << m_numberOfPosArgRequired
00474                   << " positional argument(s), but only found "
00475                   << m_numberOfPosArgParsed << ".";
00476           DLR_THROW(IOException, "OptionParser::parseCommandLine()",
00477                     message.str().c_str());                      
00478         }
00479         if(!m_allowExtraArguments && (m_extraArgumentValues.size() != 0)) {
00480           std::ostringstream message;
00481           message << "Expected no more than "
00482                   << m_positionalArgumentNames.size()
00483                   << " positional arguments, but found "
00484                   << m_numberOfPosArgParsed << ".";
00485           DLR_THROW(IOException, "OptionParser::parseCommandLine()",
00486                     message.str().c_str());                      
00487         }
00488 
00489       } catch(const IOException& caughtException) {
00490         switch(m_errorBehavior) {
00491         case UsageAndExitOnError:
00492           // Print diagnostic info and usage, then fall through into
00493           // ExitOnError code.
00494           std::cout << caughtException.what() << "\n\n";
00495           std::cout << this->getUsage() << std::endl;
00496         case ExitOnError:
00497           // Exit with the user specified error code.
00498           exit(m_exitCode);
00499           break;
00500         case ThrowOnError:
00501         default:
00502           // Default behavior is to rethrow the exception.
00503           throw;
00504           break;
00505         }
00506       }
00507     }
00508     
00509 
00510     bool
00511     OptionParser::
00512     findOptionDescription(const std::string& argument,
00513                           OptionDescription& optionDescription,
00514                           size_t& typedLength,
00515                           bool& isShortMatch)
00516     {
00517       // Ugly algorithm here makes arg parsing be O(N^2) where N the
00518       // number of args.  For now, the 80/20 rule dictates that we not
00519       // sweat about it.
00520       typedLength = 0;
00521       bool foundOption = false;
00522       std::map<std::string, OptionDescription>::const_iterator iter =
00523         m_optionDescriptions.begin();
00524       while(iter != m_optionDescriptions.end()) {
00525         bool localIsShortMatch;
00526         size_t localTypedLength =
00527           (iter->second).getMatchLength(argument, localIsShortMatch);
00528         if(localTypedLength != 0) {
00529           if(foundOption) {
00530             std::ostringstream message;
00531             message << "Argument \"" << argument << "\" is ambiguous.";
00532             DLR_THROW(IOException, "OptionParser::findOptionDescription()",
00533                       message.str().c_str());                      
00534           } else {
00535             typedLength = localTypedLength;
00536             foundOption = true;
00537             optionDescription = iter->second;
00538             isShortMatch = localIsShortMatch;
00539           }
00540         }
00541         ++iter;
00542       }
00543       return foundOption;
00544     }
00545 
00546 
00547     void
00548     OptionParser::
00549     recordPositionalArgument(const std::string& argument)
00550     {
00551       if(m_numberOfPosArgParsed < m_positionalArgumentNames.size()) {
00552         std::string argumentName =
00553           m_positionalArgumentNames[m_numberOfPosArgParsed];
00554         m_optionValues[argumentName].push_back(argument);
00555       } else {
00556         m_extraArgumentValues.push_back(argument);
00557       }
00558       ++m_numberOfPosArgParsed;
00559     }
00560 
00561 
00562   } // namespace utilities
00563 
00564 } // namespace dlr

Generated on Mon Jul 9 20:34:03 2007 for dlrLibs Utility Libraries by  doxygen 1.5.2