• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 DE_DECLARE_COMMAND_LINE_OPT(Help,		bool);
40 }
41 
42 namespace detail
43 {
44 
getNamedValueName(const void * namedValue)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 
TypedFieldMap(void)52 TypedFieldMap::TypedFieldMap (void)
53 {
54 }
55 
~TypedFieldMap(void)56 TypedFieldMap::~TypedFieldMap (void)
57 {
58 	clear();
59 }
60 
clear(void)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 
contains(const std::type_info * key) const71 bool TypedFieldMap::contains (const std::type_info* key) const
72 {
73 	return m_fields.find(key) != m_fields.end();
74 }
75 
get(const std::type_info * key) const76 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 
set(const std::type_info * key,const Entry & value)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 
Parser(void)100 Parser::Parser (void)
101 {
102 	addOption(Option<Help>("h", "help", "Show this help"));
103 }
104 
~Parser(void)105 Parser::~Parser (void)
106 {
107 }
108 
addOption(const OptInfo & option)109 void Parser::addOption (const OptInfo& option)
110 {
111 	m_options.push_back(option);
112 }
113 
parse(int numArgs,const char * const * args,CommandLine * dst,std::ostream & err) const114 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->helpSpecified());
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->helpSpecified())
240 		allOk = false;
241 
242 	return allOk;
243 }
244 
help(std::ostream & str) const245 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 
clear(void)289 void CommandLine::clear (void)
290 {
291 	m_options.clear();
292 	m_args.clear();
293 }
294 
helpSpecified(void) const295 bool CommandLine::helpSpecified (void) const
296 {
297 	return m_options.get<Help>();
298 }
299 
findNamedValueMatch(const char * src,const void * namedValues,const void * namedValuesEnd,size_t stride)300 const void* findNamedValueMatch (const char* src, const void* namedValues, const void* namedValuesEnd, size_t stride)
301 {
302 	std::string srcStr(src);
303 
304 	for (const void* curValue = namedValues; curValue != namedValuesEnd; curValue = (const void*)((deUintptr)curValue + stride))
305 	{
306 		if (srcStr == getNamedValueName(curValue))
307 			return curValue;
308 	}
309 
310 	throw std::invalid_argument("unrecognized value '" + srcStr + "'");
311 }
312 
313 } // detail
314 
315 // Default / parsing functions
316 
317 template<>
getTypeDefault(bool * dst)318 void getTypeDefault (bool* dst)
319 {
320 	*dst = false;
321 }
322 
323 template<>
parseType(const char *,bool * dst)324 void parseType<bool> (const char*, bool* dst)
325 {
326 	*dst = true;
327 }
328 
329 template<>
parseType(const char * src,std::string * dst)330 void parseType<std::string> (const char* src, std::string* dst)
331 {
332 	*dst = src;
333 }
334 
335 template<>
parseType(const char * src,int * dst)336 void parseType<int> (const char* src, int* dst)
337 {
338 	std::istringstream str(src);
339 	str >> *dst;
340 	if (str.bad() || !str.eof())
341 		throw std::invalid_argument("invalid integer literal");
342 }
343 
344 // Tests
345 
346 DE_DECLARE_COMMAND_LINE_OPT(TestStringOpt,		std::string);
347 DE_DECLARE_COMMAND_LINE_OPT(TestStringDefOpt,	std::string);
348 DE_DECLARE_COMMAND_LINE_OPT(TestIntOpt,			int);
349 DE_DECLARE_COMMAND_LINE_OPT(TestBoolOpt,		bool);
350 DE_DECLARE_COMMAND_LINE_OPT(TestNamedOpt,		deUint64);
351 
selfTest(void)352 void selfTest (void)
353 {
354 	// Parsing with no options.
355 	{
356 		Parser parser;
357 
358 		{
359 			std::ostringstream	err;
360 			CommandLine			cmdLine;
361 			const bool			parseOk		= parser.parse(0, DE_NULL, &cmdLine, err);
362 
363 			DE_TEST_ASSERT(parseOk && err.str().empty());
364 		}
365 
366 		{
367 			const char*			args[]		= { "-h" };
368 			std::ostringstream	err;
369 			CommandLine			cmdLine;
370 			const bool			parseOk		= parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
371 
372 			DE_TEST_ASSERT(!parseOk);
373 			DE_TEST_ASSERT(err.str().empty()); // No message about -h
374 		}
375 
376 		{
377 			const char*			args[]		= { "--help" };
378 			std::ostringstream	err;
379 			CommandLine			cmdLine;
380 			const bool			parseOk		= parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
381 
382 			DE_TEST_ASSERT(!parseOk);
383 			DE_TEST_ASSERT(err.str().empty()); // No message about -h
384 		}
385 
386 		{
387 			const char*			args[]		= { "foo", "bar", "baz baz" };
388 			std::ostringstream	err;
389 			CommandLine			cmdLine;
390 			const bool			parseOk		= parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
391 
392 			DE_TEST_ASSERT(parseOk && err.str().empty());
393 			DE_TEST_ASSERT(cmdLine.getArgs().size() == DE_LENGTH_OF_ARRAY(args));
394 
395 			for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(args); ndx++)
396 				DE_TEST_ASSERT(cmdLine.getArgs()[ndx] == args[ndx]);
397 		}
398 	}
399 
400 	// Parsing with options.
401 	{
402 		Parser parser;
403 
404 		static const NamedValue<deUint64> s_namedValues[] =
405 		{
406 			{ "zero",	0		},
407 			{ "one",	1		},
408 			{ "huge",	~0ull	}
409 		};
410 
411 		parser << Option<TestStringOpt>		("s",	"string",	"String option")
412 			   << Option<TestStringDefOpt>	("x",	"xyz",		"String option w/ default value",	"foo")
413 			   << Option<TestIntOpt>		("i",	"int",		"Int option")
414 			   << Option<TestBoolOpt>		("b",	"bool",		"Test boolean flag")
415 			   << Option<TestNamedOpt>		("n",	"named",	"Test named opt",	DE_ARRAY_BEGIN(s_namedValues),	DE_ARRAY_END(s_namedValues),	"one");
416 
417 		{
418 			std::ostringstream err;
419 			DE_TEST_ASSERT(err.str().empty());
420 			parser.help(err);
421 			DE_TEST_ASSERT(!err.str().empty());
422 		}
423 
424 		// Default values
425 		{
426 			CommandLine			cmdLine;
427 			std::ostringstream	err;
428 			bool				parseOk	= parser.parse(0, DE_NULL, &cmdLine, err);
429 
430 			DE_TEST_ASSERT(parseOk);
431 			DE_TEST_ASSERT(err.str().empty());
432 
433 			DE_TEST_ASSERT(!cmdLine.hasOption<TestStringOpt>());
434 			DE_TEST_ASSERT(!cmdLine.hasOption<TestIntOpt>());
435 			DE_TEST_ASSERT(cmdLine.getOption<TestNamedOpt>() == 1);
436 			DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>() == false);
437 			DE_TEST_ASSERT(cmdLine.getOption<TestStringDefOpt>() == "foo");
438 		}
439 
440 		// Basic parsing
441 		{
442 			const char*			args[]	= { "-s", "test value", "-b", "-i=9", "--named=huge" };
443 			CommandLine			cmdLine;
444 			std::ostringstream	err;
445 			bool				parseOk	= parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
446 
447 			DE_TEST_ASSERT(parseOk);
448 			DE_TEST_ASSERT(err.str().empty());
449 
450 			DE_TEST_ASSERT(cmdLine.getOption<TestStringOpt>() == "test value");
451 			DE_TEST_ASSERT(cmdLine.getOption<TestIntOpt>() == 9);
452 			DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>());
453 			DE_TEST_ASSERT(cmdLine.getOption<TestNamedOpt>() == ~0ull);
454 			DE_TEST_ASSERT(cmdLine.getOption<TestStringDefOpt>() == "foo");
455 		}
456 
457 		// End of argument list (--)
458 		{
459 			const char*			args[]	= { "--string=foo", "-b", "--", "--int=2", "-b" };
460 			CommandLine			cmdLine;
461 			std::ostringstream	err;
462 			bool				parseOk	= parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
463 
464 			DE_TEST_ASSERT(parseOk);
465 			DE_TEST_ASSERT(err.str().empty());
466 
467 			DE_TEST_ASSERT(cmdLine.getOption<TestStringOpt>() == "foo");
468 			DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>());
469 			DE_TEST_ASSERT(!cmdLine.hasOption<TestIntOpt>());
470 
471 			DE_TEST_ASSERT(cmdLine.getArgs().size() == 2);
472 			DE_TEST_ASSERT(cmdLine.getArgs()[0] == "--int=2");
473 			DE_TEST_ASSERT(cmdLine.getArgs()[1] == "-b");
474 		}
475 
476 		// Value --
477 		{
478 			const char*			args[]	= { "--string", "--", "-b", "foo" };
479 			CommandLine			cmdLine;
480 			std::ostringstream	err;
481 			bool				parseOk	= parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
482 
483 			DE_TEST_ASSERT(parseOk);
484 			DE_TEST_ASSERT(err.str().empty());
485 
486 			DE_TEST_ASSERT(cmdLine.getOption<TestStringOpt>() == "--");
487 			DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>());
488 			DE_TEST_ASSERT(!cmdLine.hasOption<TestIntOpt>());
489 
490 			DE_TEST_ASSERT(cmdLine.getArgs().size() == 1);
491 			DE_TEST_ASSERT(cmdLine.getArgs()[0] == "foo");
492 		}
493 
494 		// Invalid flag usage
495 		{
496 			const char*			args[]	= { "-b=true" };
497 			CommandLine			cmdLine;
498 			std::ostringstream	err;
499 			bool				parseOk	= parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
500 
501 			DE_TEST_ASSERT(!parseOk);
502 			DE_TEST_ASSERT(!err.str().empty());
503 		}
504 
505 		// Invalid named option
506 		{
507 			const char*			args[]	= { "-n=two" };
508 			CommandLine			cmdLine;
509 			std::ostringstream	err;
510 			bool				parseOk	= parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
511 
512 			DE_TEST_ASSERT(!parseOk);
513 			DE_TEST_ASSERT(!err.str().empty());
514 		}
515 
516 		// Unrecognized option (-x)
517 		{
518 			const char*			args[]	= { "-x" };
519 			CommandLine			cmdLine;
520 			std::ostringstream	err;
521 			bool				parseOk	= parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
522 
523 			DE_TEST_ASSERT(!parseOk);
524 			DE_TEST_ASSERT(!err.str().empty());
525 		}
526 
527 		// Unrecognized option (--xxx)
528 		{
529 			const char*			args[]	= { "--xxx" };
530 			CommandLine			cmdLine;
531 			std::ostringstream	err;
532 			bool				parseOk	= parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
533 
534 			DE_TEST_ASSERT(!parseOk);
535 			DE_TEST_ASSERT(!err.str().empty());
536 		}
537 
538 		// Invalid int value
539 		{
540 			const char*			args[]	= { "--int", "1x" };
541 			CommandLine			cmdLine;
542 			std::ostringstream	err;
543 			bool				parseOk	= parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
544 
545 			DE_TEST_ASSERT(!parseOk);
546 			DE_TEST_ASSERT(!err.str().empty());
547 		}
548 
549 		// Arg specified multiple times
550 		{
551 			const char*			args[]	= { "-s=2", "-s=3" };
552 			CommandLine			cmdLine;
553 			std::ostringstream	err;
554 			bool				parseOk	= parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
555 
556 			DE_TEST_ASSERT(!parseOk);
557 			DE_TEST_ASSERT(!err.str().empty());
558 		}
559 
560 		// Missing value
561 		{
562 			const char*			args[]	= { "--int" };
563 			CommandLine			cmdLine;
564 			std::ostringstream	err;
565 			bool				parseOk	= parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
566 
567 			DE_TEST_ASSERT(!parseOk);
568 			DE_TEST_ASSERT(!err.str().empty());
569 		}
570 
571 		// Empty value --arg=
572 		{
573 			const char*			args[]	= { "--string=", "-b", "-x", "" };
574 			CommandLine			cmdLine;
575 			std::ostringstream	err;
576 			bool				parseOk	= parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
577 
578 			DE_TEST_ASSERT(parseOk);
579 			DE_TEST_ASSERT(err.str().empty());
580 			DE_TEST_ASSERT(cmdLine.getOption<TestStringOpt>() == "");
581 			DE_TEST_ASSERT(cmdLine.getOption<TestStringDefOpt>() == "");
582 			DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>());
583 		}
584 	}
585 }
586 
587 } // cmdline
588 } // de
589