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<uint8_t> *>(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 = nullptr;
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 : nullptr;
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, nullptr, &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]) : nullptr;
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()
221 << "\n";
222 allOk = false;
223 }
224 }
225 else
226 {
227 err << "Expected value for command line option '--" << opt->longName << "'\n";
228 allOk = false;
229 }
230 }
231 }
232 else
233 {
234 // Not an option
235 dst->m_args.push_back(arg);
236 }
237 }
238
239 // Help specified?
240 if (dst->helpSpecified())
241 allOk = false;
242
243 return allOk;
244 }
245
help(std::ostream & str) const246 void Parser::help(std::ostream &str) const
247 {
248 for (vector<OptInfo>::const_iterator optIter = m_options.begin(); optIter != m_options.end(); ++optIter)
249 {
250 const OptInfo &opt = *optIter;
251
252 str << " ";
253 if (opt.shortName)
254 str << "-" << opt.shortName;
255
256 if (opt.shortName && opt.longName)
257 str << ", ";
258
259 if (opt.longName)
260 str << "--" << opt.longName;
261
262 if (opt.namedValues)
263 {
264 str << "=[";
265
266 for (const void *curValue = opt.namedValues; curValue != opt.namedValuesEnd;
267 curValue = (const void *)((uintptr_t)curValue + opt.namedValueStride))
268 {
269 if (curValue != opt.namedValues)
270 str << "|";
271 str << getNamedValueName(curValue);
272 }
273
274 str << "]";
275 }
276 else if (!opt.isFlag)
277 str << "=<value>";
278
279 str << "\n";
280
281 if (opt.description)
282 str << " " << opt.description << "\n";
283
284 if (opt.defaultValue)
285 str << " default: '" << opt.defaultValue << "'\n";
286
287 str << "\n";
288 }
289 }
290
clear(void)291 void CommandLine::clear(void)
292 {
293 m_options.clear();
294 m_args.clear();
295 }
296
helpSpecified(void) const297 bool CommandLine::helpSpecified(void) const
298 {
299 return m_options.get<Help>();
300 }
301
findNamedValueMatch(const char * src,const void * namedValues,const void * namedValuesEnd,size_t stride)302 const void *findNamedValueMatch(const char *src, const void *namedValues, const void *namedValuesEnd, size_t stride)
303 {
304 std::string srcStr(src);
305
306 for (const void *curValue = namedValues; curValue != namedValuesEnd;
307 curValue = (const void *)((uintptr_t)curValue + stride))
308 {
309 if (srcStr == getNamedValueName(curValue))
310 return curValue;
311 }
312
313 throw std::invalid_argument("unrecognized value '" + srcStr + "'");
314 }
315
316 } // namespace detail
317
318 // Default / parsing functions
319
320 template <>
getTypeDefault(bool * dst)321 void getTypeDefault(bool *dst)
322 {
323 *dst = false;
324 }
325
326 template <>
parseType(const char *,bool * dst)327 void parseType<bool>(const char *, bool *dst)
328 {
329 *dst = true;
330 }
331
332 template <>
parseType(const char * src,std::string * dst)333 void parseType<std::string>(const char *src, std::string *dst)
334 {
335 *dst = src;
336 }
337
338 template <>
parseType(const char * src,int * dst)339 void parseType<int>(const char *src, int *dst)
340 {
341 std::istringstream str(src);
342 str >> *dst;
343 if (str.bad() || !str.eof())
344 throw std::invalid_argument("invalid integer literal");
345 }
346
347 // Tests
348
349 DE_DECLARE_COMMAND_LINE_OPT(TestStringOpt, std::string);
350 DE_DECLARE_COMMAND_LINE_OPT(TestStringDefOpt, std::string);
351 DE_DECLARE_COMMAND_LINE_OPT(TestIntOpt, int);
352 DE_DECLARE_COMMAND_LINE_OPT(TestBoolOpt, bool);
353 DE_DECLARE_COMMAND_LINE_OPT(TestNamedOpt, uint64_t);
354
selfTest(void)355 void selfTest(void)
356 {
357 // Parsing with no options.
358 {
359 Parser parser;
360
361 {
362 std::ostringstream err;
363 CommandLine cmdLine;
364 const bool parseOk = parser.parse(0, nullptr, &cmdLine, err);
365
366 DE_TEST_ASSERT(parseOk && err.str().empty());
367 }
368
369 {
370 const char *args[] = {"-h"};
371 std::ostringstream err;
372 CommandLine cmdLine;
373 const bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
374
375 DE_TEST_ASSERT(!parseOk);
376 DE_TEST_ASSERT(err.str().empty()); // No message about -h
377 }
378
379 {
380 const char *args[] = {"--help"};
381 std::ostringstream err;
382 CommandLine cmdLine;
383 const bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
384
385 DE_TEST_ASSERT(!parseOk);
386 DE_TEST_ASSERT(err.str().empty()); // No message about -h
387 }
388
389 {
390 const char *args[] = {"foo", "bar", "baz baz"};
391 std::ostringstream err;
392 CommandLine cmdLine;
393 const bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
394
395 DE_TEST_ASSERT(parseOk && err.str().empty());
396 DE_TEST_ASSERT(cmdLine.getArgs().size() == DE_LENGTH_OF_ARRAY(args));
397
398 for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(args); ndx++)
399 DE_TEST_ASSERT(cmdLine.getArgs()[ndx] == args[ndx]);
400 }
401 }
402
403 // Parsing with options.
404 {
405 Parser parser;
406
407 static const NamedValue<uint64_t> s_namedValues[] = {{"zero", 0}, {"one", 1}, {"huge", ~0ull}};
408
409 parser << Option<TestStringOpt>("s", "string", "String option")
410 << Option<TestStringDefOpt>("x", "xyz", "String option w/ default value", "foo")
411 << Option<TestIntOpt>("i", "int", "Int option") << Option<TestBoolOpt>("b", "bool", "Test boolean flag")
412 << Option<TestNamedOpt>("n", "named", "Test named opt", DE_ARRAY_BEGIN(s_namedValues),
413 DE_ARRAY_END(s_namedValues), "one");
414
415 {
416 std::ostringstream err;
417 DE_TEST_ASSERT(err.str().empty());
418 parser.help(err);
419 DE_TEST_ASSERT(!err.str().empty());
420 }
421
422 // Default values
423 {
424 CommandLine cmdLine;
425 std::ostringstream err;
426 bool parseOk = parser.parse(0, nullptr, &cmdLine, err);
427
428 DE_TEST_ASSERT(parseOk);
429 DE_TEST_ASSERT(err.str().empty());
430
431 DE_TEST_ASSERT(!cmdLine.hasOption<TestStringOpt>());
432 DE_TEST_ASSERT(!cmdLine.hasOption<TestIntOpt>());
433 DE_TEST_ASSERT(cmdLine.getOption<TestNamedOpt>() == 1);
434 DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>() == false);
435 DE_TEST_ASSERT(cmdLine.getOption<TestStringDefOpt>() == "foo");
436 }
437
438 // Basic parsing
439 {
440 const char *args[] = {"-s", "test value", "-b", "-i=9", "--named=huge"};
441 CommandLine cmdLine;
442 std::ostringstream err;
443 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
444
445 DE_TEST_ASSERT(parseOk);
446 DE_TEST_ASSERT(err.str().empty());
447
448 DE_TEST_ASSERT(cmdLine.getOption<TestStringOpt>() == "test value");
449 DE_TEST_ASSERT(cmdLine.getOption<TestIntOpt>() == 9);
450 DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>());
451 DE_TEST_ASSERT(cmdLine.getOption<TestNamedOpt>() == ~0ull);
452 DE_TEST_ASSERT(cmdLine.getOption<TestStringDefOpt>() == "foo");
453 }
454
455 // End of argument list (--)
456 {
457 const char *args[] = {"--string=foo", "-b", "--", "--int=2", "-b"};
458 CommandLine cmdLine;
459 std::ostringstream err;
460 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
461
462 DE_TEST_ASSERT(parseOk);
463 DE_TEST_ASSERT(err.str().empty());
464
465 DE_TEST_ASSERT(cmdLine.getOption<TestStringOpt>() == "foo");
466 DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>());
467 DE_TEST_ASSERT(!cmdLine.hasOption<TestIntOpt>());
468
469 DE_TEST_ASSERT(cmdLine.getArgs().size() == 2);
470 DE_TEST_ASSERT(cmdLine.getArgs()[0] == "--int=2");
471 DE_TEST_ASSERT(cmdLine.getArgs()[1] == "-b");
472 }
473
474 // Value --
475 {
476 const char *args[] = {"--string", "--", "-b", "foo"};
477 CommandLine cmdLine;
478 std::ostringstream err;
479 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
480
481 DE_TEST_ASSERT(parseOk);
482 DE_TEST_ASSERT(err.str().empty());
483
484 DE_TEST_ASSERT(cmdLine.getOption<TestStringOpt>() == "--");
485 DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>());
486 DE_TEST_ASSERT(!cmdLine.hasOption<TestIntOpt>());
487
488 DE_TEST_ASSERT(cmdLine.getArgs().size() == 1);
489 DE_TEST_ASSERT(cmdLine.getArgs()[0] == "foo");
490 }
491
492 // Invalid flag usage
493 {
494 const char *args[] = {"-b=true"};
495 CommandLine cmdLine;
496 std::ostringstream err;
497 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
498
499 DE_TEST_ASSERT(!parseOk);
500 DE_TEST_ASSERT(!err.str().empty());
501 }
502
503 // Invalid named option
504 {
505 const char *args[] = {"-n=two"};
506 CommandLine cmdLine;
507 std::ostringstream err;
508 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
509
510 DE_TEST_ASSERT(!parseOk);
511 DE_TEST_ASSERT(!err.str().empty());
512 }
513
514 // Unrecognized option (-x)
515 {
516 const char *args[] = {"-x"};
517 CommandLine cmdLine;
518 std::ostringstream err;
519 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
520
521 DE_TEST_ASSERT(!parseOk);
522 DE_TEST_ASSERT(!err.str().empty());
523 }
524
525 // Unrecognized option (--xxx)
526 {
527 const char *args[] = {"--xxx"};
528 CommandLine cmdLine;
529 std::ostringstream err;
530 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
531
532 DE_TEST_ASSERT(!parseOk);
533 DE_TEST_ASSERT(!err.str().empty());
534 }
535
536 // Invalid int value
537 {
538 const char *args[] = {"--int", "1x"};
539 CommandLine cmdLine;
540 std::ostringstream err;
541 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
542
543 DE_TEST_ASSERT(!parseOk);
544 DE_TEST_ASSERT(!err.str().empty());
545 }
546
547 // Arg specified multiple times
548 {
549 const char *args[] = {"-s=2", "-s=3"};
550 CommandLine cmdLine;
551 std::ostringstream err;
552 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
553
554 DE_TEST_ASSERT(!parseOk);
555 DE_TEST_ASSERT(!err.str().empty());
556 }
557
558 // Missing value
559 {
560 const char *args[] = {"--int"};
561 CommandLine cmdLine;
562 std::ostringstream err;
563 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
564
565 DE_TEST_ASSERT(!parseOk);
566 DE_TEST_ASSERT(!err.str().empty());
567 }
568
569 // Empty value --arg=
570 {
571 const char *args[] = {"--string=", "-b", "-x", ""};
572 CommandLine cmdLine;
573 std::ostringstream err;
574 bool parseOk = parser.parse(DE_LENGTH_OF_ARRAY(args), &args[0], &cmdLine, err);
575
576 DE_TEST_ASSERT(parseOk);
577 DE_TEST_ASSERT(err.str().empty());
578 DE_TEST_ASSERT(cmdLine.getOption<TestStringOpt>() == "");
579 DE_TEST_ASSERT(cmdLine.getOption<TestStringDefOpt>() == "");
580 DE_TEST_ASSERT(cmdLine.getOption<TestBoolOpt>());
581 }
582 }
583 }
584
585 } // namespace cmdline
586 } // namespace de
587