blocxx

CmdLineParser.cpp

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