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{
39DE_DECLARE_COMMAND_LINE_OPT(Help,		bool);
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	clear();
59}
60
61void 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
71bool TypedFieldMap::contains (const std::type_info* key) const
72{
73	return m_fields.find(key) != m_fields.end();
74}
75
76const 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
85void 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
100Parser::Parser (void)
101{
102	addOption(Option<Help>("h", "help", "Show this help"));
103}
104
105Parser::~Parser (void)
106{
107}
108
109void Parser::addOption (const OptInfo& option)
110{
111	m_options.push_back(option);
112}
113
114bool 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
245void 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
289void CommandLine::clear (void)
290{
291	m_options.clear();
292	m_args.clear();
293}
294
295bool CommandLine::helpSpecified (void) const
296{
297	return m_options.get<Help>();
298}
299
300const 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
317template<>
318void getTypeDefault (bool* dst)
319{
320	*dst = false;
321}
322
323template<>
324void parseType<bool> (const char*, bool* dst)
325{
326	*dst = true;
327}
328
329template<>
330void parseType<std::string> (const char* src, std::string* dst)
331{
332	*dst = src;
333}
334
335template<>
336void 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
346DE_DECLARE_COMMAND_LINE_OPT(TestStringOpt,		std::string);
347DE_DECLARE_COMMAND_LINE_OPT(TestStringDefOpt,	std::string);
348DE_DECLARE_COMMAND_LINE_OPT(TestIntOpt,			int);
349DE_DECLARE_COMMAND_LINE_OPT(TestBoolOpt,		bool);
350DE_DECLARE_COMMAND_LINE_OPT(TestNamedOpt,		deUint64);
351
352void 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