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