blocxx
|
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