• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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