CmdLineParser.cpp

Go to the documentation of this file.
00001 /*******************************************************************************
00002 * Copyright (C) 2004 Vintela, Inc. All rights reserved.
00003 * Copyright (C) 2005 Novell, Inc. All rights reserved.
00004 *
00005 * Redistribution and use in source and binary forms, with or without
00006 * modification, are permitted provided that the following conditions are met:
00007 *
00008 *  - Redistributions of source code must retain the above copyright notice,
00009 *    this list of conditions and the following disclaimer.
00010 *
00011 *  - Redistributions in binary form must reproduce the above copyright notice,
00012 *    this list of conditions and the following disclaimer in the documentation
00013 *    and/or other materials provided with the distribution.
00014 *
00015 *  - Neither the name of Vintela, Inc., Novell, Inc., nor the names of its
00016 *    contributors may be used to endorse or promote products derived from this
00017 *    software without specific prior written permission.
00018 *
00019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
00020 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00021 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00022 * ARE DISCLAIMED. IN NO EVENT SHALL Vintela, Inc., Novell, Inc., OR THE 
00023 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
00024 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
00025 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
00026 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
00027 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
00028 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
00029 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00030 *******************************************************************************/
00031 
00032 
00037 #include "blocxx/BLOCXX_config.h"
00038 #include "blocxx/CmdLineParser.hpp"
00039 #include "blocxx/Array.hpp"
00040 #include "blocxx/ExceptionIds.hpp"
00041 #include "blocxx/StringBuffer.hpp"
00042 #include "blocxx/Assertion.hpp"
00043 
00044 #include <algorithm>
00045 
00046 
00047 namespace BLOCXX_NAMESPACE
00048 {
00049 
00050 BLOCXX_DEFINE_EXCEPTION_WITH_ID(CmdLineParser)
00051 
00052 namespace
00053 {
00055    struct longOptIs
00056    {
00057       longOptIs(const String& longOpt) : m_longOpt(longOpt) {}
00058 
00059       bool operator()(const CmdLineParser::Option& x) const
00060       {
00061          if (x.longopt != 0)
00062          {
00063             return m_longOpt.startsWith(x.longopt);
00064          }
00065          return false;
00066       }
00067 
00068       String m_longOpt;
00069    };
00070 
00072    struct shortOptIs
00073    {
00074       shortOptIs(char shortOpt) : m_shortOpt(shortOpt) {}
00075 
00076       bool operator()(const CmdLineParser::Option& x) const
00077       {
00078          return m_shortOpt == x.shortopt;
00079       }
00080 
00081       char m_shortOpt;
00082    };
00083 
00084 }
00085 
00087 CmdLineParser::CmdLineParser(int argc, char const* const* const argv_, const Option* options, EAllowNonOptionArgsFlag allowNonOptionArgs)
00088 {
00089    BLOCXX_ASSERT(argc > 0); // have to get at least the name
00090    BLOCXX_ASSERT(argv_ != 0);
00091    BLOCXX_ASSERT(options != 0);
00092    char const* const* argv = argv_;
00093    char const* const* argvEnd = argv + argc;
00094 
00095    // m_options is an array terminated by a final entry that has a '\0' shortopt && 0 longopt.
00096    const Option* optionsEnd(options);
00097    while (optionsEnd->shortopt != '\0' || optionsEnd->longopt != 0)
00098    {
00099       ++optionsEnd;
00100    }
00101 
00102    // skip the first argv, which is the program name and loop through the rest
00103    ++argv;
00104    while (argv != argvEnd)
00105    {
00106       BLOCXX_ASSERT(*argv != 0);
00107       String arg(*argv);
00108 
00109       // look for an option
00110       if ((arg.length() >= 2) && (arg[0] == '-'))
00111       {
00112          const Option* theOpt(0);
00113          bool longOpt = false;
00114          if (arg[1] == '-')
00115          {
00116             // long option
00117             longOpt = true;
00118             arg = arg.substring(2); // erase the --
00119             theOpt = std::find_if (options,  optionsEnd, longOptIs(arg));
00120          }
00121          else // short option
00122          {
00123             longOpt = false;
00124             arg = arg.substring(1); // erase the -
00125             theOpt = std::find_if (options,  optionsEnd, shortOptIs(arg[0]));
00126          }
00127 
00128          if (theOpt == optionsEnd)
00129          {
00130             BLOCXX_THROW_ERR(CmdLineParserException, arg.c_str(), E_INVALID_OPTION);
00131          }
00132 
00133          if (theOpt->argtype == E_NO_ARG)
00134          {
00135             m_parsedOptions[theOpt->id]; // if one is already there, don't modify it, else insert a new one
00136             ++argv;
00137             continue;
00138          }
00139          // now see if we can get the value
00140          String val;
00141          if ((theOpt->argtype == E_OPTIONAL_ARG) && (theOpt->defaultValue != 0))
00142          {
00143             val = theOpt->defaultValue;
00144          }
00145          
00146          const char* p = ::strchr(arg.c_str(), '=');
00147          if (p)
00148          {
00149             // get everything after the =
00150             val = String(p+1);
00151          }
00152          else
00153          {
00154             // a short option can have the value together with it (i.e. -I/opt/vintela/include)
00155             if (longOpt == false && arg.length() > 1)
00156             {
00157                val = arg.substring(1);
00158             }
00159             // look at the next arg
00160             else if (argv+1 != argvEnd)
00161             {
00162                if (**(argv+1) != '-')
00163                {
00164                   val = *(argv+1);
00165                   ++argv;
00166                }
00167             }
00168          }
00169 
00170          // make sure we got an arg if one is required
00171          if (theOpt->argtype == E_REQUIRED_ARG && val.empty())
00172          {
00173             BLOCXX_THROW_ERR(CmdLineParserException, arg.c_str(), E_MISSING_ARGUMENT);
00174          }
00175 
00176          m_parsedOptions[theOpt->id].push_back(val);
00177       }
00178       else
00179       {
00180          if (allowNonOptionArgs == E_NON_OPTION_ARGS_INVALID)
00181          {
00182             BLOCXX_THROW_ERR(CmdLineParserException, arg.c_str(), E_INVALID_NON_OPTION_ARG);
00183          }
00184          else
00185          {
00186             m_nonOptionArgs.push_back(arg);
00187          }
00188       }
00189       ++argv;
00190    }
00191 }
00192 
00194 // static
00195 String
00196 CmdLineParser::getUsage(const Option* options, unsigned int maxColumns)
00197 {
00198 // looks like this:
00199 //     "Options:\n"
00200 //     "  -1, --one                 first description\n"
00201 //     "  -2, --two [arg]           second description (default is optional)\n"
00202 //     "  -3, --three <arg>         third description\n"
00203 
00204    const unsigned int NUM_OPTION_COLUMNS = 28;
00205    StringBuffer usage("Options:\n");
00206 
00207    // m_options is an array terminated by a final entry that has a '\0' shortopt && 0 longOpt.
00208    for (const Option* curOption = options; curOption->shortopt != '\0' || curOption->longopt != 0; ++curOption)
00209    {
00210       StringBuffer curLine;
00211       curLine += "  ";
00212       if (curOption->shortopt != '\0')
00213       {
00214          curLine += '-';
00215          curLine += curOption->shortopt;
00216          if (curOption->longopt != 0)
00217          {
00218             curLine += ", ";
00219          }
00220       }
00221       if (curOption->longopt != 0)
00222       {
00223          curLine += "--";
00224          curLine += curOption->longopt;
00225       }
00226 
00227       if (curOption->argtype == E_REQUIRED_ARG)
00228       {
00229          curLine += " <arg>";
00230       }
00231       else if (curOption->argtype == E_OPTIONAL_ARG)
00232       {
00233          curLine += " [arg]";
00234       }
00235 
00236       size_t bufferlen = (curLine.length() >= NUM_OPTION_COLUMNS-1) ? 1 : (NUM_OPTION_COLUMNS - curLine.length());
00237       for (size_t i = 0; i < bufferlen; ++i)
00238       {
00239          curLine += ' ';
00240       }
00241 
00242       if (curOption->description != 0)
00243       {
00244          curLine += curOption->description;
00245       }
00246 
00247       if (curOption->defaultValue != 0)
00248       {
00249          curLine += " (default is ";
00250          curLine += curOption->defaultValue;
00251          curLine += ')';
00252       }
00253 
00254       // now if curLine is too long or contains newlines, we need to wrap it.
00255       while (curLine.length() > maxColumns || curLine.toString().indexOf('\n') != String::npos)
00256       {
00257          String curLineStr(curLine.toString());
00258          // first we look for a \n to cut at
00259          size_t newlineIdx = curLineStr.indexOf('\n');
00260 
00261          // next look for the last space to cut at
00262          size_t lastSpaceIdx = curLineStr.lastIndexOf(' ', maxColumns);
00263 
00264          size_t cutIdx = 0;
00265          size_t nextLineBeginIdx = 0;
00266          if (newlineIdx <= maxColumns)
00267          {
00268             cutIdx = newlineIdx;
00269             nextLineBeginIdx = newlineIdx + 1; // skip the newline
00270          }
00271          else if (lastSpaceIdx > NUM_OPTION_COLUMNS)
00272          {
00273             cutIdx = lastSpaceIdx;
00274             nextLineBeginIdx = lastSpaceIdx + 1; // skip the space
00275          }
00276          else
00277          {
00278             // no space to cut it, just cut it in the middle of a word
00279             cutIdx = maxColumns;
00280             nextLineBeginIdx = maxColumns;
00281          }
00282 
00283          // found a place to cut, so do it.
00284          usage += curLineStr.substring(0, cutIdx);
00285          usage += '\n';
00286 
00287          // cut out the line from curLine
00288          StringBuffer spaces;
00289          for (size_t i = 0; i < NUM_OPTION_COLUMNS; ++i)
00290          {
00291             spaces += ' ';
00292          }
00293          curLine = spaces.releaseString() + curLineStr.substring(nextLineBeginIdx);
00294       }
00295 
00296       curLine += '\n';
00297       usage += curLine;
00298    }
00299    return usage.releaseString();
00300 }
00301 
00303 String
00304 CmdLineParser::getOptionValue(int id, const char* defaultValue) const
00305 {
00306    optionsMap_t::const_iterator ci = m_parsedOptions.find(id);
00307    if (ci != m_parsedOptions.end() && ci->second.size() > 0)
00308    {
00309       // grab the last one
00310       return ci->second[ci->second.size()-1];
00311    }
00312    return defaultValue;
00313 }
00314 
00316 String
00317 CmdLineParser::mustGetOptionValue(int id, const char* exceptionMessage) const
00318 {
00319    optionsMap_t::const_iterator ci = m_parsedOptions.find(id);
00320    if (ci != m_parsedOptions.end() && ci->second.size() > 0)
00321    {
00322       // grab the last one
00323       return ci->second[ci->second.size()-1];
00324    }
00325    BLOCXX_THROW_ERR(CmdLineParserException, exceptionMessage, E_MISSING_OPTION);
00326 }
00327 
00329 StringArray
00330 CmdLineParser::getOptionValueList(int id) const
00331 {
00332    StringArray rval;
00333    optionsMap_t::const_iterator ci = m_parsedOptions.find(id);
00334    if (ci != m_parsedOptions.end() && ci->second.size() > 0)
00335    {
00336       rval = ci->second;
00337    }
00338    return rval;
00339 }
00340 
00342 StringArray
00343 CmdLineParser::mustGetOptionValueList(int id, const char* exceptionMessage) const
00344 {
00345    optionsMap_t::const_iterator ci = m_parsedOptions.find(id);
00346    if (ci != m_parsedOptions.end() && ci->second.size() > 0)
00347    {
00348       return ci->second;
00349    }
00350    BLOCXX_THROW_ERR(CmdLineParserException, exceptionMessage, E_MISSING_OPTION);
00351 }
00352 
00354 bool
00355 CmdLineParser::isSet(int id) const
00356 {
00357    return m_parsedOptions.count(id) > 0;
00358 }
00359 
00361 size_t
00362 CmdLineParser::getNonOptionCount () const
00363 {
00364    return m_nonOptionArgs.size();
00365 }
00366 
00368 String
00369 CmdLineParser::getNonOptionArg(size_t n) const
00370 {
00371    return m_nonOptionArgs[n];
00372 }
00373 
00375 StringArray
00376 CmdLineParser::getNonOptionArgs() const
00377 {
00378    return m_nonOptionArgs;
00379 }
00380 
00381 
00382 
00383 } // end namespace BLOCXX_NAMESPACE
00384 
00385 
00386 

Generated on Fri Jun 16 15:39:08 2006 for blocxx by  doxygen 1.4.6