Static Value-Flow Analysis
CommandLine.h
Go to the documentation of this file.
1 #ifndef COMMANDLINE_H_
2 #define COMMANDLINE_H_
3 
4 #include <algorithm>
5 #include <cassert>
6 #include <string>
7 #include <tuple>
8 #include <type_traits>
9 #include <unordered_map>
10 #include <unordered_set>
11 #include <limits>
12 #include <map>
13 #include <vector>
14 #include <cstdlib>
15 #include <iostream>
16 #include <sstream>
17 
18 typedef unsigned u32_t;
19 
21 {
22 protected:
24  typedef std::pair<std::string, std::string> PossibilityDescription;
25  typedef std::vector<std::pair<std::string, std::string>> PossibilityDescriptions;
26 
29  template <typename T>
30  using OptionPossibility = std::tuple<T, std::string, std::string>;
31 
32 protected:
35  {
36  }
37 
40  {
41  assert(name[0] != '-' && "OptionBase: name starts with '-'");
42  assert(!isHelpName(name) && "OptionBase: reserved help name");
43  assert(getOptionsMap().find(name) == getOptionsMap().end() && "OptionBase: duplicate option");
44 
45  // Types with empty names (i.e., OptionMultiple) can handle things themselves.
46  if (!name.empty())
47  {
48  // Standard name=value option.
49  getOptionsMap()[name] = this;
50  }
51  }
52 
54  virtual bool parseAndSetValue(const std::string value) = 0;
55 
58  virtual bool isBool(void) const
59  {
60  return false;
61  }
62 
64  virtual bool isMultiple(void) const
65  {
66  return false;
67  }
68 
70  virtual bool canSet(void) const = 0;
71 
72 public:
75  static std::vector<std::string> parseOptions(int argc, char *argv[], std::string description, std::string callFormat)
76  {
77  const std::string usage = buildUsage(description, std::string(argv[0]), callFormat);
78 
79  std::vector<std::string> positionalArguments;
80 
81  for (int i = 1; i < argc; ++i)
82  {
83  std::string arg(argv[i]);
84  if (arg.empty()) continue;
85  if (arg[0] != '-')
86  {
87  // Positional argument. NOT a value to another argument because we
88  // "skip" over evaluating those without the corresponding argument.
89  positionalArguments.push_back(arg);
90  continue;
91  }
92 
93  // Chop off '-'.
94  arg = arg.substr(1);
95 
96  std::string argName;
97  std::string argValue;
98  OptionBase *opt = nullptr;
99 
100  size_t equalsSign = arg.find('=');
101  if (equalsSign != std::string::npos)
102  {
103  // Argument has an equal sign, i.e. argName=argValue
104  argName = arg.substr(0, equalsSign);
105  if (isHelpName(argName)) usageAndExit(usage, false);
106 
107  opt = getOption(argName);
108  if (opt == nullptr)
109  {
110  std::cerr << "Unknown option: " << argName << std::endl;
111  usageAndExit(usage, true);
112  }
113 
114  argValue = arg.substr(equalsSign + 1);
115  }
116  else
117  {
118  argName = arg;
119  if (isHelpName(argName)) usageAndExit(usage, false);
120 
121  opt = getOption(argName);
122  if (opt == nullptr)
123  {
124  std::cerr << "Unknown option: " << argName << std::endl;
125  usageAndExit(usage, true);
126  }
127 
128  // No equals sign means we may need next argument.
129  if (opt->isBool())
130  {
131  // Booleans do not accept -arg true/-arg false.
132  // They must be -arg=true/-arg=false.
133  argValue = "true";
134  }
135  else if (opt->isMultiple())
136  {
137  // Name is the value and will be converted to an enum.
138  argValue = argName;
139  }
140  else if (i + 1 < argc)
141  {
142  // On iteration, we'll skip the value.
143  ++i;
144  argValue = std::string(argv[i]);
145  }
146  else
147  {
148  std::cerr << "Expected value for: " << argName << std::endl;
149  usageAndExit(usage, true);
150  }
151  }
152 
153  if (!opt->canSet())
154  {
155  std::cerr << "Unable to set: " << argName << "; check for duplicates" << std::endl;
156  usageAndExit(usage, true);
157  }
158 
159  bool valueSet = opt->parseAndSetValue(argValue);
160  if (!valueSet)
161  {
162  std::cerr << "Bad value for: " << argName << std::endl;
163  usageAndExit(usage, true);
164  }
165  }
166 
167  return positionalArguments;
168  }
169 
170 private:
175  const std::string description, const std::string argv0, const std::string callFormat
176  )
177  {
178  // Determine longest option to split into two columns: options and descriptions.
179  unsigned longest = 0;
180  for (const std::pair<std::string, OptionBase *> nopt : getOptionsMap())
181  {
182  const std::string name = std::get<0>(nopt);
183  const OptionBase *option = std::get<1>(nopt);
184  if (option->isMultiple())
185  {
186  // For Multiple, description goes in left column.
187  if (option->description.length() > longest) longest = option->description.length();
188  }
189  else
190  {
191  if (name.length() > longest) longest = name.length();
192  }
193 
194  for (const PossibilityDescription &pd : option->possibilityDescriptions)
195  {
196  const std::string possibility = std::get<0>(pd);
197  if (possibility.length() + 3 > longest) longest = possibility.length() + 3;
198  }
199  }
200 
201  std::stringstream ss;
202 
203  ss << description << std::endl << std::endl;
204 
205  ss << "USAGE:" << std::endl;
206  ss << " " << argv0 << " " << callFormat << std::endl;
207  ss << std::endl;
208 
209  ss << "OPTIONS:" << std::endl;
210 
211  // Required as we have OptionMultiples doing a many-to-one in options.
212  std::unordered_set<const OptionBase *> handled;
213  for (const std::pair<std::string, OptionBase *> nopt : getOptionsMap())
214  {
215  const std::string name = std::get<0>(nopt);
216  const OptionBase *option = std::get<1>(nopt);
217  if (handled.find(option) != handled.end()) continue;
218  handled.insert(option);
219 
220  if (option->isMultiple())
221  {
222  // description
223  // -name1 - description
224  // -name2 - description
225  // ...
226  ss << " " << option->description << std::endl;
227  for (const PossibilityDescription &pd : option->possibilityDescriptions)
228  {
229  const std::string possibility = std::get<0>(pd);
230  const std::string description = std::get<1>(pd);
231  ss << " -" << possibility << std::string(longest - possibility.length() + 2, ' ');
232  ss << "- " << description << std::endl;
233  }
234  }
235  else
236  {
237  // name - description
238  // or
239  // name - description
240  // =opt1 - description
241  // =opt2 - description
242  // ...
243  ss << " -" << name << std::string(longest - name.length() + 2, ' ');
244  ss << "- " << option->description << std::endl;
245  for (const PossibilityDescription &pd : option->possibilityDescriptions)
246  {
247  const std::string possibility = std::get<0>(pd);
248  const std::string description = std::get<1>(pd);
249  ss << " =" << possibility << std::string(longest - possibility.length() + 2, ' ');
250  ss << "- " << description << std::endl;
251  }
252  }
253  }
254 
255  // Help message.
256  ss << std::endl;
257  ss << " -help" << std::string(longest - 4 + 2, ' ') << "- show usage and exit" << std::endl;
258  ss << " -h" << std::string(longest - 1 + 2, ' ') << "- show usage and exit" << std::endl;
259 
260  // How to set boolean options.
261  ss << std::endl;
262  ss << "Note: for boolean options, -name true and -name false are invalid." << std::endl;
263  ss << " Use -name, -name=true, or -name=false." << std::endl;
264 
265  return ss.str();
266  }
267 
269  static OptionBase *getOption(const std::string optName)
270  {
271  auto optIt = getOptionsMap().find(optName);
272  if (optIt == getOptionsMap().end()) return nullptr;
273  else return optIt->second;
274  }
275 
277  static void usageAndExit(const std::string usage, bool error)
278  {
279  if (error) std::cerr << usage;
280  else std::cout << usage;
281  std::exit(error ? 1 : 0);
282  }
283 
285  static bool isHelpName(const std::string name)
286  {
287  static std::vector<std::string> helpNames = {"help", "h", "-help"};
288  return std::find(helpNames.begin(), helpNames.end(), name) != helpNames.end();
289  }
290 
291 protected:
292  // Return the name/description part of OptionsPossibilities (second and third fields).
293  template<typename T>
295  {
297  for (const OptionPossibility<T> &op : possibilities)
298  {
299  possibilityDescriptions.push_back(std::make_pair(std::get<1>(op), std::get<2>(op)));
300  }
301 
303  }
304 
307  static std::map<std::string, OptionBase *> &getOptionsMap(void)
308  {
309  // Not static member to avoid initialisation order problems.
310  static std::map<std::string, OptionBase *> options;
311  return options;
312  }
313 
314 
315 protected:
320 };
321 
324 template <typename T>
325 class Option : public OptionBase
326 {
327 public:
330  {
331  assert(!name.empty() && "Option: empty option name given");
332  }
333 
334  virtual bool canSet(void) const override
335  {
336  // Don't allow duplicates.
337  return !isExplicitlySet;
338  }
339 
340  virtual bool parseAndSetValue(const std::string s) override
341  {
343  return isExplicitlySet;
344  }
345 
346  virtual bool isBool(void) const override
347  {
348  return std::is_same<T, bool>::value;
349  }
350 
351  void setValue(T v)
352  {
353  value = v;
354  }
355 
356  T operator()(void) const
357  {
358  return value;
359  }
360 
361 private:
362  // Convert string to boolean, returning whether we succeeded.
363  static bool fromString(const std::string& s, bool& value)
364  {
365  if (s == "true")
366  value = true;
367  else if (s == "false")
368  value = false;
369  else
370  return false;
371  return true;
372  }
373 
374  // Convert string to string, always succeeds.
375  static bool fromString(const std::string s, std::string &value)
376  {
377  value = s;
378  return true;
379  }
380 
381  // Convert string to u32_t, returning whether we succeeded.
382  static bool fromString(const std::string s, u32_t &value)
383  {
384  // We won't allow anything except [0-9]+.
385  if (s.empty()) return false;
386  for (char c : s)
387  {
388  if (!(c >= '0' && c <= '9')) return false;
389  }
390 
391  // Use strtoul because we're not using exceptions.
392  assert(sizeof(unsigned long) >= sizeof(u32_t));
393  const unsigned long sv = std::strtoul(s.c_str(), nullptr, 10);
394 
395  // Out of range according to strtoul, or according to us compared to u32_t.
396  if (errno == ERANGE || sv > std::numeric_limits<u32_t>::max()) return false;
397  value = sv;
398  return true;
399  }
400 
401 private:
403  T value;
404 };
405 
408 template <typename T>
409 class OptionMap : public OptionBase
410 {
411 public:
412  typedef std::vector<OptionPossibility<T>> OptionPossibilities;
413 
417  {
418  assert(!name.empty() && "OptionMap: empty option name given");
419  }
420 
421  virtual bool canSet(void) const override
422  {
423  return !isExplicitlySet;
424  }
425 
426  virtual bool parseAndSetValue(const std::string s) override
427  {
428  for (const OptionPossibility<T> &op : possibilities)
429  {
430  // Check if the given string is a valid one.
431  if (s == std::get<1>(op))
432  {
433  // What that string maps to.
434  value = std::get<0>(op);
435  isExplicitlySet = true;
436  break;
437  }
438  }
439 
440  return isExplicitlySet;
441  }
442 
443  T operator()(void) const
444  {
445  return value;
446  }
447 
448 private:
450  T value;
452 };
453 
456 template <typename T>
458 {
459 public:
460  typedef std::vector<OptionPossibility<T>> OptionPossibilities;
461 
464  {
465  for (const OptionPossibility<T> &op : possibilities)
466  {
467  optionValues[std::get<0>(op)] = false;
468  }
469 
470  for (const OptionPossibility<T> &op : possibilities)
471  {
472  std::string possibilityName = std::get<1>(op);
473  getOptionsMap()[possibilityName] = this;
474  }
475  }
476 
477  virtual bool canSet(void) const override
478  {
479  return true;
480  }
481 
482  virtual bool parseAndSetValue(const std::string s) override
483  {
484  // Like in OptionMap basically, except we can have many values.
485  for (const OptionPossibility<T> &op : possibilities)
486  {
487  if (s == std::get<1>(op))
488  {
489  optionValues[std::get<0>(op)] = true;
490  return true;
491  }
492  }
493 
494  return false;
495  }
496 
497  virtual bool isMultiple(void) const override
498  {
499  return true;
500  }
501 
503  bool nothingSet(void) const
504  {
505  for (const std::pair<T, bool> tb : optionValues)
506  {
507  if (tb.second) return false;
508  }
509 
510  return true;
511  }
512 
514  bool operator()(const T v) const
515  {
516  typename std::unordered_map<T, bool>::const_iterator ovIt = optionValues.find(v);
517  // TODO: disabled as we check for "invalid" values in some places.
518  // assert(ovIt != optionValues.end() && "OptionMultiple: bad value checked");
519  if (ovIt == optionValues.end()) return false;
520  return ovIt->second;
521  }
522 
523 private:
526  std::unordered_map<T, bool> optionValues;
528 };
529 
530 #endif /* COMMANDLINE_H_ */
unsigned u32_t
Definition: CommandLine.h:18
#define false
Definition: cJSON.cpp:70
const char *const string
Definition: cJSON.h:172
std::pair< std::string, std::string > PossibilityDescription
Name/description pairs.
Definition: CommandLine.h:24
static PossibilityDescriptions extractPossibilityDescriptions(const std::vector< OptionPossibility< T >> possibilities)
Definition: CommandLine.h:294
OptionBase(std::string name, std::string description, PossibilityDescriptions possibilityDescriptions)
Definition: CommandLine.h:38
virtual bool canSet(void) const =0
Can this option be set?
std::string name
Definition: CommandLine.h:316
static std::vector< std::string > parseOptions(int argc, char *argv[], std::string description, std::string callFormat)
Definition: CommandLine.h:75
static std::string buildUsage(const std::string description, const std::string argv0, const std::string callFormat)
Definition: CommandLine.h:174
std::tuple< T, std::string, std::string > OptionPossibility
Definition: CommandLine.h:30
std::string description
Definition: CommandLine.h:317
PossibilityDescriptions possibilityDescriptions
For when we have possibilities like in an OptionMap.
Definition: CommandLine.h:319
static OptionBase * getOption(const std::string optName)
Find option based on name in options map. Returns nullptr if not found.
Definition: CommandLine.h:269
static std::map< std::string, OptionBase * > & getOptionsMap(void)
Definition: CommandLine.h:307
static void usageAndExit(const std::string usage, bool error)
Print usage and exit. If error is set, print to stderr and exits with code 1.
Definition: CommandLine.h:277
virtual bool isBool(void) const
Definition: CommandLine.h:58
virtual bool isMultiple(void) const
Whether this option is an OptionMultiple.
Definition: CommandLine.h:64
std::vector< std::pair< std::string, std::string > > PossibilityDescriptions
Definition: CommandLine.h:25
static bool isHelpName(const std::string name)
Returns whether name is one of the reserved help options.
Definition: CommandLine.h:285
OptionBase(std::string name, std::string description)
Definition: CommandLine.h:33
virtual bool parseAndSetValue(const std::string value)=0
From a given string, set the value of this option.
OptionPossibilities possibilities
Definition: CommandLine.h:451
T operator()(void) const
Definition: CommandLine.h:443
std::vector< OptionPossibility< T > > OptionPossibilities
Definition: CommandLine.h:412
virtual bool parseAndSetValue(const std::string s) override
From a given string, set the value of this option.
Definition: CommandLine.h:426
bool isExplicitlySet
Definition: CommandLine.h:449
virtual bool canSet(void) const override
Can this option be set?
Definition: CommandLine.h:421
OptionMap(std::string name, std::string description, T init, OptionPossibilities possibilities)
Definition: CommandLine.h:414
OptionMultiple(std::string description, OptionPossibilities possibilities)
Definition: CommandLine.h:462
std::unordered_map< T, bool > optionValues
Definition: CommandLine.h:526
bool nothingSet(void) const
If the bitset is empty.
Definition: CommandLine.h:503
std::vector< OptionPossibility< T > > OptionPossibilities
Definition: CommandLine.h:460
virtual bool canSet(void) const override
Can this option be set?
Definition: CommandLine.h:477
virtual bool isMultiple(void) const override
Whether this option is an OptionMultiple.
Definition: CommandLine.h:497
OptionPossibilities possibilities
Definition: CommandLine.h:527
virtual bool parseAndSetValue(const std::string s) override
From a given string, set the value of this option.
Definition: CommandLine.h:482
bool operator()(const T v) const
Returns whether the value v had been set as an option.
Definition: CommandLine.h:514
T operator()(void) const
Definition: CommandLine.h:356
static bool fromString(const std::string &s, bool &value)
Definition: CommandLine.h:363
virtual bool canSet(void) const override
Can this option be set?
Definition: CommandLine.h:334
static bool fromString(const std::string s, u32_t &value)
Definition: CommandLine.h:382
virtual bool parseAndSetValue(const std::string s) override
From a given string, set the value of this option.
Definition: CommandLine.h:340
Option(const std::string &name, const std::string &description, T init)
Definition: CommandLine.h:328
static bool fromString(const std::string s, std::string &value)
Definition: CommandLine.h:375
virtual bool isBool(void) const override
Definition: CommandLine.h:346
bool isExplicitlySet
Definition: CommandLine.h:402
void setValue(T v)
Definition: CommandLine.h:351
unsigned long int strtoul(const char *str, char **endptr, int base)
Definition: extapi.c:983
Definition: cJSON.cpp:89