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	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->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
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
295const 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
312template<>
313void getTypeDefault (bool* dst)
314{
315	*dst = false;
316}
317
318template<>
319void parseType<bool> (const char*, bool* dst)
320{
321	*dst = true;
322}
323
324template<>
325void parseType<std::string> (const char* src, std::string* dst)
326{
327	*dst = src;
328}
329
330template<>
331void 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
341DE_DECLARE_COMMAND_LINE_OPT(TestStringOpt,		std::string);
342DE_DECLARE_COMMAND_LINE_OPT(TestStringDefOpt,	std::string);
343DE_DECLARE_COMMAND_LINE_OPT(TestIntOpt,			int);
344DE_DECLARE_COMMAND_LINE_OPT(TestBoolOpt,		bool);
345DE_DECLARE_COMMAND_LINE_OPT(TestNamedOpt,		deUint64);
346
347void 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