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