1/*------------------------------------------------------------------------- 2 * drawElements C++ Base Library 3 * ----------------------------- 4 * 5 * Copyright 2014 The Android Open Source Project 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 * 19 *//*! 20 * \file 21 * \brief Command line parser. 22 *//*--------------------------------------------------------------------*/ 23 24#include "deCommandLine.hpp" 25 26#include <set> 27#include <sstream> 28#include <cstring> 29#include <stdexcept> 30#include <algorithm> 31 32namespace de 33{ 34namespace cmdline 35{ 36 37namespace 38{ 39struct Help { typedef bool ValueType; }; 40} 41 42namespace detail 43{ 44 45inline const char* getNamedValueName (const void* namedValue) 46{ 47 return static_cast<const NamedValue<deUint8>*>(namedValue)->name; 48} 49 50using std::set; 51 52TypedFieldMap::TypedFieldMap (void) 53{ 54} 55 56TypedFieldMap::~TypedFieldMap (void) 57{ 58 for (Map::const_iterator iter = m_fields.begin(); iter != m_fields.end(); ++iter) 59 { 60 if (iter->second.value) 61 iter->second.destructor(iter->second.value); 62 } 63} 64 65bool TypedFieldMap::contains (const std::type_info* key) const 66{ 67 return m_fields.find(key) != m_fields.end(); 68} 69 70const TypedFieldMap::Entry& TypedFieldMap::get (const std::type_info* key) const 71{ 72 Map::const_iterator pos = m_fields.find(key); 73 if (pos != m_fields.end()) 74 return pos->second; 75 else 76 throw std::out_of_range("Value not set"); 77} 78 79void TypedFieldMap::set (const std::type_info* key, const Entry& value) 80{ 81 Map::iterator pos = m_fields.find(key); 82 83 if (pos != m_fields.end()) 84 { 85 pos->second.destructor(pos->second.value); 86 pos->second.value = DE_NULL; 87 88 pos->second = value; 89 } 90 else 91 m_fields.insert(std::make_pair(key, value)); 92} 93 94Parser::Parser (void) 95{ 96 addOption(Option<Help>("h", "help", "Show this help")); 97} 98 99Parser::~Parser (void) 100{ 101} 102 103void Parser::addOption (const OptInfo& option) 104{ 105 m_options.push_back(option); 106} 107 108bool Parser::parse (int numArgs, const char* const* args, CommandLine* dst, std::ostream& err) const 109{ 110 typedef map<string, const OptInfo*> OptMap; 111 typedef set<const OptInfo*> OptSet; 112 113 OptMap shortOptMap; 114 OptMap longOptMap; 115 OptSet seenOpts; 116 bool allOk = true; 117 118 DE_ASSERT(dst->m_args.empty() && dst->m_options.empty()); 119 120 for (vector<OptInfo>::const_iterator optIter = m_options.begin(); optIter != m_options.end(); optIter++) 121 { 122 const OptInfo& opt = *optIter; 123 124 DE_ASSERT(opt.shortName || opt.longName); 125 126 if (opt.shortName) 127 { 128 DE_ASSERT(shortOptMap.find(opt.shortName) == shortOptMap.end()); 129 shortOptMap[opt.shortName] = &opt; 130 } 131 132 if (opt.longName) 133 { 134 DE_ASSERT(longOptMap.find(opt.longName) == longOptMap.end()); 135 longOptMap[opt.longName] = &opt; 136 } 137 138 // Set default values. 139 if (opt.defaultValue) 140 opt.dispatchParse(&opt, opt.defaultValue, &dst->m_options); 141 else 142 opt.getDefault(&dst->m_options); 143 } 144 145 DE_ASSERT(!dst->m_options.get<Help>()); 146 147 for (int argNdx = 0; argNdx < numArgs; argNdx++) 148 { 149 const char* arg = args[argNdx]; 150 int argLen = (int)strlen(arg); 151 152 if (arg[0] == '-' && arg[1] == '-' && arg[2] == 0) 153 { 154 // End of option list (--) 155 for (int optNdx = argNdx+1; optNdx < numArgs; optNdx++) 156 dst->m_args.push_back(args[optNdx]); 157 break; 158 } 159 else if (arg[0] == '-') 160 { 161 const bool isLongName = arg[1] == '-'; 162 const char* nameStart = arg + (isLongName ? 2 : 1); 163 const char* nameEnd = std::find(nameStart, arg+argLen, '='); 164 const bool hasImmValue = nameEnd != (arg+argLen); 165 const OptMap& optMap = isLongName ? longOptMap : shortOptMap; 166 OptMap::const_iterator optPos = optMap.find(string(nameStart, nameEnd)); 167 const OptInfo* opt = optPos != optMap.end() ? optPos->second : DE_NULL; 168 169 if (!opt) 170 { 171 err << "Unrecognized command line option '" << arg << "'\n"; 172 allOk = false; 173 continue; 174 } 175 176 if (seenOpts.find(opt) != seenOpts.end()) 177 { 178 err << "Command line option '--" << opt->longName << "' specified multiple times\n"; 179 allOk = false; 180 continue; 181 } 182 183 seenOpts.insert(opt); 184 185 if (opt->isFlag) 186 { 187 if (!hasImmValue) 188 { 189 opt->dispatchParse(opt, DE_NULL, &dst->m_options); 190 } 191 else 192 { 193 err << "No value expected for command line option '--" << opt->longName << "'\n"; 194 allOk = false; 195 } 196 } 197 else 198 { 199 const bool hasValue = hasImmValue || (argNdx+1 < numArgs); 200 201 if (hasValue) 202 { 203 const char* value = hasValue ? (hasImmValue ? nameEnd+1 : args[argNdx+1]) : DE_NULL; 204 205 if (!hasImmValue) 206 argNdx += 1; // Skip value 207 208 try 209 { 210 opt->dispatchParse(opt, value, &dst->m_options); 211 } 212 catch (const std::exception& e) 213 { 214 err << "Got error parsing command line option '--" << opt->longName << "': " << e.what() << "\n"; 215 allOk = false; 216 } 217 } 218 else 219 { 220 err << "Expected value for command line option '--" << opt->longName << "'\n"; 221 allOk = false; 222 } 223 } 224 } 225 else 226 { 227 // Not an option 228 dst->m_args.push_back(arg); 229 } 230 } 231 232 // Help specified? 233 if (dst->m_options.get<Help>()) 234 allOk = false; 235 236 return allOk; 237} 238 239void Parser::help (std::ostream& str) const 240{ 241 for (vector<OptInfo>::const_iterator optIter = m_options.begin(); optIter != m_options.end(); ++optIter) 242 { 243 const OptInfo& opt = *optIter; 244 245 str << " "; 246 if (opt.shortName) 247 str << "-" << opt.shortName; 248 249 if (opt.shortName && opt.longName) 250 str << ", "; 251 252 if (opt.longName) 253 str << "--" << opt.longName; 254 255 if (opt.namedValues) 256 { 257 str << "=["; 258 259 for (const void* curValue = opt.namedValues; curValue != opt.namedValuesEnd; curValue = (const void*)((deUintptr)curValue + opt.namedValueStride)) 260 { 261 if (curValue != opt.namedValues) 262 str << "|"; 263 str << getNamedValueName(curValue); 264 } 265 266 str << "]"; 267 } 268 else if (!opt.isFlag) 269 str << "=<value>"; 270 271 str << "\n"; 272 273 if (opt.description) 274 str << " " << opt.description << "\n"; 275 276 if (opt.defaultValue) 277 str << " default: '" << opt.defaultValue << "'\n"; 278 279 str << "\n"; 280 } 281} 282 283void CommandLine::clear (void) 284{ 285 m_options.clear(); 286 m_args.clear(); 287} 288 289const void* findNamedValueMatch (const char* src, const void* namedValues, const void* namedValuesEnd, size_t stride) 290{ 291 std::string srcStr(src); 292 293 for (const void* curValue = namedValues; curValue != namedValuesEnd; curValue = (const void*)((deUintptr)curValue + stride)) 294 { 295 if (srcStr == getNamedValueName(curValue)) 296 return curValue; 297 } 298 299 throw std::invalid_argument("unrecognized value '" + srcStr + "'"); 300} 301 302} // detail 303 304// Default / parsing functions 305 306template<> 307void getTypeDefault (bool* dst) 308{ 309 *dst = false; 310} 311 312template<> 313void parseType<bool> (const char*, bool* dst) 314{ 315 *dst = true; 316} 317 318template<> 319void parseType<std::string> (const char* src, std::string* dst) 320{ 321 *dst = src; 322} 323 324template<> 325void parseType<int> (const char* src, int* dst) 326{ 327 std::istringstream str(src); 328 str >> *dst; 329 if (str.bad() || !str.eof()) 330 throw std::invalid_argument("invalid integer literal"); 331} 332 333// Tests 334 335DE_DECLARE_COMMAND_LINE_OPT(TestStringOpt, std::string); 336DE_DECLARE_COMMAND_LINE_OPT(TestIntOpt, int); 337DE_DECLARE_COMMAND_LINE_OPT(TestBoolOpt, bool); 338DE_DECLARE_COMMAND_LINE_OPT(TestNamedOpt, deUint64); 339 340void selfTest (void) 341{ 342 // Parsing with no options. 343 { 344 Parser parser; 345 346 { 347 std::ostringstream err; 348 CommandLine cmdLine; 349 const bool parseOk = parser.parse(0, DE_NULL, &cmdLine, err); 350 351 DE_TEST_ASSERT(parseOk && err.str().empty()); 352 } 353 354 { 355 const char* args[] = { "-h" }; 356 std::ostringstream err; 357 CommandLine cmdLine; 358 const bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); 359 360 DE_TEST_ASSERT(!parseOk); 361 DE_TEST_ASSERT(err.str().empty()); // No message about -h 362 } 363 364 { 365 const char* args[] = { "--help" }; 366 std::ostringstream err; 367 CommandLine cmdLine; 368 const bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); 369 370 DE_TEST_ASSERT(!parseOk); 371 DE_TEST_ASSERT(err.str().empty()); // No message about -h 372 } 373 374 { 375 const char* args[] = { "foo", "bar", "baz baz" }; 376 std::ostringstream err; 377 CommandLine cmdLine; 378 const bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); 379 380 DE_TEST_ASSERT(parseOk && err.str().empty()); 381 DE_TEST_ASSERT(cmdLine.getArgs().size() == DE_LENGTH_OF_ARRAY(args)); 382 383 for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(args); ndx++) 384 DE_TEST_ASSERT(cmdLine.getArgs()[ndx] == args[ndx]); 385 } 386 } 387 388 // Parsing with options. 389 { 390 Parser parser; 391 392 static const NamedValue<deUint64> s_namedValues[] = 393 { 394 { "zero", 0 }, 395 { "one", 1 }, 396 { "huge", ~0ull } 397 }; 398 399 parser << Option<TestStringOpt> ("s", "string", "String option") 400 << Option<TestIntOpt> ("i", "int", "Int option") 401 << Option<TestBoolOpt> ("b", "bool", "Test boolean flag") 402 << Option<TestNamedOpt> ("n", "named", "Test named opt", DE_ARRAY_BEGIN(s_namedValues), DE_ARRAY_END(s_namedValues), "one"); 403 404 { 405 std::ostringstream err; 406 DE_TEST_ASSERT(err.str().empty()); 407 parser.help(err); 408 DE_TEST_ASSERT(!err.str().empty()); 409 } 410 411 // Default values 412 { 413 CommandLine cmdLine; 414 std::ostringstream err; 415 bool parseOk = parser.parse(0, DE_NULL, &cmdLine, err); 416 417 DE_TEST_ASSERT(parseOk); 418 DE_TEST_ASSERT(err.str().empty()); 419 420 DE_TEST_ASSERT(cmdLine.getOption<TestStringOpt>() == ""); 421 DE_TEST_ASSERT(cmdLine.getOption<TestIntOpt>() == 0); 422 DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>() == false); 423 DE_TEST_ASSERT(cmdLine.getOption<TestNamedOpt>() == 1); 424 } 425 426 // Basic parsing 427 { 428 const char* args[] = { "-s", "test value", "-b", "-i=9", "--named=huge" }; 429 CommandLine cmdLine; 430 std::ostringstream err; 431 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); 432 433 DE_TEST_ASSERT(parseOk); 434 DE_TEST_ASSERT(err.str().empty()); 435 436 DE_TEST_ASSERT(cmdLine.getOption<TestStringOpt>() == "test value"); 437 DE_TEST_ASSERT(cmdLine.getOption<TestIntOpt>() == 9); 438 DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>()); 439 DE_TEST_ASSERT(cmdLine.getOption<TestNamedOpt>() == ~0ull); 440 } 441 442 // End of argument list (--) 443 { 444 const char* args[] = { "--string=foo", "-b", "--", "--int=2", "-b" }; 445 CommandLine cmdLine; 446 std::ostringstream err; 447 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); 448 449 DE_TEST_ASSERT(parseOk); 450 DE_TEST_ASSERT(err.str().empty()); 451 452 DE_TEST_ASSERT(cmdLine.getOption<TestStringOpt>() == "foo"); 453 DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>()); 454 DE_TEST_ASSERT(cmdLine.getOption<TestIntOpt>() == 0); // Not specified 455 456 DE_TEST_ASSERT(cmdLine.getArgs().size() == 2); 457 DE_TEST_ASSERT(cmdLine.getArgs()[0] == "--int=2"); 458 DE_TEST_ASSERT(cmdLine.getArgs()[1] == "-b"); 459 } 460 461 // Value -- 462 { 463 const char* args[] = { "--string", "--", "-b", "foo" }; 464 CommandLine cmdLine; 465 std::ostringstream err; 466 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); 467 468 DE_TEST_ASSERT(parseOk); 469 DE_TEST_ASSERT(err.str().empty()); 470 471 DE_TEST_ASSERT(cmdLine.getOption<TestStringOpt>() == "--"); 472 DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>()); 473 DE_TEST_ASSERT(cmdLine.getOption<TestIntOpt>() == 0); // Not specified 474 475 DE_TEST_ASSERT(cmdLine.getArgs().size() == 1); 476 DE_TEST_ASSERT(cmdLine.getArgs()[0] == "foo"); 477 } 478 479 // Invalid flag usage 480 { 481 const char* args[] = { "-b=true" }; 482 CommandLine cmdLine; 483 std::ostringstream err; 484 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); 485 486 DE_TEST_ASSERT(!parseOk); 487 DE_TEST_ASSERT(!err.str().empty()); 488 } 489 490 // Invalid named option 491 { 492 const char* args[] = { "-n=two" }; 493 CommandLine cmdLine; 494 std::ostringstream err; 495 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); 496 497 DE_TEST_ASSERT(!parseOk); 498 DE_TEST_ASSERT(!err.str().empty()); 499 } 500 501 // Unrecognized option (-x) 502 { 503 const char* args[] = { "-x" }; 504 CommandLine cmdLine; 505 std::ostringstream err; 506 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); 507 508 DE_TEST_ASSERT(!parseOk); 509 DE_TEST_ASSERT(!err.str().empty()); 510 } 511 512 // Unrecognized option (--xxx) 513 { 514 const char* args[] = { "--xxx" }; 515 CommandLine cmdLine; 516 std::ostringstream err; 517 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); 518 519 DE_TEST_ASSERT(!parseOk); 520 DE_TEST_ASSERT(!err.str().empty()); 521 } 522 523 // Invalid int value 524 { 525 const char* args[] = { "--int", "1x" }; 526 CommandLine cmdLine; 527 std::ostringstream err; 528 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); 529 530 DE_TEST_ASSERT(!parseOk); 531 DE_TEST_ASSERT(!err.str().empty()); 532 } 533 534 // Arg specified multiple times 535 { 536 const char* args[] = { "-s=2", "-s=3" }; 537 CommandLine cmdLine; 538 std::ostringstream err; 539 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); 540 541 DE_TEST_ASSERT(!parseOk); 542 DE_TEST_ASSERT(!err.str().empty()); 543 } 544 545 // Missing value 546 { 547 const char* args[] = { "--int" }; 548 CommandLine cmdLine; 549 std::ostringstream err; 550 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); 551 552 DE_TEST_ASSERT(!parseOk); 553 DE_TEST_ASSERT(!err.str().empty()); 554 } 555 556 // Empty value --arg= 557 { 558 const char* args[] = { "--string=", "-b" }; 559 CommandLine cmdLine; 560 std::ostringstream err; 561 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err); 562 563 DE_TEST_ASSERT(parseOk); 564 DE_TEST_ASSERT(err.str().empty()); 565 DE_TEST_ASSERT(cmdLine.getOption<TestStringOpt>() == ""); 566 DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>()); 567 } 568 } 569} 570 571} // cmdline 572} // de 573