library of assembled shared sources

http://lass.cocamware.com

arg_parser.cpp

Go to the documentation of this file.
00001 /** @file
00002  *  @author Bram de Greve (bramz@users.sourceforge.net)
00003  *  @author Tom De Muer (tomdemuer@users.sourceforge.net)
00004  *
00005  *  *** BEGIN LICENSE INFORMATION ***
00006  *  
00007  *  The contents of this file are subject to the Common Public Attribution License 
00008  *  Version 1.0 (the "License"); you may not use this file except in compliance with 
00009  *  the License. You may obtain a copy of the License at 
00010  *  http://lass.sourceforge.net/cpal-license. The License is based on the 
00011  *  Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover 
00012  *  use of software over a computer network and provide for limited attribution for 
00013  *  the Original Developer. In addition, Exhibit A has been modified to be consistent 
00014  *  with Exhibit B.
00015  *  
00016  *  Software distributed under the License is distributed on an "AS IS" basis, WITHOUT 
00017  *  WARRANTY OF ANY KIND, either express or implied. See the License for the specific 
00018  *  language governing rights and limitations under the License.
00019  *  
00020  *  The Original Code is LASS - Library of Assembled Shared Sources.
00021  *  
00022  *  The Initial Developer of the Original Code is Bram de Greve and Tom De Muer.
00023  *  The Original Developer is the Initial Developer.
00024  *  
00025  *  All portions of the code written by the Initial Developer are:
00026  *  Copyright (C) 2004-2007 the Initial Developer.
00027  *  All Rights Reserved.
00028  *  
00029  *  Contributor(s):
00030  *
00031  *  Alternatively, the contents of this file may be used under the terms of the 
00032  *  GNU General Public License Version 2 or later (the GPL), in which case the 
00033  *  provisions of GPL are applicable instead of those above.  If you wish to allow use
00034  *  of your version of this file only under the terms of the GPL and not to allow 
00035  *  others to use your version of this file under the CPAL, indicate your decision by 
00036  *  deleting the provisions above and replace them with the notice and other 
00037  *  provisions required by the GPL License. If you do not delete the provisions above,
00038  *  a recipient may use your version of this file under either the CPAL or the GPL.
00039  *  
00040  *  *** END LICENSE INFORMATION ***
00041  */
00042 
00043 #include "io_common.h"
00044 #include "arg_parser.h"
00045 
00046 #include <locale>
00047 
00048 namespace lass
00049 {
00050 namespace io
00051 {
00052 
00053 // --- ArgParser ------------------------------------------------------------------------------
00054 
00055 /** Construct a quiet parser.
00056  *  A quiet parser won't write anything to the output (it will still log though), and it will
00057  *  throw as few exceptions as possible
00058  */
00059 ArgParser::ArgParser():
00060     programName_(""),
00061     programVersion_(""),
00062     positionals_(""),
00063     isQuiet_(true)
00064 {
00065 }
00066 
00067 
00068 
00069 /** Construct a not so quiet parser.
00070  *  On wrong input, it will write info to the standard input and will throw some more exceptions.
00071  */
00072 ArgParser::ArgParser(const std::string& iProgramName,
00073                      const std::string& iProgramVersion,
00074                      const std::string& iPositionalArguments):
00075     programName_(iProgramName),
00076     programVersion_(iProgramVersion),
00077     positionals_(iPositionalArguments),
00078     isQuiet_(false)
00079 {
00080 }
00081 
00082 
00083 
00084 /** parse arguments
00085  *  @note in contrary like @c argc/argv , you shouldn't store the program name as the first
00086  *        argument, since the parser will start with @c iArguments[0] .  Hence, the parser would
00087  *        see the program name as the first positional if you did so.
00088  */
00089 bool ArgParser::parse(const TArguments& iArguments, TArguments* oPositionals)
00090 {
00091     bool result = true;
00092     bool allPositionals = false;
00093 
00094     for (TArguments::size_type i = 0; i < iArguments.size(); ++i)
00095     {
00096         std::string arg = iArguments[i];
00097         if (arg.length() > 0)
00098         {
00099             if (arg[0] == '-' && !allPositionals)
00100             {
00101                 if (arg.length() > 1)
00102                 {
00103                     if (arg[1] == '-')
00104                     {
00105                         if (arg.length() > 2)
00106                         {
00107                             // --longOption
00108                             result &= parseLong(iArguments, i);
00109                         }
00110                         else
00111                         {
00112                             // --
00113                             allPositionals = true;
00114                         }
00115                     }
00116                     else
00117                     {
00118                         // -shortOptions
00119                         result &= parseShort(iArguments, i);
00120                     }
00121                 }
00122                 else
00123                 {
00124                     // -  what qbout it?
00125                 }
00126             }
00127             else
00128             {
00129                 if (oPositionals)
00130                 {
00131                     oPositionals->push_back(arg);
00132                 }
00133             }
00134         }
00135     }
00136 
00137     return true;
00138 }
00139 
00140 
00141 
00142 /** parse arguments from commandline
00143  *  @note we suppose @c iArgv[0] is the program name and will skipped by the parser.
00144  *        the parser will only take care of @c iArgv[1] and the following!
00145  */
00146 bool ArgParser::parse(int iArgc, char* iArgv[], TArguments* oPositionals)
00147 {
00148     TArguments arguments;
00149     std::copy(iArgv + 1, iArgv + iArgc, std::back_inserter(arguments));
00150 
00151     return parse(arguments, oPositionals);
00152 }
00153 
00154 
00155 
00156 /** parse arguments that come by a string.
00157  *  break string based on whitespace unless it's surrounded by quotes.
00158  *  @note You shouldn't start the string with the program name like argc/argv do.  The string
00159  *        will be parsed from the beginning, so the program name would be seen as the first
00160  *        positional if you did.
00161  */
00162 bool ArgParser::parse(const std::string& iArguments, TArguments* oPositionals)
00163 {
00164     const std::string whitespace = " \t\n";
00165     const std::string quotes = "\"";
00166     const std::string escapes = "\\";
00167 
00168     //LASS_EVAL(iArguments);
00169 
00170     TArguments result;
00171 
00172     std::string token;
00173     bool isQuoted = false;
00174     char quote = '#';
00175     bool isEscaped = false;
00176     char escape = '#';
00177 
00178     for (std::string::const_iterator ch = iArguments.begin(); ch != iArguments.end(); ++ch)
00179     {
00180         if (whitespace.find(*ch) != std::string::npos && !isQuoted)
00181         {
00182             if (!token.empty())
00183             {
00184                 result.push_back(token);
00185                 token.clear();
00186             }
00187         }
00188         else if (quotes.find(*ch) != std::string::npos)
00189         {
00190             if (isEscaped)
00191             {
00192                 token.append(1, *ch);
00193                 isEscaped = false;
00194             }
00195             else
00196             {
00197                 if (isQuoted)
00198                 {
00199                     if (quote == *ch)
00200                     {
00201                         isQuoted = false;
00202                     }
00203                 }
00204                 else
00205                 {
00206                     quote = *ch;
00207                     isQuoted = true;
00208                 }
00209             }
00210         }
00211         else if (escapes.find(*ch) != std::string::npos)
00212         {
00213             if (isEscaped)
00214             {
00215                 token.append(1, *ch);
00216                 isEscaped = false;
00217             }
00218             else
00219             {
00220                 escape = *ch;
00221                 isEscaped = true;
00222             }
00223         }
00224         else
00225         {
00226             if (isEscaped)
00227             {
00228                 token.append(1, escape);
00229                 isEscaped = false;
00230             }
00231             token.append(1, *ch);
00232         }
00233     }
00234 
00235     result.push_back(token);
00236     return parse(result, oPositionals);
00237 }
00238 
00239 
00240 
00241 std::string ArgParser::usage() const
00242 {
00243     std::ostringstream result;
00244 
00245     result << "usage:";
00246     if (!programName_.empty())
00247     {
00248         result << " " << programName_;
00249     }
00250 
00251     result << " [-v|--version] [-h|--help]";
00252 
00253     for (TParameters::const_iterator pit = parameters_.begin(); pit != parameters_.end(); ++pit)
00254     {
00255         result << " " << (*pit)->format();
00256     }
00257 
00258     if (!positionals_.empty())
00259     {
00260         result << " " << positionals_;
00261     }
00262 
00263     return result.str();
00264 }
00265 
00266 
00267 
00268 
00269 void ArgParser::subscribe(ArgParameter& iParameter)
00270 {
00271     const std::string& shortName = iParameter.shortName();
00272     const std::string& longName = iParameter.longName();
00273 
00274     // we need at least a short or a long option name.
00275     //
00276     if (shortName.empty() && longName.empty())
00277     {
00278         LASS_THROW("Subscribing parameter failed because both the short and the long names are "
00279             "empty what makes it impossible to identify the parameter.");
00280     }
00281 
00282     // short names cannot be long and must be alphanumerical
00283     //
00284     if (!shortName.empty())
00285     {
00286         if (shortName.length() != 1 || !std::isalnum(shortName[0], std::locale()))
00287         {
00288             LASS_THROW("Subscribing parameter failed because the short name is not a single "
00289                 "alpha-numeric character.");
00290         }
00291     }
00292 
00293     if (!isValidLongName(longName))
00294     {
00295         LASS_THROW("Subscribing parameter failed because the long name isn't valid.");
00296     }
00297 
00298     // both short and long names must be unique (if their not zero length)
00299     //
00300     for (TParameters::const_iterator pit = parameters_.begin(); pit != parameters_.end(); ++pit)
00301     {
00302         ArgParameter* param = *pit;
00303         if ((!shortName.empty() && param->shortName() == shortName) ||
00304             (!longName.empty() && param->longName() == longName))
00305         {
00306             LASS_THROW("Subscribing parameter failed because there is already an parameter with "
00307                 "the same short or long name.");
00308         }
00309     }
00310 
00311     // all is well, subscribe!
00312     parameters_.push_back(&iParameter);
00313 }
00314 
00315 
00316 
00317 bool ArgParser::parseShort(const TArguments& iArguments, TSize& ioIndex)
00318 {
00319     const std::string& arg = iArguments[ioIndex];
00320     LASS_ASSERT(arg.length() > 1); // arg is of form '-abcdef'
00321 
00322     // first one can have a value.  scan all parameters to see if you can find it
00323     //
00324     std::string shortName(arg, 1, 1); // in above example shortName is 'a'
00325     LASS_ASSERT(shortName.length() == 1);
00326     LASS_ASSERT(shortName[0] == arg[1]);
00327     if (writeVersionOrHelp(shortName))
00328     {
00329         return false;
00330     }
00331     for (TParameters::iterator pit = parameters_.begin(); pit != parameters_.end(); ++pit)
00332     {
00333         ArgParameter* param = *pit;
00334         if (param->shortName() == shortName)
00335         {
00336             if (!(param->mode() & amNoValue)) // param can have value?
00337             {
00338                 std::string value = "";
00339                 if (arg.length() > 2)
00340                 {
00341                     value = arg.substr(2); // the rest of arg is the value
00342                 }
00343                 else
00344                 {
00345                     // if more arguments follow, the following is the value (but it can't start
00346                     // with a '-' !
00347                     //
00348                     if (ioIndex + 1 < iArguments.size())
00349                     {
00350                         std::string candidate = iArguments[ioIndex + 1];
00351                         if (candidate.length() > 0 && candidate[0] != '-')
00352                         {
00353                             value = candidate;
00354                             ++ioIndex;
00355                         }
00356                     }
00357                 }
00358                 param->setValue(value);
00359                 return true;
00360             }
00361             else
00362             {
00363                 param->setValue("");
00364             }
00365         }
00366     }
00367 
00368     // if first one couldn't have a value (it was a flag), then other flags may follow
00369     //
00370     for (std::string::size_type i = 2; i < arg.length(); ++i)
00371     {
00372         shortName = arg.substr(i, 1);
00373         LASS_ASSERT(shortName.length() == 1);
00374         LASS_ASSERT(shortName[0] == arg[i]);
00375         if (writeVersionOrHelp(shortName))
00376         {
00377             return false;
00378         }
00379         for (TParameters::iterator pit = parameters_.begin(); pit != parameters_.end(); ++pit)
00380         {
00381             ArgParameter* param = *pit;
00382             if (param->shortName() == shortName)
00383             {
00384                 if (param->mode() & amNoValue) // only params without values may follow
00385                 {
00386                     param->setValue("");
00387                 }
00388                 else
00389                 {
00390                     if (!isQuiet_)
00391                     {
00392                         LASS_COUT << "Bad program arguments: the argument '" << shortName
00393                             << "' can take a value.  You cannot group it with other arguments like "
00394                             << "in '" << arg << "'.\n" << usage() << "\n";
00395                     }
00396                     return false;
00397                 }
00398             }
00399         }
00400     }
00401     return false;
00402 }
00403 
00404 
00405 
00406 bool ArgParser::parseLong(const TArguments& iArguments, TSize iIndex)
00407 {
00408     bool result = true;
00409 
00410     const std::string& arg = iArguments[iIndex];
00411     LASS_ASSERT(arg.length() > 2); // arg is of form '--longName=value'
00412 
00413     // split name and value
00414     //
00415     std::string longName;
00416     std::string value;
00417     std::string::size_type equalPosition = arg.find('=');
00418     if (equalPosition == std::string::npos)
00419     {
00420         longName = arg.substr(2);
00421         value = "";
00422     }
00423     else
00424     {
00425         longName = arg.substr(2, equalPosition - 2);
00426         value = arg.substr(equalPosition + 1);
00427     }
00428 
00429     // check syntax of name.
00430     //
00431     if (!isValidLongName(longName))
00432     {
00433         if (!isQuiet_)
00434         {
00435             LASS_COUT << "Bad program arguments: the argument '--" << longName << "' is not a "
00436                 << "valid long parameter name.\n" << usage() << "\n";
00437         }
00438         return false;
00439     }
00440 
00441     if (writeVersionOrHelp(longName))
00442     {
00443         return false;
00444     }
00445 
00446     TParameters::iterator match = parameters_.end(); // no match yet.
00447 
00448     // try to find an exact match
00449     //
00450     for (TParameters::iterator pit = parameters_.begin(); pit != parameters_.end(); ++pit)
00451     {
00452         ArgParameter* param = *pit;
00453         if (param->longName() == longName)
00454         {
00455             match = pit; // huray, we have one!
00456             break;
00457         }
00458     }
00459 
00460     // otherwise, try to find a unique best match
00461     //
00462     if (match == parameters_.end())
00463     {
00464         for (std::string::size_type length = longName.length(); length > 0; --length)
00465         {
00466             TParameters::iterator candidate = parameters_.end();
00467             bool isUnique = true;
00468 
00469             for (TParameters::iterator pit = parameters_.begin(); pit != parameters_.end(); ++pit)
00470             {
00471                 ArgParameter* param = *pit;
00472                 if (param->longName().length() >= length)
00473                 {
00474                     if (param->longName().substr(1, length) == longName.substr(1, length))
00475                     {
00476                         isUnique = candidate == parameters_.end();
00477                         candidate = pit;
00478                     }
00479                 }
00480             }
00481 
00482             if (candidate != parameters_.end() && isUnique == true)
00483             {
00484                 match = candidate; // huray too!
00485                 break;
00486             }
00487         }
00488     }
00489 
00490     // by now, we have to have a match
00491     //
00492     if (match == parameters_.end())
00493     {
00494         if (!isQuiet_)
00495         {
00496             LASS_COUT << "Bad program arguments: the argument '--" << longName << "' is not as a "
00497                 << "long parameter name, nor is it a unique abbrevation of one.\n" << usage()
00498                 << "\n";
00499         }
00500         return false;
00501     }
00502 
00503     // we have one, let's deal with it.
00504     //
00505     (*match)->setValue(value); // even works if no value is expected
00506     return result;
00507 }
00508 
00509 
00510 
00511 /** long names can only exists of alphanumerics and some extra characters '-'.
00512  */
00513 bool ArgParser::isValidLongName(const std::string& iLongName) const
00514 {
00515     std::string extra = "-";
00516 
00517     for (std::string::size_type i = 0; i < iLongName.length(); ++i)
00518     {
00519         const char ch = iLongName[i];
00520         if (!std::isalnum(ch, std::locale()) && extra.find(ch) == std::string::npos)
00521         {
00522             return false;
00523         }
00524     }
00525     return true;
00526 }
00527 
00528 
00529 
00530 bool ArgParser::writeVersionOrHelp(const std::string& iArgument) const
00531 {
00532     if (iArgument == "v" || iArgument == "version")
00533     {
00534         writeVersion();
00535         return true;
00536     }
00537     if (iArgument == "h" || iArgument == "help")
00538     {
00539         writeHelp();
00540         return true;
00541     }
00542     return false;
00543 }
00544 
00545 
00546 
00547 void ArgParser::writeVersion() const
00548 {
00549     LASS_COUT << programName_ << " version " << programVersion_ << "\n";
00550 }
00551 
00552 
00553 
00554 void ArgParser::writeHelp() const
00555 {
00556     writeVersion();
00557     LASS_COUT << usage() << "\n";
00558 }
00559 
00560 
00561 
00562 // --- ArgParameter -------------------------------------------------------------------------------------
00563 
00564 ArgParameter::~ArgParameter()
00565 {
00566 }
00567 
00568 
00569 
00570 const std::string& ArgParameter::shortName() const
00571 {
00572     return shortName_;
00573 }
00574 
00575 
00576 
00577 const std::string& ArgParameter::longName() const
00578 {
00579     return longName_;
00580 }
00581 
00582 
00583 
00584 const int ArgParameter::mode() const
00585 {
00586     return mode_;
00587 }
00588 
00589 
00590 
00591 bool ArgParameter::operator!() const
00592 {
00593     return !isSet_;
00594 }
00595 
00596 
00597 
00598 ArgParameter::operator num::SafeBool() const
00599 {
00600     return isSet_ ? num::safeTrue : num::safeFalse;
00601 }
00602 
00603 
00604 
00605 ArgParameter::ArgParameter(ArgParser& iParser,
00606                            const std::string& iShortName,
00607                            const std::string& iLongName,
00608                            int iArgMode):
00609     parser_(iParser),
00610     shortName_(iShortName),
00611     longName_(iLongName),
00612     mode_(iArgMode),
00613     isSet_(false)
00614 {
00615     parser_.subscribe(*this);
00616 }
00617 
00618 
00619 
00620 const std::string ArgParameter::names() const
00621 {
00622     std::ostringstream result;
00623 
00624     if (!shortName_.empty())
00625     {
00626         result << "-" << shortName_;
00627     }
00628     if (!longName_.empty())
00629     {
00630         if (!shortName_.empty())
00631         {
00632             result << "|";
00633         }
00634         result << "--" << longName_;
00635     }
00636 
00637     return result.str();
00638 }
00639 
00640 
00641 
00642 const bool ArgParameter::parserIsQuiet() const
00643 {
00644     return parser_.isQuiet_;
00645 }
00646 
00647 
00648 
00649 void ArgParameter::set()
00650 {
00651     isSet_ = true;
00652 }
00653 
00654 
00655 
00656 const std::string ArgParameter::format() const
00657 {
00658     return doFormat();
00659 }
00660 
00661 
00662 
00663 const bool ArgParameter::setValue(const std::string& iValue)
00664 {
00665     return doSetValue(iValue);
00666 }
00667 
00668 
00669 
00670 const std::string ArgParameter::doFormat() const
00671 {
00672     std::ostringstream result;
00673     result << "[" << names() << "]";
00674     return result.str();
00675 }
00676 
00677 
00678 
00679 const bool ArgParameter::doSetValue(const std::string& /* iValue */)
00680 {
00681     //LASS_LOG("parameter '" << names() << "' is set.");
00682     set();
00683     return true;
00684 }
00685 
00686 
00687 
00688 // --- ArgFlag -------------------------------------------------------------------------------------
00689 
00690 ArgFlag::ArgFlag(ArgParser& iParser,
00691                  const std::string& iShortName,
00692                  const std::string& iLongName):
00693     ArgParameter(iParser, iShortName, iLongName, amNoValue)
00694 {
00695 }
00696 
00697 
00698 
00699 ArgFlag::ArgFlag(ArgParser& iParser,
00700                  const ArgFormat& iFormat):
00701     ArgParameter(iParser, iFormat.shortName, iFormat.longName, amNoValue)
00702 {
00703 }
00704 
00705 
00706 
00707 }
00708 
00709 }
00710 
00711 // EOF

Generated on Mon Nov 10 14:19:59 2008 for Library of Assembled Shared Sources by doxygen 1.5.7.1
SourceForge.net Logo