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