C++ Utilities  4.8.0
Common C++ classes and routines used by my applications such as argument parser, IO and conversion utilities
argumentparser.cpp
Go to the documentation of this file.
1 #include "./argumentparser.h"
3 #include "./commandlineutils.h"
4 #include "./failure.h"
5 
6 #include "../conversion/stringbuilder.h"
7 #include "../conversion/stringconversion.h"
8 #include "../io/ansiescapecodes.h"
9 #include "../io/path.h"
10 
11 #include <algorithm>
12 #include <cstdlib>
13 #include <cstring>
14 #include <iostream>
15 #include <sstream>
16 #include <string>
17 
18 using namespace std;
19 using namespace std::placeholders;
20 using namespace ConversionUtilities;
21 using namespace IoUtilities;
22 
27 namespace ApplicationUtilities {
28 
32 enum ArgumentDenotationType : unsigned char {
33  Value = 0,
35  FullName = 2
36 };
37 
44 ArgumentReader::ArgumentReader(ArgumentParser &parser, const char *const *argv, const char *const *end, bool completionMode)
45  : parser(parser)
46  , args(parser.m_mainArgs)
47  , index(0)
48  , argv(argv)
49  , end(end)
50  , lastArg(nullptr)
51  , argDenotation(nullptr)
52  , completionMode(completionMode)
53 {
54 }
55 
59 ArgumentReader &ArgumentReader::reset(const char *const *argv, const char *const *end)
60 {
61  this->argv = argv;
62  this->end = end;
63  index = 0;
64  lastArg = nullptr;
65  argDenotation = nullptr;
66  return *this;
67 }
68 
74 {
75  read(args);
76 }
77 
83 {
84  // method is called recursively for sub args to the last argument (which is nullptr in the initial call) is the current parent argument
85  Argument *const parentArg = lastArg;
86  // determine the current path
87  const vector<Argument *> &parentPath = parentArg ? parentArg->path(parentArg->occurrences() - 1) : vector<Argument *>();
88 
89  Argument *lastArgInLevel = nullptr;
90  vector<const char *> *values = nullptr;
91 
92  // iterate through all argument denotations; loop might exit earlier when an denotation is unknown
93  while (argv != end) {
94  if (values && lastArgInLevel->requiredValueCount() != static_cast<size_t>(-1) && values->size() < lastArgInLevel->requiredValueCount()) {
95  // there are still values to read
96  values->emplace_back(argDenotation ? argDenotation : *argv);
97  ++index, ++argv, argDenotation = nullptr;
98  } else {
99  // determine how denotation must be processed
100  bool abbreviationFound = false;
101  if (argDenotation) {
102  // continue reading childs for abbreviation denotation already detected
103  abbreviationFound = false;
105  } else {
106  // determine denotation type
107  argDenotation = *argv;
108  if (!*argDenotation && (!lastArgInLevel || values->size() >= lastArgInLevel->requiredValueCount())) {
109  // skip empty arguments
110  ++index, ++argv, argDenotation = nullptr;
111  continue;
112  }
113  abbreviationFound = false;
116  }
117 
118  // try to find matching Argument instance
119  Argument *matchingArg = nullptr;
120  size_t argDenotationLength;
121  if (argDenotationType != Value) {
122  const char *const equationPos = strchr(argDenotation, '=');
123  for (argDenotationLength = equationPos ? static_cast<size_t>(equationPos - argDenotation) : strlen(argDenotation);
124  argDenotationLength; matchingArg = nullptr) {
125  // search for arguments by abbreviation or name depending on the previously determined denotation type
127  for (Argument *arg : args) {
128  if (arg->abbreviation() && arg->abbreviation() == *argDenotation) {
129  matchingArg = arg;
130  abbreviationFound = true;
131  break;
132  }
133  }
134  } else {
135  for (Argument *arg : args) {
136  if (arg->name() && !strncmp(arg->name(), argDenotation, argDenotationLength)
137  && *(arg->name() + argDenotationLength) == '\0') {
138  matchingArg = arg;
139  break;
140  }
141  }
142  }
143 
144  if (matchingArg) {
145  // an argument matched the specified denotation so add an occurrence
146  matchingArg->m_occurrences.emplace_back(index, parentPath, parentArg);
147 
148  // prepare reading parameter values
149  values = &matchingArg->m_occurrences.back().values;
150  if (equationPos) {
151  values->push_back(equationPos + 1);
152  }
153 
154  // read sub arguments
155  ++index, ++parser.m_actualArgc, lastArg = lastArgInLevel = matchingArg, lastArgDenotation = argv;
156  if (argDenotationType != Abbreviation || (++argDenotation != equationPos)) {
158  // no further abbreviations follow -> read sub args for next argv
159  ++argv, argDenotation = nullptr;
160  read(lastArg->m_subArgs);
161  argDenotation = nullptr;
162  } else {
163  // further abbreviations follow -> don't increment argv, keep processing outstanding chars of argDenotation
164  read(lastArg->m_subArgs);
165  }
166  break;
167  } // else: another abbreviated argument follows (and it is not present in the sub args)
168  } else {
169  break;
170  }
171  }
172  }
173 
174  if (!matchingArg) {
175  // unknown argument might be a sibling of the parent element
176  if (argDenotationType != Value) {
177  for (auto parentArgument = parentPath.crbegin(), pathEnd = parentPath.crend();; ++parentArgument) {
178  for (Argument *sibling : (parentArgument != pathEnd ? (*parentArgument)->subArguments() : parser.m_mainArgs)) {
179  if (sibling->occurrences() < sibling->maxOccurrences()) {
180  if ((argDenotationType == Abbreviation && (sibling->abbreviation() && sibling->abbreviation() == *argDenotation))
181  || (sibling->name() && !strncmp(sibling->name(), argDenotation, argDenotationLength))) {
182  return;
183  }
184  }
185  }
186  if (parentArgument == pathEnd) {
187  break;
188  }
189  };
190  }
191 
192  // unknown argument might just be a parameter value of the last argument
193  if (lastArgInLevel && values->size() < lastArgInLevel->requiredValueCount()) {
194  values->emplace_back(abbreviationFound ? argDenotation : *argv);
195  ++index, ++argv, argDenotation = nullptr;
196  continue;
197  }
198 
199  // first value might denote "operation"
200  for (Argument *arg : args) {
201  if (arg->denotesOperation() && arg->name() && !strcmp(arg->name(), *argv)) {
202  (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
204  ++index, ++argv;
205  break;
206  }
207  }
208 
209  // use the first default argument which is not already present if there is still no match
210  if (!matchingArg && (!completionMode || (argv + 1 != end))) {
211  const bool uncombinableMainArgPresent = parentArg ? false : parser.isUncombinableMainArgPresent();
212  for (Argument *arg : args) {
213  if (arg->isImplicit() && !arg->isPresent() && !arg->wouldConflictWithArgument()
214  && (!uncombinableMainArgPresent || !arg->isMainArgument())) {
215  (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
216  break;
217  }
218  }
219  }
220 
221  if (matchingArg) {
222  // an argument matched the specified denotation
223  if (lastArgInLevel == matchingArg) {
224  break; // break required? -> TODO: add test for this condition
225  }
226 
227  // prepare reading parameter values
228  values = &matchingArg->m_occurrences.back().values;
229 
230  // read sub arguments
231  ++parser.m_actualArgc, lastArg = lastArgInLevel = matchingArg, argDenotation = nullptr;
232  read(lastArg->m_subArgs);
233  argDenotation = nullptr;
234  continue;
235  }
236 
237  // argument denotation is unknown -> handle error
238  if (parentArg) {
239  // continue with parent level
240  return;
241  }
242  if (completionMode) {
243  // ignore unknown denotation
244  ++index, ++argv, argDenotation = nullptr;
245  } else {
246  switch (parser.m_unknownArgBehavior) {
248  cerr << "The specified argument \"" << *argv << "\" is unknown and will be ignored." << endl;
249  FALLTHROUGH;
251  // ignore unknown denotation
252  ++index, ++argv, argDenotation = nullptr;
253  break;
255  throw Failure(argsToString("The specified argument \"", *argv, "\" is unknown."));
256  }
257  }
258  } // if(!matchingArg)
259  } // no values to read
260  } // while(argv != end)
261 }
262 
264 const char *applicationName = nullptr;
266 const char *applicationAuthor = nullptr;
268 const char *applicationVersion = nullptr;
270 const char *applicationUrl = nullptr;
272 std::initializer_list<const char *> dependencyVersions;
273 
278 void (*exitFunction)(int) = &exit;
279 
281 
282 inline bool notEmpty(const char *str)
283 {
284  return str && *str;
285 }
286 
288 
305 Argument::Argument(const char *name, char abbreviation, const char *description, const char *example)
306  : m_name(name)
307  , m_abbreviation(abbreviation)
308  , m_environmentVar(nullptr)
309  , m_description(description)
310  , m_example(example)
311  , m_minOccurrences(0)
312  , m_maxOccurrences(1)
313  , m_combinable(false)
314  , m_denotesOperation(false)
315  , m_requiredValueCount(0)
316  , m_implicit(false)
317  , m_isMainArg(false)
320  , m_preDefinedCompletionValues(nullptr)
321 {
322 }
323 
328 {
329 }
330 
338 const char *Argument::firstValue() const
339 {
340  if (!m_occurrences.empty() && !m_occurrences.front().values.empty()) {
341  return m_occurrences.front().values.front();
342  } else if (m_environmentVar) {
343  return getenv(m_environmentVar);
344  } else {
345  return nullptr;
346  }
347 }
348 
352 void Argument::printInfo(ostream &os, unsigned char indentation) const
353 {
354  os << Indentation(indentation);
356  if (notEmpty(name())) {
357  os << '-' << '-' << name();
358  }
359  if (notEmpty(name()) && abbreviation()) {
360  os << ',' << ' ';
361  }
362  if (abbreviation()) {
363  os << '-' << abbreviation();
364  }
366  if (requiredValueCount()) {
367  unsigned int valueNamesPrint = 0;
368  for (auto i = valueNames().cbegin(), end = valueNames().cend(); i != end && valueNamesPrint < requiredValueCount(); ++i) {
369  os << ' ' << '[' << *i << ']';
370  ++valueNamesPrint;
371  }
372  if (requiredValueCount() == static_cast<size_t>(-1)) {
373  os << " ...";
374  } else {
375  for (; valueNamesPrint < requiredValueCount(); ++valueNamesPrint) {
376  os << " [value " << (valueNamesPrint + 1) << ']';
377  }
378  }
379  }
380  indentation += 2;
381  if (notEmpty(description())) {
382  os << '\n' << Indentation(indentation) << description();
383  }
384  if (isRequired()) {
385  os << '\n' << Indentation(indentation) << "particularities: mandatory";
386  if (!isMainArgument()) {
387  os << " if parent argument is present";
388  }
389  }
390  if (environmentVariable()) {
391  os << '\n' << Indentation(indentation) << "default environment variable: " << environmentVariable();
392  }
393  if (notEmpty(example())) {
394  os << '\n' << Indentation(indentation) << "\nusage: " << example();
395  }
396  os << '\n';
397  for (const auto *arg : subArguments()) {
398  arg->printInfo(os, indentation);
399  }
400 }
401 
408 {
409  for (Argument *arg : args) {
410  if (arg != except && arg->isPresent() && !arg->isCombinable()) {
411  return arg;
412  }
413  }
414  return nullptr;
415 }
416 
431 void Argument::setSubArguments(const ArgumentInitializerList &secondaryArguments)
432 {
433  // remove this argument from the parents list of the previous secondary arguments
434  for (Argument *arg : m_subArgs) {
435  arg->m_parents.erase(remove(arg->m_parents.begin(), arg->m_parents.end(), this), arg->m_parents.end());
436  }
437  // assign secondary arguments
438  m_subArgs.assign(secondaryArguments);
439  // add this argument to the parents list of the assigned secondary arguments
440  // and set the parser
441  for (Argument *arg : m_subArgs) {
442  if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
443  arg->m_parents.push_back(this);
444  }
445  }
446 }
447 
456 {
457  if (find(m_subArgs.cbegin(), m_subArgs.cend(), arg) == m_subArgs.cend()) {
458  m_subArgs.push_back(arg);
459  if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
460  arg->m_parents.push_back(this);
461  }
462  }
463 }
464 
470 {
471  if (isMainArgument()) {
472  return true;
473  }
474  for (const Argument *parent : m_parents) {
475  if (parent->isPresent()) {
476  return true;
477  }
478  }
479  return false;
480 }
481 
491 {
492  return isPresent() ? wouldConflictWithArgument() : nullptr;
493 }
494 
504 {
505  if (!isCombinable()) {
506  for (Argument *parent : m_parents) {
507  for (Argument *sibling : parent->subArguments()) {
508  if (sibling != this && sibling->isPresent() && !sibling->isCombinable()) {
509  return sibling;
510  }
511  }
512  }
513  }
514  return nullptr;
515 }
516 
522 {
523  for (Argument *arg : m_subArgs) {
524  arg->resetRecursively();
525  }
526  reset();
527 }
528 
546  : m_actualArgc(0)
547  , m_executable(nullptr)
548  , m_unknownArgBehavior(UnknownArgumentBehavior::Fail)
549  , m_defaultArg(nullptr)
550 {
551 }
552 
563 {
564  if (mainArguments.size()) {
565  for (Argument *arg : mainArguments) {
566  arg->m_isMainArg = true;
567  }
568  m_mainArgs.assign(mainArguments);
569  if (!m_defaultArg) {
570  if (!(*mainArguments.begin())->requiredValueCount()) {
571  bool subArgsRequired = false;
572  for (const Argument *subArg : (*mainArguments.begin())->subArguments()) {
573  if (subArg->isRequired()) {
574  subArgsRequired = true;
575  break;
576  }
577  }
578  if (!subArgsRequired) {
579  m_defaultArg = *mainArguments.begin();
580  }
581  }
582  }
583  } else {
584  m_mainArgs.clear();
585  }
586 }
587 
595 {
596  argument->m_isMainArg = true;
597  m_mainArgs.push_back(argument);
598 }
599 
603 void ArgumentParser::printHelp(ostream &os) const
604 {
606  if (applicationName && *applicationName) {
607  os << applicationName;
608  if (applicationVersion && *applicationVersion) {
609  os << ',' << ' ';
610  }
611  }
612  if (applicationVersion && *applicationVersion) {
613  os << "version " << applicationVersion;
614  }
615  if (dependencyVersions.size()) {
616  if ((applicationName && *applicationName) || (applicationVersion && *applicationVersion)) {
617  os << '\n';
619  }
620  auto i = dependencyVersions.begin(), end = dependencyVersions.end();
621  os << "Linked against: " << *i;
622  for (++i; i != end; ++i) {
623  os << ',' << ' ' << *i;
624  }
625  }
626  if ((applicationName && *applicationName) || (applicationVersion && *applicationVersion) || dependencyVersions.size()) {
627  os << '\n' << '\n';
628  }
630  if (!m_mainArgs.empty()) {
631  os << "Available arguments:";
632  for (const Argument *arg : m_mainArgs) {
633  os << '\n';
634  arg->printInfo(os);
635  }
636  }
637  if (applicationUrl && *applicationUrl) {
638  os << "\nProject website: " << applicationUrl << endl;
639  }
640 }
641 
651 void ArgumentParser::parseArgs(int argc, const char *const *argv)
652 {
653  readArgs(argc, argv);
654  if (argc) {
655  checkConstraints(m_mainArgs);
656  invokeCallbacks(m_mainArgs);
657  }
658 }
659 
669 void ArgumentParser::readArgs(int argc, const char *const *argv)
670 {
671  IF_DEBUG_BUILD(verifyArgs(m_mainArgs, std::vector<char>(), std::vector<const char *>());)
672  m_actualArgc = 0;
673  if (argc) {
674  // the first argument is the executable name
675  m_executable = *argv;
676 
677  // check for further arguments
678  if (--argc) {
679  // if the first argument (after executable name) is "--bash-completion-for" bash completion for the following arguments is requested
680  bool completionMode = !strcmp(*++argv, "--bash-completion-for");
681  unsigned int currentWordIndex;
682  if (completionMode) {
683  // the first argument after "--bash-completion-for" is the index of the current word
684  try {
685  currentWordIndex = (--argc ? stringToNumber<unsigned int, string>(*(++argv)) : 0);
686  } catch (const ConversionException &) {
687  currentWordIndex = static_cast<unsigned int>(argc);
688  }
689  if (argc) {
690  ++argv, --argc;
691  }
692  }
693 
694  // read specified arguments
695  ArgumentReader reader(*this, argv,
696  argv + (completionMode ? min(static_cast<unsigned int>(argc), currentWordIndex + 1) : static_cast<unsigned int>(argc)),
697  completionMode);
698  try {
699  reader.read();
700  } catch (const Failure &) {
701  if (!completionMode) {
702  throw;
703  }
704  }
705 
706  if (completionMode) {
707  printBashCompletion(argc, argv, currentWordIndex, reader);
708  exitFunction(0); // prevent the applicaton to continue with the regular execution
709  }
710  } else {
711  // no arguments specified -> flag default argument as present if one is assigned
712  if (m_defaultArg) {
713  m_defaultArg->m_occurrences.emplace_back(0);
714  }
715  }
716  } else {
717  m_executable = nullptr;
718  }
719 }
720 
726 {
727  for (Argument *arg : m_mainArgs) {
728  arg->resetRecursively();
729  }
730  m_actualArgc = 0;
731 }
732 
737 {
738  for (const Argument *arg : m_mainArgs) {
739  if (!arg->isCombinable() && arg->isPresent()) {
740  return true;
741  }
742  }
743  return false;
744 }
745 
746 #ifdef DEBUG_BUILD
747 
763 void ApplicationUtilities::ArgumentParser::verifyArgs(const ArgumentVector &args, vector<char>, vector<const char *>)
764 {
765  vector<const Argument *> verifiedArgs;
766  verifiedArgs.reserve(args.size());
767  vector<char> abbreviations;
768  abbreviations.reserve(abbreviations.size() + args.size());
769  vector<const char *> names;
770  names.reserve(names.size() + args.size());
771  bool hasImplicit = false;
772  for (const Argument *arg : args) {
773  assert(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) == verifiedArgs.cend());
774  verifiedArgs.push_back(arg);
775  assert(!arg->isImplicit() || !hasImplicit);
776  hasImplicit |= arg->isImplicit();
777  assert(!arg->abbreviation() || find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) == abbreviations.cend());
778  abbreviations.push_back(arg->abbreviation());
779  assert(!arg->name() || find_if(names.cbegin(), names.cend(), [arg](const char *name) { return !strcmp(arg->name(), name); }) == names.cend());
780  assert(arg->requiredValueCount() == 0 || arg->subArguments().size() == 0);
781  names.emplace_back(arg->name());
782  }
783  for (const Argument *arg : args) {
784  verifyArgs(arg->subArguments(), vector<char>(), vector<const char *>());
785  }
786 }
787 #endif
788 
796 bool compareArgs(const Argument *arg1, const Argument *arg2)
797 {
798  if (arg1->denotesOperation() && !arg2->denotesOperation()) {
799  return true;
800  } else if (!arg1->denotesOperation() && arg2->denotesOperation()) {
801  return false;
802  } else {
803  return strcmp(arg1->name(), arg2->name()) < 0;
804  }
805 }
806 
811 void insertSiblings(const ArgumentVector &siblings, list<const Argument *> &target)
812 {
813  bool onlyCombinable = false;
814  for (const Argument *sibling : siblings) {
815  if (sibling->isPresent() && !sibling->isCombinable()) {
816  onlyCombinable = true;
817  break;
818  }
819  }
820  for (const Argument *sibling : siblings) {
821  if ((!onlyCombinable || sibling->isCombinable()) && sibling->occurrences() < sibling->maxOccurrences()) {
822  target.push_back(sibling);
823  }
824  }
825 }
826 
832 void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader)
833 {
834  // variables to store relevant completions (arguments, pre-defined values, files/dirs)
835  list<const Argument *> relevantArgs, relevantPreDefinedValues;
836  bool completeFiles = false, completeDirs = false, noWhitespace = false;
837 
838  // get the last argument the argument parser was able to detect successfully
839  const Argument *const lastDetectedArg = reader.lastArg;
840  size_t lastDetectedArgIndex;
841  vector<Argument *> lastDetectedArgPath;
842  if (lastDetectedArg) {
843  lastDetectedArgIndex = reader.lastArgDenotation - argv;
844  lastDetectedArgPath = lastDetectedArg->path(lastDetectedArg->occurrences() - 1);
845  }
846 
847  // determine last arg, omitting trailing empty args
848  const char *const *lastSpecifiedArg;
849  unsigned int lastSpecifiedArgIndex;
850  if (argc) {
851  lastSpecifiedArgIndex = static_cast<unsigned int>(argc) - 1;
852  lastSpecifiedArg = argv + lastSpecifiedArgIndex;
853  for (; lastSpecifiedArg >= argv && **lastSpecifiedArg == '\0'; --lastSpecifiedArg, --lastSpecifiedArgIndex)
854  ;
855  }
856 
857  // determine arguments relevant for completion
858  bool nextArgumentOrValue;
859  if (lastDetectedArg && lastDetectedArg->isPresent()) {
860  if ((nextArgumentOrValue = (currentWordIndex > lastDetectedArgIndex))) {
861  // parameter values of the last arg are possible completions
862  auto currentValueCount = lastDetectedArg->values(lastDetectedArg->occurrences() - 1).size();
863  if (currentValueCount) {
864  currentValueCount -= (currentWordIndex - lastDetectedArgIndex);
865  }
866  if (lastDetectedArg->requiredValueCount() == static_cast<size_t>(-1) || (currentValueCount < lastDetectedArg->requiredValueCount())) {
868  relevantPreDefinedValues.push_back(lastDetectedArg);
869  }
871  || !lastDetectedArg->preDefinedCompletionValues()) {
872  completeFiles = completeFiles || lastDetectedArg->valueCompletionBehaviour() & ValueCompletionBehavior::Files;
873  completeDirs = completeDirs || lastDetectedArg->valueCompletionBehaviour() & ValueCompletionBehavior::Directories;
874  }
875  }
876 
877  if (lastDetectedArg->requiredValueCount() == static_cast<size_t>(-1)
878  || lastDetectedArg->values(lastDetectedArg->occurrences() - 1).size() >= lastDetectedArg->requiredValueCount()) {
879  // sub arguments of the last arg are possible completions
880  for (const Argument *subArg : lastDetectedArg->subArguments()) {
881  if (subArg->occurrences() < subArg->maxOccurrences()) {
882  relevantArgs.push_back(subArg);
883  }
884  }
885 
886  // siblings of parents are possible completions as well
887  for (auto parentArgument = lastDetectedArgPath.crbegin(), end = lastDetectedArgPath.crend();; ++parentArgument) {
888  insertSiblings(parentArgument != end ? (*parentArgument)->subArguments() : m_mainArgs, relevantArgs);
889  if (parentArgument == end) {
890  break;
891  }
892  }
893  }
894  } else {
895  // since the argument could be detected (hopefully unambiguously?) just return it for "final completion"
896  relevantArgs.push_back(lastDetectedArg);
897  }
898 
899  } else {
900  // no arguments detected -> just use main arguments for completion
901  nextArgumentOrValue = true;
902  insertSiblings(m_mainArgs, relevantArgs);
903  }
904 
905  // read the "opening" (started but not finished argument denotation)
906  const char *opening = nullptr;
907  string compoundOpening;
908  size_t openingLen, compoundOpeningStartLen = 0;
909  unsigned char openingDenotationType = Value;
910  if (argc && nextArgumentOrValue) {
911  if (currentWordIndex < static_cast<unsigned int>(argc)) {
912  opening = argv[currentWordIndex];
913  // For some reason completions for eg. "set --values disk=1 tag=a" are splitted so the
914  // equation sign is an own argument ("set --values disk = 1 tag = a").
915  // This is not how values are treated by the argument parser. Hence the opening
916  // must be joined again. In this case only the part after the equation sign needs to be
917  // provided for completion so compoundOpeningStartLen is set to number of characters to skip.
918  size_t minCurrentWordIndex = (lastDetectedArg ? lastDetectedArgIndex : 0);
919  if (currentWordIndex > minCurrentWordIndex && !strcmp(opening, "=")) {
920  compoundOpening.reserve(compoundOpeningStartLen = strlen(argv[--currentWordIndex]) + 1);
921  compoundOpening = argv[currentWordIndex];
922  compoundOpening += '=';
923  } else if (currentWordIndex > (minCurrentWordIndex + 1) && !strcmp(argv[currentWordIndex - 1], "=")) {
924  compoundOpening.reserve((compoundOpeningStartLen = strlen(argv[currentWordIndex -= 2]) + 1) + strlen(opening));
925  compoundOpening = argv[currentWordIndex];
926  compoundOpening += '=';
927  compoundOpening += opening;
928  }
929  if (!compoundOpening.empty()) {
930  opening = compoundOpening.data();
931  }
932  } else {
933  opening = *lastSpecifiedArg;
934  }
935  *opening == '-' && (++opening, ++openingDenotationType) && *opening == '-' && (++opening, ++openingDenotationType);
936  openingLen = strlen(opening);
937  }
938 
939  relevantArgs.sort(compareArgs);
940 
941  // print "COMPREPLY" bash array
942  cout << "COMPREPLY=(";
943  // -> completions for parameter values
944  for (const Argument *arg : relevantPreDefinedValues) {
945  if (arg->preDefinedCompletionValues()) {
946  bool appendEquationSign = arg->valueCompletionBehaviour() & ValueCompletionBehavior::AppendEquationSign;
947  if (argc && currentWordIndex <= lastSpecifiedArgIndex && opening) {
948  if (openingDenotationType == Value) {
949  bool wordStart = true, ok = false, equationSignAlreadyPresent = false;
950  size_t wordIndex = 0;
951  for (const char *i = arg->preDefinedCompletionValues(), *end = opening + openingLen; *i;) {
952  if (wordStart) {
953  const char *i1 = i, *i2 = opening;
954  for (; *i1 && i2 != end && *i1 == *i2; ++i1, ++i2)
955  ;
956  if ((ok = (i2 == end))) {
957  cout << '\'';
958  }
959  wordStart = false;
960  wordIndex = 0;
961  } else if ((wordStart = (*i == ' ') || (*i == '\n'))) {
962  equationSignAlreadyPresent = false;
963  if (ok) {
964  cout << '\'' << ' ';
965  }
966  ++i;
967  continue;
968  } else if (*i == '=') {
969  equationSignAlreadyPresent = true;
970  }
971  if (ok) {
972  if (!compoundOpeningStartLen || wordIndex >= compoundOpeningStartLen) {
973  if (*i == '\'') {
974  cout << "'\"'\"'";
975  } else {
976  cout << *i;
977  }
978  }
979  ++i, ++wordIndex;
980  switch (*i) {
981  case ' ':
982  case '\n':
983  case '\0':
984  if (appendEquationSign && !equationSignAlreadyPresent) {
985  cout << '=';
986  noWhitespace = true;
987  equationSignAlreadyPresent = false;
988  }
989  if (*i == '\0') {
990  cout << '\'';
991  }
992  }
993  } else {
994  ++i;
995  }
996  }
997  cout << ' ';
998  }
999  } else if (const char *i = arg->preDefinedCompletionValues()) {
1000  bool equationSignAlreadyPresent = false;
1001  cout << '\'';
1002  while (*i) {
1003  if (*i == '\'') {
1004  cout << "'\"'\"'";
1005  } else {
1006  cout << *i;
1007  }
1008  switch (*(++i)) {
1009  case '=':
1010  equationSignAlreadyPresent = true;
1011  break;
1012  case ' ':
1013  case '\n':
1014  case '\0':
1015  if (appendEquationSign && !equationSignAlreadyPresent) {
1016  cout << '=';
1017  equationSignAlreadyPresent = false;
1018  }
1019  if (*i != '\0') {
1020  cout << '\'';
1021  if (*(++i)) {
1022  cout << ' ' << '\'';
1023  }
1024  }
1025  }
1026  }
1027  cout << '\'' << ' ';
1028  }
1029  }
1030  }
1031  // -> completions for further arguments
1032  for (const Argument *arg : relevantArgs) {
1033  if (argc && currentWordIndex <= lastSpecifiedArgIndex && opening) {
1034  switch (openingDenotationType) {
1035  case Value:
1036  if (!arg->denotesOperation() || strncmp(arg->name(), opening, openingLen)) {
1037  continue;
1038  }
1039  break;
1040  case Abbreviation:
1041  break;
1042  case FullName:
1043  if (strncmp(arg->name(), opening, openingLen)) {
1044  continue;
1045  }
1046  }
1047  }
1048 
1049  if (opening && openingDenotationType == Abbreviation && !nextArgumentOrValue) {
1050  cout << '\'' << '-' << opening << arg->abbreviation() << '\'' << ' ';
1051  } else if (lastDetectedArg && reader.argDenotationType == Abbreviation && !nextArgumentOrValue) {
1052  if (reader.argv == reader.end) {
1053  cout << '\'' << *(reader.argv - 1) << '\'' << ' ';
1054  }
1055  } else if (arg->denotesOperation()) {
1056  cout << '\'' << arg->name() << '\'' << ' ';
1057  } else {
1058  cout << '\'' << '-' << '-' << arg->name() << '\'' << ' ';
1059  }
1060  }
1061  // -> completions for files and dirs
1062  // -> if there's already an "opening", determine the dir part and the file part
1063  string actualDir, actualFile;
1064  bool haveFileOrDirCompletions = false;
1065  if (argc && currentWordIndex == lastSpecifiedArgIndex && opening) {
1066  // the "opening" might contain escaped characters which need to be unescaped first (let's hope this covers all possible escapings)
1067  string unescapedOpening(opening);
1068  findAndReplace<string>(unescapedOpening, "\\ ", " ");
1069  findAndReplace<string>(unescapedOpening, "\\,", ",");
1070  findAndReplace<string>(unescapedOpening, "\\[", "[");
1071  findAndReplace<string>(unescapedOpening, "\\]", "]");
1072  findAndReplace<string>(unescapedOpening, "\\!", "!");
1073  findAndReplace<string>(unescapedOpening, "\\#", "#");
1074  findAndReplace<string>(unescapedOpening, "\\$", "$");
1075  findAndReplace<string>(unescapedOpening, "\\'", "'");
1076  findAndReplace<string>(unescapedOpening, "\\\"", "\"");
1077  findAndReplace<string>(unescapedOpening, "\\\\", "\\");
1078  // determine the "directory" part
1079  string dir = directory(unescapedOpening);
1080  if (dir.empty()) {
1081  actualDir = ".";
1082  } else {
1083  if (dir[0] == '\"' || dir[0] == '\'') {
1084  dir.erase(0, 1);
1085  }
1086  if (dir.size() > 1 && (dir[dir.size() - 2] == '\"' || dir[dir.size() - 2] == '\'')) {
1087  dir.erase(dir.size() - 2, 1);
1088  }
1089  actualDir = move(dir);
1090  }
1091  // determine the "file" part
1092  string file = fileName(unescapedOpening);
1093  if (file[0] == '\"' || file[0] == '\'') {
1094  file.erase(0, 1);
1095  }
1096  if (file.size() > 1 && (file[dir.size() - 2] == '\"' || dir[file.size() - 2] == '\'')) {
1097  file.erase(file.size() - 2, 1);
1098  }
1099  actualFile = move(file);
1100  }
1101 
1102  // -> completion for files and dirs
1104  if (completeFiles) {
1105  entryTypes |= DirectoryEntryType::File;
1106  }
1107  if (completeDirs) {
1108  entryTypes |= DirectoryEntryType::Directory;
1109  }
1110  if (entryTypes != DirectoryEntryType::None) {
1111  const string replace("'"), with("'\"'\"'");
1112  if (argc && currentWordIndex <= lastSpecifiedArgIndex && opening) {
1113  list<string> entries = directoryEntries(actualDir.c_str(), entryTypes);
1114  findAndReplace(actualDir, replace, with);
1115  for (string &dirEntry : entries) {
1116  if (startsWith(dirEntry, actualFile)) {
1117  cout << '\'';
1118  if (actualDir != ".") {
1119  cout << actualDir;
1120  }
1121  findAndReplace(dirEntry, replace, with);
1122  cout << dirEntry << '\'' << ' ';
1123  haveFileOrDirCompletions = true;
1124  }
1125  }
1126  } else {
1127  for (string &dirEntry : directoryEntries(".", entryTypes)) {
1128  findAndReplace(dirEntry, replace, with);
1129  cout << '\'' << dirEntry << '\'' << ' ';
1130  haveFileOrDirCompletions = true;
1131  }
1132  }
1133  }
1134  cout << ')';
1135 
1136  // ensure file or dir completions are formatted appropriately
1137  if (haveFileOrDirCompletions) {
1138  cout << "; compopt -o filenames";
1139  }
1140 
1141  // ensure trailing whitespace is ommitted
1142  if (noWhitespace) {
1143  cout << "; compopt -o nospace";
1144  }
1145 
1146  cout << endl;
1147 }
1148 
1154 {
1155  for (const Argument *arg : args) {
1156  const auto occurrences = arg->occurrences();
1157  if (arg->isParentPresent() && occurrences > arg->maxOccurrences()) {
1158  throw Failure(argsToString("The argument \"", arg->name(), "\" mustn't be specified more than ", arg->maxOccurrences(),
1159  (arg->maxOccurrences() == 1 ? " time." : " times.")));
1160  }
1161  if (arg->isParentPresent() && occurrences < arg->minOccurrences()) {
1162  throw Failure(argsToString("The argument \"", arg->name(), "\" must be specified at least ", arg->minOccurrences(),
1163  (arg->minOccurrences() == 1 ? " time." : " times.")));
1164  }
1165  Argument *conflictingArgument = nullptr;
1166  if (arg->isMainArgument()) {
1167  if (!arg->isCombinable() && arg->isPresent()) {
1168  conflictingArgument = firstPresentUncombinableArg(m_mainArgs, arg);
1169  }
1170  } else {
1171  conflictingArgument = arg->conflictsWithArgument();
1172  }
1173  if (conflictingArgument) {
1174  throw Failure(argsToString("The argument \"", conflictingArgument->name(), "\" can not be combined with \"", arg->name(), "\"."));
1175  }
1176  for (size_t i = 0; i != occurrences; ++i) {
1177  if (!arg->allRequiredValuesPresent(i)) {
1178  stringstream ss(stringstream::in | stringstream::out);
1179  ss << "Not all parameter for argument \"" << arg->name() << "\" ";
1180  if (i) {
1181  ss << " (" << (i + 1) << " occurrence) ";
1182  }
1183  ss << "provided. You have to provide the following parameter:";
1184  size_t valueNamesPrint = 0;
1185  for (const auto &name : arg->m_valueNames) {
1186  ss << ' ' << name, ++valueNamesPrint;
1187  }
1188  if (arg->m_requiredValueCount != static_cast<size_t>(-1)) {
1189  while (valueNamesPrint < arg->m_requiredValueCount) {
1190  ss << "\nvalue " << (++valueNamesPrint);
1191  }
1192  }
1193  throw Failure(ss.str());
1194  }
1195  }
1196 
1197  // check contraints of sub arguments recursively
1198  checkConstraints(arg->m_subArgs);
1199  }
1200 }
1201 
1210 {
1211  for (const Argument *arg : args) {
1212  // invoke the callback for each occurrence of the argument
1213  if (arg->m_callbackFunction) {
1214  for (const auto &occurrence : arg->m_occurrences) {
1215  arg->m_callbackFunction(occurrence);
1216  }
1217  }
1218  // invoke the callbacks for sub arguments recursively
1219  invokeCallbacks(arg->m_subArgs);
1220  }
1221 }
1222 
1233  : Argument("help", 'h', "shows this information")
1234 {
1235  setCallback([&parser](const ArgumentOccurrence &) {
1237  parser.printHelp(cout);
1238  });
1239 }
1240 
1251 }
bool isMainArgument() const
Returns an indication whether the argument is used as main argument.
CPP_UTILITIES_EXPORT const char * applicationUrl
Specifies the URL to the application website (used by ArgumentParser::printHelp()).
bool startsWith(const StringType &str, const StringType &phrase)
Returns whether str starts with phrase.
ValueCompletionBehavior
The ValueCompletionBehavior enum specifies the items to be considered when generating completion for ...
const char * preDefinedCompletionValues() const
Returns the assigned values used when generating completion for the values.
bool denotesOperation() const
Returns whether the argument denotes the operation.
void resetArgs()
Resets all Argument instances assigned as mainArguments() and sub arguments.
ArgumentVector & args
The Argument instances to store the results. Sub arguments of args are considered as well...
std::initializer_list< Argument * > ArgumentInitializerList
#define IF_DEBUG_BUILD(x)
Wraps debug-only lines conveniently.
Definition: global.h:130
std::size_t requiredValueCount() const
Returns the number of values which are required to be given for this argument.
Argument * conflictsWithArgument() const
Checks if this arguments conflicts with other arguments.
const char * description() const
Returns the description of the argument.
const char * argDenotation
The currently processed abbreviation denotation (should be substring of one of the args in argv)...
bool isParentPresent() const
Returns whether at least one parent argument is present.
ArgumentParser()
Constructs a new ArgumentParser.
ValueCompletionBehavior valueCompletionBehaviour() const
Returns the items to be considered when generating completion for the values.
void readArgs(int argc, const char *const *argv)
Parses the specified command line arguments.
void setStyle(std::ostream &stream, TextAttribute displayAttribute=TextAttribute::Reset)
CPP_UTILITIES_EXPORT std::list< std::string > directoryEntries(const char *path, DirectoryEntryType types=DirectoryEntryType::All)
Returns the names of the directory entries in the specified path with the specified types...
Definition: path.cpp:181
The ConversionException class is thrown by the various conversion functions of this library when a co...
Contains currently only ArgumentParser and related classes.
const char *const * argv
Points to the first argument denotation and will be incremented when a denotation has been processed...
constexpr StringType argsToString(Args &&... args)
void setMainArguments(const ArgumentInitializerList &mainArguments)
Sets the main arguments for the parser.
Argument(const char *name, char abbreviation='\0', const char *description=nullptr, const char *example=nullptr)
Constructs an Argument with the given name, abbreviation and description.
STL namespace.
bool isRequired() const
Returns an indication whether the argument is mandatory.
Argument CPP_UTILITIES_EXPORT * firstPresentUncombinableArg(const ArgumentVector &args, const Argument *except)
This function return the first present and uncombinable argument of the given list of arguments...
void parseArgs(int argc, const char *const *argv)
Parses the specified command line arguments.
const char * firstValue() const
Returns the first parameter value of the first occurrence of the argument.
CPP_UTILITIES_EXPORT const char * applicationVersion
Specifies the version of the application (used by ArgumentParser::printHelp()).
void invokeCallbacks()
Invokes all assigned callbacks.
~Argument()
Destroys the Argument.
The Indentation class allows printing indentation conveniently, eg.
#define CMD_UTILS_START_CONSOLE
void addMainArgument(Argument *argument)
Adds the specified argument to the main argument.
#define FALLTHROUGH
Prevents clang from warning about missing break in switch-case.
Definition: global.h:142
void checkConstraints()
Checks whether contraints are violated.
const char * name() const
Returns the name of the argument.
const std::vector< const char * > & valueNames() const
Returns the names of the requried values.
HelpArgument(ArgumentParser &parser)
Constructs a new help argument for the specified parser.
unsigned char argDenotationType
The type of the currently processed abbreviation denotation. Unspecified if argDenotation is not set...
const ArgumentVector & subArguments() const
Returns the secondary arguments for this argument.
Contains utility classes helping to read and write streams.
Definition: binaryreader.h:10
ArgumentParser & parser
The associated ArgumentParser instance.
void printHelp(std::ostream &os) const
Prints help text for all assigned arguments.
void findAndReplace(StringType &str, const StringType &find, const StringType &replace)
Replaces all occurences of find with relpace in the specified str.
CPP_UTILITIES_EXPORT std::initializer_list< const char * > dependencyVersions
Specifies the dependency versions the application was linked against (used by ArgumentParser::printHe...
const std::vector< const char * > & values(std::size_t occurrence=0) const
Returns the parameter values for the specified occurrence of argument.
Contains several functions providing conversions between different data types.
CPP_UTILITIES_EXPORT void(* exitFunction)(int)
Specifies a function quit the application.
Argument * wouldConflictWithArgument() const
Checks if this argument would conflict with other arguments if it was present.
std::size_t occurrences() const
Returns how often the argument could be detected when parsing.
void read()
Reads the commands line arguments specified when constructing the object.
The Argument class is a wrapper for command line argument information.
void setCallback(CallbackFunction callback)
Sets a callback function which will be called by the parser if the argument could be found and no par...
const std::vector< Argument * > & path(std::size_t occurrence=0) const
Returns the path of the specified occurrence.
bool isCombinable() const
Returns an indication whether the argument is combinable.
ApplicationUtilities::ArgumentReader & reset(const char *const *argv, const char *const *end)
Resets the ArgumentReader to continue reading new argv.
ArgumentDenotationType
The ArgumentDenotationType enum specifies the type of a given argument denotation.
UnknownArgumentBehavior
The UnknownArgumentBehavior enum specifies the behavior of the argument parser when an unknown argume...
void printInfo(std::ostream &os, unsigned char indentation=0) const
Writes the name, the abbreviation and other information about the Argument to the give ostream...
const char *const * lastArgDenotation
Points to the element in argv where lastArg was encountered. Unspecified if lastArg is not set...
CPP_UTILITIES_EXPORT std::string directory(const std::string &path)
Returns the directory of the specified path string (including trailing slash).
Definition: path.cpp:52
void addSubArgument(Argument *arg)
Adds arg as a secondary argument for this argument.
The ArgumentOccurrence struct holds argument values for an occurrence of an argument.
CPP_UTILITIES_EXPORT const char * applicationName
Specifies the name of the application (used by ArgumentParser::printHelp()).
bool isPresent() const
Returns an indication whether the argument could be detected when parsing.
bool completionMode
Whether completion mode is enabled. In this case reading args will be continued even if an denotation...
The Failure class is thrown by an ArgumentParser when a parsing error occurs.
Definition: failure.h:11
const ArgumentVector & mainArguments() const
Returns the main arguments.
bool isUncombinableMainArgPresent() const
Checks whether at least one uncombinable main argument is present.
char abbreviation() const
Returns the abbreviation of the argument.
const char * example() const
Returns the usage example of the argument.
void reset()
Resets occurrences (indices, values and paths).
const char *const * end
Points to the end of the argv array.
CPP_UTILITIES_EXPORT std::string fileName(const std::string &path)
Returns the file name and extension of the specified path string.
Definition: path.cpp:32
size_t index
An index which is incremented when an argument is encountered (the current index is stored in the occ...
CPP_UTILITIES_EXPORT const char * applicationAuthor
Specifies the author of the application (used by ArgumentParser::printHelp()).
DirectoryEntryType
The DirectoryEntryType enum specifies the type of a directory entry (file, directory or symlink)...
Definition: path.h:26
const char * environmentVariable() const
Returns the environment variable queried when firstValue() is called.
Argument * lastArg
The last Argument instance which could be detected. Set to nullptr in the initial call...
void setSubArguments(const ArgumentInitializerList &subArguments)
Sets the secondary arguments for this arguments.
bool compareArgs(const Argument *arg1, const Argument *arg2)
Returns whether arg1 should be listed before arg2 when printing completion.
The ArgumentParser class provides a means for handling command line arguments.
void resetRecursively()
Resets this argument and all sub arguments recursively.
void insertSiblings(const ArgumentVector &siblings, list< const Argument *> &target)
Inserts the specified siblings in the target list.
std::vector< Argument * > ArgumentVector