Utilities  1
Collection of utility classes and functions used by my C++ applications.
 All Classes Namespaces Files Functions Typedefs Enumerations Enumerator Friends Macros
argumentparser.cpp
Go to the documentation of this file.
1 #include "argumentparser.h"
2 #include "failure.h"
3 #include "../conversion/stringconversion.h"
4 
5 #include <algorithm>
6 #include <vector>
7 #include <iostream>
8 #include <sstream>
9 #include <stdexcept>
10 
11 using namespace std;
12 
17 namespace ApplicationUtilities {
18 
34 Argument::Argument(const std::string &name, const std::string abbreviation, const std::string &description) :
35  m_required(false),
36  m_combinable(false),
37  m_requiredValueCount(0),
38  m_present(false),
39  m_isMainArg(false)
40 {
41  setName(name);
42  setAbbreviation(abbreviation);
43  setDescription(description);
44 }
45 
51 Argument::Argument(const char *name, const char *abbreviation, const char *description) :
52  m_required(false),
53  m_combinable(false),
54  m_requiredValueCount(0),
55  m_present(false),
56  m_isMainArg(false)
57 {
58  setName(name);
59  setAbbreviation(abbreviation);
60  setDescription(description);
61 }
62 
67 {}
68 
73 void Argument::printInfo(ostream &os) const
74 {
75  if(!name().empty())
76  os << "--" << name();
77  if(!name().empty() && !abbreviation().empty())
78  os << ", ";
79  if(!abbreviation().empty())
80  os << "-" << abbreviation();
81  if(requiredValueCount() > 0) {
82  int valueNamesPrint = 0;
83  for(auto i = valueNames().cbegin(), end = valueNames().cend(); i != end && valueNamesPrint < requiredValueCount(); ++i) {
84  os << " [" << *i << "]";
85  ++valueNamesPrint;
86  }
87  for(; valueNamesPrint < requiredValueCount(); ++valueNamesPrint) {
88  os << " [value " << (valueNamesPrint + 1) << "]";
89  }
90  } else if(requiredValueCount() < 0) {
91  for(auto i = valueNames().cbegin(), end = valueNames().cend(); i != end; ++i) {
92  os << " [" << *i << "]";
93  }
94  os << " ...";
95  }
96 
97  if(!description().empty()) {
98  os << endl << description();
99  }
100  if(isRequired()) {
101  os << endl << "This argument is required.";
102  }
103  os << endl << endl;
104 }
105 
111 {
112  for(Argument *arg : args) {
113  if(arg != except && arg->isPresent() && !arg->isCombinable()) {
114  return arg;
115  }
116  }
117  return nullptr;
118 }
119 
133 {
134  // remove this argument from the parents list of the previous secondary arguments
135  for(Argument *arg : m_secondaryArgs) {
136  m_parents.erase(remove(arg->m_parents.begin(), arg->m_parents.end(), this), m_parents.end());
137  }
138  // assign secondary arguments
139  m_secondaryArgs.assign(secondaryArguments);
140  // add this argument to the parents list of the assigned secondary arguments
141  // and set the parser
142  for(Argument *arg : m_secondaryArgs) {
143  if(find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
144  arg->m_parents.push_back(this);
145  }
146  }
147 }
148 
154 string Argument::parentNames() const
155 {
156  string res;
157  if(m_parents.size()) {
158  vector<string> names;
159  names.reserve(m_parents.size());
160  for(const Argument *parent : m_parents) {
161  names.push_back(parent->name());
162  }
163  res.assign(ConversionUtilities::concateStrings(names, ", "));
164  }
165  return res;
166 }
167 
173 {
174  for(Argument *parent : m_parents) {
175  if(parent->isPresent()) {
176  return true;
177  }
178  }
179  return false;
180 }
181 
187 {
188  if(!isCombinable() && isPresent()) {
189  for(Argument *parent : m_parents) {
190  for(Argument *sibling : parent->secondaryArguments()) {
191  if(sibling != this && sibling->isPresent() && !sibling->isCombinable()) {
192  return sibling;
193  }
194  }
195  }
196  }
197  return nullptr;
198 }
199 
217  m_actualArgc(0),
218  m_ignoreUnknownArgs(true)
219 {}
220 
229 {
230  for(Argument *arg : mainArguments) {
231  arg->m_isMainArg = true;
232  }
233  m_mainArgs.assign(mainArguments);
234 }
235 
239 void ArgumentParser::printHelp(ostream &os) const
240 {
241  if(!m_mainArgs.size())
242  return;
243  os << "Available parameters:\n\n";
244  for(const Argument *arg : m_mainArgs) {
245  arg->printInfo(os);
246  }
247 }
248 
254 {
255  return findArg(m_mainArgs, predicate);
256 }
257 
262 Argument *ArgumentParser::findArg(const ArgumentVector &arguments, const ArgumentPredicate &predicate) const
263 {
264  for(Argument *arg : arguments) {
265  if(predicate(arg)) {
266  return arg; // argument matches
267  } else if(Argument *subarg = findArg(arg->secondaryArguments(), predicate)) {
268  return subarg; // a secondary argument matches
269  }
270  }
271  return nullptr; // no argument matches
272 }
273 
287 {
288  vector<Argument *> verifiedArgs;
289  vector<string> abbreviations;
290  vector<string> names;
291  function<void (const ArgumentVector &args)> checkArguments;
292  checkArguments = [&verifiedArgs, &abbreviations, &names, &checkArguments] (const ArgumentVector &args) {
293  for(Argument *arg : args) {
294  if(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) != verifiedArgs.cend())
295  continue; // do not verify the same argument twice
296  if(arg->isMainArgument() && arg->parents().size())
297  throw invalid_argument("Argument \"" + arg->name() + "\" can not be used as main argument and sub argument at the same time.");
298  if(!arg->abbreviation().empty() && find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) != abbreviations.cend())
299  throw invalid_argument("Abbreviation \"" + arg->abbreviation() + "\" has been used more then once.");
300  if(find(names.cbegin(), names.cend(), arg->name()) != names.cend())
301  throw invalid_argument("Name \"" + arg->name() + "\" has been used more then once.");
302  abbreviations.push_back(arg->abbreviation());
303  names.push_back(arg->name());
304  verifiedArgs.push_back(arg);
305  }
306  };
307  checkArguments(m_mainArgs);
308 }
309 
323 void ArgumentParser::parseArgs(int argc, char *argv[])
324 {
325  // initiate parser
326  verifySetup();
327  m_actualArgc = 0; // reset actual agument count
328  unsigned int actualArgc = 0;
329  int valuesToRead = 0;
330  // read current directory
331  if(argc >= 1) {
332  m_currentDirectory = string(*argv);
333  } else {
334  m_currentDirectory.clear();
335  }
336  // parse given arguments
337  if(argc >= 2) {
338  Argument *currentArg = nullptr;
339  // iterate through given arguments
340  for(char **i = argv + 1, ** end = argv + argc; i != end; ++i) {
341  string givenArg(*i); // convert argument to string
342  if(!givenArg.empty()) { // skip empty entries
343  if(valuesToRead <= 0 && givenArg.size() > 1 && givenArg.at(0) == '-') {
344  // we have no values left to read and the given arguments starts with '-'
345  // -> the next "value" is a main argument or a sub argument
346  ArgumentPredicate pred;
347  size_t equationPos = givenArg.find('=');
348  if(givenArg.size() > 2 && givenArg.at(1) == '-') {
349  // the argument starts with '--'
350  // -> the full argument name has been provided
351  pred = [&givenArg, equationPos] (Argument *arg) {
352  return arg->name() == givenArg.substr(2, equationPos);
353  };
354  } else {
355  // the argument starts with a single '-'
356  // -> the abbreviation has been provided
357  pred = [&givenArg, equationPos] (Argument *arg) {
358  return arg->abbreviation() == givenArg.substr(1, equationPos);
359  };
360  }
361 
362  // find the corresponding instande of the Argument class
363  currentArg = findArg(pred);
364 
365  if(currentArg) {
366  // the corresponding instance of Argument class has been found
367  if(currentArg->m_present) {
368  // the argument has been provided more then once
369  throw Failure("The argument \"" + currentArg->name() + "\" has been given more then one time.");
370  } else {
371  // set present flag of argument
372  currentArg->m_present = true;
373  ++actualArgc; // we actually found an argument
374  // now we might need to read values tied to that argument
375  valuesToRead = currentArg->requiredValueCount();
376  if(equationPos != string::npos) {
377  // a value has been specified using the --argument=value syntax
378  string value = givenArg.substr(equationPos + 1);
379  if(valuesToRead != 0) {
380  currentArg->m_values.push_back(value);
381  if(valuesToRead > 0) {
382  --valuesToRead;
383  }
384  } else {
385  throw Failure("Invalid extra information \"" + value + "\" (specified using \"--argument=value\" syntax) for the argument \"" + currentArg->name() + "\" given.");
386  }
387  }
388  }
389  } else {
390  // the given argument seems to be unknown
391  if(valuesToRead < 0) {
392  // we have a variable number of values to expect -> treat "unknown argument" as value
393  goto readValue;
394  } else {
395  // we have no more values to expect so we need to complain about the unknown argument
396  goto invalidArg;
397  }
398  }
399  } else if(currentArg) {
400  readValue:
401  // check if the given value is tied to the most recently parsed argument
402  if(valuesToRead == 0) {
403  throw Failure("Invalid extra information \"" + givenArg + "\" for the argument \"" + currentArg->name() + "\" given.");
404  } else if(valuesToRead < 0) {
405  currentArg->m_values.push_back(givenArg);
406  } else {
407  currentArg->m_values.push_back(givenArg);
408  --valuesToRead; // one value less to be read
409  }
410  } else {
411  // we have not parsed an argument before -> the "value" has to be an argument
412  // but does not start with '-' or '--'
413  invalidArg:
414  string msg("The given argument \"" + givenArg + "\" is unknown.");
415  if(m_ignoreUnknownArgs) {
416  cout << msg << " It will be ignored." << endl;
417  } else {
418  throw Failure(msg);
419  }
420  }
421  }
422  }
423  }
424  // function for iterating through all arguments
425  function<void(Argument *, const ArgumentVector &, const function<void (Argument *, Argument *)> &)> foreachArg;
426  foreachArg = [&foreachArg] (Argument *parent, const ArgumentVector &args, const function<void (Argument *, Argument *)> &proc) {
427  for(Argument *arg : args) {
428  proc(parent, arg);
429  foreachArg(arg, arg->secondaryArguments(), proc);
430  }
431  };
432  // iterate actually through all arguments using previously defined function to check gathered arguments
433  foreachArg(nullptr, m_mainArgs, [this] (Argument *parent, Argument *arg) {
434  if(arg->isPresent()) {
435  if(!arg->isMainArgument() && arg->parents().size() && !arg->isParentPresent()) {
436  if(arg->parents().size() > 1) {
437  throw Failure("The argument \"" + arg->name() + "\" needs to be used together with one the following arguments: " + arg->parentNames());
438  } else {
439  throw Failure("The argument \"" + arg->name() + "\" needs to be used together with the argument \"" + arg->parents().at(0)->name() + "\".");
440  }
441  }
442  Argument *conflictingArgument = nullptr;
443  if(arg->isMainArgument()) {
444  conflictingArgument = firstPresentUncombinableArg(m_mainArgs, arg);
445  } else {
446  conflictingArgument = arg->conflictsWithArgument();
447  }
448  if(conflictingArgument) {
449  throw Failure("The argument \"" + conflictingArgument->name() + "\" can not be combined with \"" + arg->name() + "\".");
450  }
451  if(!arg->allRequiredValuesPresent()) {
452  stringstream ss(stringstream::in | stringstream::out);
453  ss << "Not all required information for the given argument \"" << arg->name() << "\" provided. You have to give the following information:";
454  int valueNamesPrint = 0;
455  for(auto i = arg->m_valueNames.cbegin(), end = arg->m_valueNames.cend(); i != end; ++i) {
456  ss << "\n" << *i;
457  ++valueNamesPrint;
458  }
459  for(; valueNamesPrint < arg->m_requiredValueCount; ++valueNamesPrint) {
460  ss << "\nvalue " << (valueNamesPrint + 1);
461  }
462  throw Failure(ss.str());
463  }
464  } else if(arg->isRequired() && (arg->isMainArgument() || (parent && parent->isPresent()))) {
465  throw Failure("The argument \"" + arg->name() + "\" is required but not given.");
466  }
467  });
468  // set actual argument count
469  m_actualArgc = actualArgc;
470  // interate through all arguments again to invoke callback functions
471  foreachArg(nullptr, m_mainArgs, [] (Argument *, Argument *arg) {
472  if(arg->isPresent() && arg->m_callbackFunction) {
473  arg->m_callbackFunction(arg->values());
474  }
475  });
476 }
477 
478 }
479 
480 
void parseArgs(int argc, char *argv[])
This method invokes verifySetup() before parsing.
std::initializer_list< Argument * > ArgumentInitializerList
void setDescription(const std::string &description)
Sets the description of the argument.
bool isPresent() const
Returns an indication whether the argument could be detected when parsing.
ArgumentParser()
Constructs a new ArgumentParser.
const StringVector & values() const
Returns the additional values for the argument.
const ArgumentVector & secondaryArguments() const
Returns the secondary arguments for this argument.
const ArgumentVector parents() const
Returns the parents of this argument.
Contains currently only ArgumentParser and related classes.
const std::string & abbreviation() const
Returns the abbreviation of the argument.
Argument * conflictsWithArgument() const
Checks if this arguments conflicts with other arguments.
std::string parentNames() const
Returns the names of the parents in the form "parent1", "parent2, "parent3", ...
void setMainArguments(const ArgumentInitializerList &mainArguments)
Sets the main arguments for the parser.
STL namespace.
Argument * firstPresentUncombinableArg(const ArgumentVector &args, const Argument *except)
This function return the first present and uncombinable argument of the given list of arguments...
LIB_EXPORT std::string concateStrings(const Container &strings, const std::string &delimiter=std::string(), bool skipEmpty=false, const std::string &leftClosure=std::string(), const std::string &rightClosure=std::string())
Concates the given strings using the specified delimiter.
~Argument()
Destroys the Argument.
void printInfo(std::ostream &os) const
Appends the name, the abbreviation and the description of the Argument to the give ostream...
Argument(const std::string &name, const std::string abbreviation=std::string(), const std::string &description=std::string())
Constructs an Argument with the given name, abbreviation and description.
std::function< bool(Argument *)> ArgumentPredicate
bool isRequired() const
Returns an indication whether the argument is mandatory.
void printHelp(std::ostream &os) const
Prints help information for all main arguments which have been set using setMainArguments().
bool isCombinable() const
Returns an indication whether the argument is combinable.
void setSecondaryArguments(const ArgumentInitializerList &secondaryArguments)
Sets the secondary arguments for this arguments.
void setName(const std::string &name)
Sets the name of the argument.
void setAbbreviation(const std::string &abbreviation)
Sets the abbreviation of the argument.
const std::string & name() const
Returns the name of the argument.
bool isMainArgument() const
Returns an indication whether the argument is used as main argument.
bool allRequiredValuesPresent() const
Returns an indication whether all required values are present.
The Argument class is a wrapper for command line argument information.
const StringList & valueNames() const
Returns the names of the requried values.
Argument * findArg(const ArgumentPredicate &predicate) const
Returns the first argument definition which matches the predicate.
const std::string & description() const
Returns the description of the argument.
void verifySetup() const
This method is used to verify the setup of the command line parser before parsing.
The exception that is thrown by an ArgumentParser when a parsing error occurs.
Definition: failure.h:11
int requiredValueCount() const
Returns the number of values which are required to be given for this argument.
bool isParentPresent() const
Returns true if at least one of the parents is present.
std::vector< Argument * > ArgumentVector