C++ Utilities  5.10.2
Useful C++ classes and routines 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 
5 #include "../conversion/stringbuilder.h"
6 #include "../conversion/stringconversion.h"
7 #include "../io/ansiescapecodes.h"
8 #include "../io/path.h"
9 #include "../misc/levenshtein.h"
10 #include "../misc/parseerror.h"
11 
12 #include <algorithm>
13 #include <cstdlib>
14 #include <cstring>
15 #include <iostream>
16 #include <set>
17 #include <sstream>
18 #include <string>
19 
20 #ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
21 #include <filesystem>
22 #endif
23 
24 using namespace std;
25 using namespace std::placeholders;
26 using namespace std::literals;
27 using namespace CppUtilities::EscapeCodes;
28 
33 namespace CppUtilities {
34 
38 enum ArgumentDenotationType : unsigned char {
39  Value = 0,
41  FullName = 2
42 };
43 
49 
50  const Argument *const lastDetectedArg;
51  size_t lastDetectedArgIndex = 0;
52  vector<Argument *> lastDetectedArgPath;
53  list<const Argument *> relevantArgs;
54  list<const Argument *> relevantPreDefinedValues;
55  const char *const *lastSpecifiedArg = nullptr;
56  unsigned int lastSpecifiedArgIndex = 0;
57  bool nextArgumentOrValue = false;
58  bool completeFiles = false, completeDirs = false;
59 };
60 
65 ArgumentCompletionInfo::ArgumentCompletionInfo(const ArgumentReader &reader)
66  : lastDetectedArg(reader.lastArg)
67 {
68 }
69 
71 struct ArgumentSuggestion {
72  ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, bool hasDashPrefix);
73  ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, size_t suggestionSize, bool hasDashPrefix);
74  bool operator<(const ArgumentSuggestion &other) const;
75  bool operator==(const ArgumentSuggestion &other) const;
76  void addTo(multiset<ArgumentSuggestion> &suggestions, size_t limit) const;
77 
78  const char *const suggestion;
79  const size_t suggestionSize;
80  const size_t editingDistance;
81  const bool hasDashPrefix;
82 };
83 
84 ArgumentSuggestion::ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, size_t suggestionSize, bool isOperation)
85  : suggestion(suggestion)
86  , suggestionSize(suggestionSize)
87  , editingDistance(computeDamerauLevenshteinDistance(unknownArg, unknownArgSize, suggestion, suggestionSize))
88  , hasDashPrefix(isOperation)
89 {
90 }
91 
92 ArgumentSuggestion::ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, bool isOperation)
93  : ArgumentSuggestion(unknownArg, unknownArgSize, suggestion, strlen(suggestion), isOperation)
94 {
95 }
96 
97 bool ArgumentSuggestion::operator<(const ArgumentSuggestion &other) const
98 {
99  return editingDistance < other.editingDistance;
100 }
101 
102 void ArgumentSuggestion::addTo(multiset<ArgumentSuggestion> &suggestions, size_t limit) const
103 {
104  if (suggestions.size() >= limit && !(*this < *--suggestions.end())) {
105  return;
106  }
107  suggestions.emplace(*this);
108  while (suggestions.size() > limit) {
109  suggestions.erase(--suggestions.end());
110  }
111 }
113 
126 ArgumentReader::ArgumentReader(ArgumentParser &parser, const char *const *argv, const char *const *end, bool completionMode)
127  : parser(parser)
128  , args(parser.m_mainArgs)
129  , index(0)
130  , argv(argv)
131  , end(end)
132  , lastArg(nullptr)
133  , argDenotation(nullptr)
134  , completionMode(completionMode)
135 {
136 }
137 
141 ArgumentReader &ArgumentReader::reset(const char *const *argv, const char *const *end)
142 {
143  this->argv = argv;
144  this->end = end;
145  index = 0;
146  lastArg = nullptr;
147  argDenotation = nullptr;
148  return *this;
149 }
150 
156 {
157  return read(args);
158 }
159 
163 bool Argument::matchesDenotation(const char *denotation, size_t denotationLength) const
164 {
165  return m_name && !strncmp(m_name, denotation, denotationLength) && *(m_name + denotationLength) == '\0';
166 }
167 
176 {
177  // method is called recursively for sub args to the last argument (which is nullptr in the initial call) is the current parent argument
178  Argument *const parentArg = lastArg;
179  // determine the current path
180  const vector<Argument *> &parentPath = parentArg ? parentArg->path(parentArg->occurrences() - 1) : vector<Argument *>();
181 
182  Argument *lastArgInLevel = nullptr;
183  vector<const char *> *values = nullptr;
184 
185  // iterate through all argument denotations; loop might exit earlier when an denotation is unknown
186  while (argv != end) {
187  // check whether there are still values to read
188  if (values && lastArgInLevel->requiredValueCount() != Argument::varValueCount && values->size() < lastArgInLevel->requiredValueCount()) {
189  // read arg as value and continue with next arg
190  values->emplace_back(argDenotation ? argDenotation : *argv);
191  ++index;
192  ++argv;
193  argDenotation = nullptr;
194  continue;
195  }
196 
197  // determine how denotation must be processed
198  bool abbreviationFound = false;
199  if (argDenotation) {
200  // continue reading children for abbreviation denotation already detected
201  abbreviationFound = false;
203  } else {
204  // determine denotation type
205  argDenotation = *argv;
206  if (!*argDenotation && (!lastArgInLevel || values->size() >= lastArgInLevel->requiredValueCount())) {
207  // skip empty arguments
208  ++index;
209  ++argv;
210  argDenotation = nullptr;
211  continue;
212  }
213  abbreviationFound = false;
215  if (*argDenotation == '-') {
216  ++argDenotation;
218  if (*argDenotation == '-') {
219  ++argDenotation;
221  }
222  }
223  }
224 
225  // try to find matching Argument instance
226  Argument *matchingArg = nullptr;
227  if (argDenotationType != Value) {
228  // determine actual denotation length (everything before equation sign)
229  const char *const equationPos = strchr(argDenotation, '=');
230  const auto argDenotationLength = equationPos ? static_cast<size_t>(equationPos - argDenotation) : strlen(argDenotation);
231 
232  // loop through each "part" of the denotation
233  // names are read at once, but for abbreviations each character is considered individually
234  for (; argDenotationLength; matchingArg = nullptr) {
235  // search for arguments by abbreviation or name depending on the previously determined denotation type
237  for (Argument *const arg : args) {
238  if (arg->abbreviation() && arg->abbreviation() == *argDenotation) {
239  matchingArg = arg;
240  abbreviationFound = true;
241  break;
242  }
243  }
244  } else {
245  for (Argument *const arg : args) {
246  if (arg->matchesDenotation(argDenotation, argDenotationLength)) {
247  matchingArg = arg;
248  break;
249  }
250  }
251  }
252  if (!matchingArg) {
253  break;
254  }
255 
256  // an argument matched the specified denotation so add an occurrence
257  matchingArg->m_occurrences.emplace_back(index, parentPath, parentArg);
258 
259  // prepare reading parameter values
260  values = &matchingArg->m_occurrences.back().values;
261 
262  // read value after equation sign
263  if ((argDenotationType != Abbreviation && equationPos) || (++argDenotation == equationPos)) {
264  values->push_back(equationPos + 1);
265  argDenotation = nullptr;
266  }
267 
268  // read sub arguments, distinguish whether further abbreviations follow
269  ++index;
270  ++parser.m_actualArgc;
271  lastArg = lastArgInLevel = matchingArg;
274  // no further abbreviations follow -> read sub args for next argv
275  ++argv;
276  argDenotation = nullptr;
277  read(lastArg->m_subArgs);
278  argDenotation = nullptr;
279  break;
280  } else {
281  // further abbreviations follow -> remember current arg value
282  const char *const *const currentArgValue = argv;
283  // don't increment argv, keep processing outstanding chars of argDenotation
284  read(lastArg->m_subArgs);
285  // stop further processing if the denotation has been consumed or even the next value has already been loaded
286  if (!argDenotation || currentArgValue != argv) {
287  argDenotation = nullptr;
288  break;
289  }
290  }
291  }
292 
293  // continue with next arg if we've got a match already
294  if (matchingArg) {
295  continue;
296  }
297 
298  // unknown argument might be a sibling of the parent element
299  for (auto parentArgument = parentPath.crbegin(), pathEnd = parentPath.crend();; ++parentArgument) {
300  for (Argument *const sibling : (parentArgument != pathEnd ? (*parentArgument)->subArguments() : parser.m_mainArgs)) {
301  if (sibling->occurrences() < sibling->maxOccurrences()) {
302  // check whether the denoted abbreviation matches the sibling's abbreviatiopn
303  if (argDenotationType == Abbreviation && (sibling->abbreviation() && sibling->abbreviation() == *argDenotation)) {
304  return false;
305  }
306  // check whether the denoted name matches the sibling's name
307  if (sibling->matchesDenotation(argDenotation, argDenotationLength)) {
308  return false;
309  }
310  }
311  }
312  if (parentArgument == pathEnd) {
313  break;
314  }
315  }
316  }
317 
318  // unknown argument might just be a parameter value of the last argument
319  if (lastArgInLevel && values->size() < lastArgInLevel->requiredValueCount()) {
320  values->emplace_back(abbreviationFound ? argDenotation : *argv);
321  ++index;
322  ++argv;
323  argDenotation = nullptr;
324  continue;
325  }
326 
327  // first value might denote "operation"
328  for (Argument *const arg : args) {
329  if (arg->denotesOperation() && arg->name() && !strcmp(arg->name(), *argv)) {
330  (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
332  ++index;
333  ++argv;
334  break;
335  }
336  }
337 
338  // use the first default argument which is not already present if there is still no match
339  if (!matchingArg && (!completionMode || (argv + 1 != end))) {
340  const bool uncombinableMainArgPresent = parentArg ? false : parser.isUncombinableMainArgPresent();
341  for (Argument *const arg : args) {
342  if (arg->isImplicit() && !arg->isPresent() && !arg->wouldConflictWithArgument()
343  && (!uncombinableMainArgPresent || !arg->isMainArgument())) {
344  (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
345  break;
346  }
347  }
348  }
349 
350  if (matchingArg) {
351  // an argument matched the specified denotation
352  if (lastArgInLevel == matchingArg) {
353  break; // break required? -> TODO: add test for this condition
354  }
355 
356  // prepare reading parameter values
357  values = &matchingArg->m_occurrences.back().values;
358 
359  // read sub arguments
360  ++parser.m_actualArgc;
361  lastArg = lastArgInLevel = matchingArg;
362  argDenotation = nullptr;
363  read(lastArg->m_subArgs);
364  argDenotation = nullptr;
365  continue;
366  }
367 
368  // argument denotation is unknown -> handle error
369  if (parentArg) {
370  // continue with parent level
371  return false;
372  }
373  if (completionMode) {
374  // ignore unknown denotation
375  ++index;
376  ++argv;
377  argDenotation = nullptr;
378  } else {
379  switch (parser.m_unknownArgBehavior) {
381  cerr << Phrases::Warning << "The specified argument \"" << *argv << "\" is unknown and will be ignored." << Phrases::EndFlush;
382  [[fallthrough]];
384  // ignore unknown denotation
385  ++index;
386  ++argv;
387  argDenotation = nullptr;
388  break;
390  return false;
391  }
392  }
393  } // while(argv != end)
394  return true;
395 }
396 
403 ostream &operator<<(ostream &os, const Wrapper &wrapper)
404 {
405  // determine max. number of columns
406  static const TerminalSize termSize(determineTerminalSize());
407  const auto maxColumns = termSize.columns ? termSize.columns : numeric_limits<unsigned short>::max();
408 
409  // print wrapped string considering indentation
410  unsigned short currentCol = wrapper.m_indentation.level;
411  for (const char *currentChar = wrapper.m_str; *currentChar; ++currentChar) {
412  const bool wrappingRequired = currentCol >= maxColumns;
413  if (wrappingRequired || *currentChar == '\n') {
414  // insert newline (TODO: wrap only at end of a word)
415  os << '\n';
416  // print indentation (if enough space)
417  if (wrapper.m_indentation.level < maxColumns) {
418  os << wrapper.m_indentation;
419  currentCol = wrapper.m_indentation.level;
420  } else {
421  currentCol = 0;
422  }
423  }
424  if (*currentChar != '\n' && (!wrappingRequired || *currentChar != ' ')) {
425  os << *currentChar;
426  ++currentCol;
427  }
428  }
429  return os;
430 }
431 
433 
435 
436 inline bool notEmpty(const char *str)
437 {
438  return str && *str;
439 }
440 
442 
459 Argument::Argument(const char *name, char abbreviation, const char *description, const char *example)
460  : m_name(name)
461  , m_abbreviation(abbreviation)
462  , m_environmentVar(nullptr)
463  , m_description(description)
464  , m_example(example)
465  , m_minOccurrences(0)
466  , m_maxOccurrences(1)
467  , m_requiredValueCount(0)
468  , m_flags(Flags::None)
469  , m_deprecatedBy(nullptr)
470  , m_isMainArg(false)
473  , m_preDefinedCompletionValues(nullptr)
474 {
475 }
476 
481 {
482 }
483 
491 const char *Argument::firstValue() const
492 {
493  if (!m_occurrences.empty() && !m_occurrences.front().values.empty()) {
494  return m_occurrences.front().values.front();
495  } else if (m_environmentVar) {
496  return getenv(m_environmentVar);
497  } else {
498  return nullptr;
499  }
500 }
501 
505 const char *Argument::firstValueOr(const char *fallback) const
506 {
507  if (const auto *const v = firstValue()) {
508  return v;
509  } else {
510  return fallback;
511  }
512 }
513 
517 void Argument::printInfo(ostream &os, unsigned char indentation) const
518 {
519  if (isDeprecated()) {
520  return;
521  }
522  Indentation ident(indentation);
523  os << ident;
525  if (notEmpty(name())) {
526  if (!denotesOperation()) {
527  os << '-' << '-';
528  }
529  os << name();
530  }
531  if (notEmpty(name()) && abbreviation()) {
532  os << ',' << ' ';
533  }
534  if (abbreviation()) {
535  os << '-' << abbreviation();
536  }
538  if (requiredValueCount()) {
539  unsigned int valueNamesPrint = 0;
540  for (auto i = valueNames().cbegin(), end = valueNames().cend(); i != end && valueNamesPrint < requiredValueCount(); ++i) {
541  os << ' ' << '[' << *i << ']';
542  ++valueNamesPrint;
543  }
545  os << " ...";
546  } else {
547  for (; valueNamesPrint < requiredValueCount(); ++valueNamesPrint) {
548  os << " [value " << (valueNamesPrint + 1) << ']';
549  }
550  }
551  }
552  ident.level += 2;
553  if (notEmpty(description())) {
554  os << '\n' << ident << Wrapper(description(), ident);
555  }
556  if (isRequired()) {
557  os << '\n' << ident << "particularities: mandatory";
558  if (!isMainArgument()) {
559  os << " if parent argument is present";
560  }
561  }
562  if (environmentVariable()) {
563  os << '\n' << ident << "default environment variable: " << Wrapper(environmentVariable(), ident + 30);
564  }
565  os << '\n';
566  bool hasSubArgs = false;
567  for (const auto *const arg : subArguments()) {
568  if (arg->isDeprecated()) {
569  continue;
570  }
571  hasSubArgs = true;
572  arg->printInfo(os, ident.level);
573  }
574  if (notEmpty(example())) {
575  if (ident.level == 2 && hasSubArgs) {
576  os << '\n';
577  }
578  os << ident << "example: " << Wrapper(example(), ident + 9);
579  os << '\n';
580  }
581 }
582 
589 {
590  for (Argument *arg : args) {
591  if (arg != except && arg->isPresent() && !arg->isCombinable()) {
592  return arg;
593  }
594  }
595  return nullptr;
596 }
597 
612 void Argument::setSubArguments(const ArgumentInitializerList &secondaryArguments)
613 {
614  // remove this argument from the parents list of the previous secondary arguments
615  for (Argument *arg : m_subArgs) {
616  arg->m_parents.erase(remove(arg->m_parents.begin(), arg->m_parents.end(), this), arg->m_parents.end());
617  }
618  // assign secondary arguments
619  m_subArgs.assign(secondaryArguments);
620  // add this argument to the parents list of the assigned secondary arguments
621  // and set the parser
622  for (Argument *arg : m_subArgs) {
623  if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
624  arg->m_parents.push_back(this);
625  }
626  }
627 }
628 
637 {
638  if (find(m_subArgs.cbegin(), m_subArgs.cend(), arg) != m_subArgs.cend()) {
639  return;
640  }
641  m_subArgs.push_back(arg);
642  if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
643  arg->m_parents.push_back(this);
644  }
645 }
646 
652 {
653  if (isMainArgument()) {
654  return true;
655  }
656  for (const Argument *parent : m_parents) {
657  if (parent->isPresent()) {
658  return true;
659  }
660  }
661  return false;
662 }
663 
673 {
674  return isPresent() ? wouldConflictWithArgument() : nullptr;
675 }
676 
686 {
687  if (isCombinable()) {
688  return nullptr;
689  }
690  for (Argument *parent : m_parents) {
691  for (Argument *sibling : parent->subArguments()) {
692  if (sibling != this && sibling->isPresent() && !sibling->isCombinable()) {
693  return sibling;
694  }
695  }
696  }
697  return nullptr;
698 }
699 
705 {
706  for (Argument *arg : m_subArgs) {
707  if (arg->denotesOperation() && arg->isPresent()) {
708  return arg;
709  }
710  }
711  return nullptr;
712 }
713 
719 {
720  for (Argument *arg : m_subArgs) {
721  arg->resetRecursively();
722  }
723  reset();
724 }
725 
743  : m_actualArgc(0)
744  , m_executable(nullptr)
745  , m_unknownArgBehavior(UnknownArgumentBehavior::Fail)
746  , m_defaultArg(nullptr)
747  , m_helpArg(*this)
748 {
749 }
750 
761 {
762  if (!mainArguments.size()) {
763  m_mainArgs.clear();
764  return;
765  }
766  for (Argument *arg : mainArguments) {
767  arg->m_isMainArg = true;
768  }
769  m_mainArgs.assign(mainArguments);
770  if (m_defaultArg || (*mainArguments.begin())->requiredValueCount()) {
771  return;
772  }
773  bool subArgsRequired = false;
774  for (const Argument *subArg : (*mainArguments.begin())->subArguments()) {
775  if (subArg->isRequired()) {
776  subArgsRequired = true;
777  break;
778  }
779  }
780  if (!subArgsRequired) {
781  m_defaultArg = *mainArguments.begin();
782  }
783 }
784 
792 {
793  argument->m_isMainArg = true;
794  m_mainArgs.push_back(argument);
795 }
796 
800 void ArgumentParser::printHelp(ostream &os) const
801 {
803  bool wroteLine = false;
805  os << applicationInfo.name;
807  os << ',' << ' ';
808  }
809  wroteLine = true;
810  }
812  os << "version " << applicationInfo.version;
813  wroteLine = true;
814  }
815  if (wroteLine) {
816  os << '\n' << '\n';
817  }
819 
822  wroteLine = true;
823  }
824  if (wroteLine) {
825  os << '\n' << '\n';
826  }
827 
828  if (!m_mainArgs.empty()) {
829  bool hasOperations = false, hasTopLevelOptions = false;
830  for (const Argument *const arg : m_mainArgs) {
831  if (arg->denotesOperation()) {
832  hasOperations = true;
833  } else if (strcmp(arg->name(), "help")) {
834  hasTopLevelOptions = true;
835  }
836  if (hasOperations && hasTopLevelOptions) {
837  break;
838  }
839  }
840 
841  // check whether operations are available
842  if (hasOperations) {
843  // split top-level operations and other configurations
844  os << "Available operations:";
845  for (const Argument *const arg : m_mainArgs) {
846  if (!arg->denotesOperation() || arg->isDeprecated() || !strcmp(arg->name(), "help")) {
847  continue;
848  }
849  os << '\n';
850  arg->printInfo(os);
851  }
852  if (hasTopLevelOptions) {
853  os << "\nAvailable top-level options:";
854  for (const Argument *const arg : m_mainArgs) {
855  if (arg->denotesOperation() || arg->isDeprecated() || !strcmp(arg->name(), "help")) {
856  continue;
857  }
858  os << '\n';
859  arg->printInfo(os);
860  }
861  }
862  } else {
863  // just show all args if no operations are available
864  os << "Available arguments:";
865  for (const Argument *const arg : m_mainArgs) {
866  if (arg->isDeprecated() || !strcmp(arg->name(), "help")) {
867  continue;
868  }
869  os << '\n';
870  arg->printInfo(os);
871  }
872  }
873  }
874 
875  if (!applicationInfo.dependencyVersions.empty()) {
876  os << '\n';
878  os << "Linked against: " << *i;
879  for (++i; i != end; ++i) {
880  os << ',' << ' ' << *i;
881  }
882  os << '\n';
883  }
884 
886  os << "\nProject website: " << applicationInfo.url << endl;
887  }
888 }
889 
906 void ArgumentParser::parseArgs(int argc, const char *const *argv, ParseArgumentBehavior behavior)
907 {
908  try {
909  readArgs(argc, argv);
910  if (!argc) {
911  return;
912  }
914  checkConstraints(m_mainArgs);
915  }
917  invokeCallbacks(m_mainArgs);
918  }
919  } catch (const ParseError &failure) {
920  if (behavior & ParseArgumentBehavior::ExitOnFailure) {
922  cerr << failure;
923  invokeExit(1);
924  }
925  throw;
926  }
927 }
928 
942 void ArgumentParser::readArgs(int argc, const char *const *argv)
943 {
944  CPP_UTILITIES_IF_DEBUG_BUILD(verifyArgs(m_mainArgs);)
945  m_actualArgc = 0;
946 
947  // the first argument is the executable name
948  if (!argc) {
949  m_executable = nullptr;
950  return;
951  }
952  m_executable = *argv;
953 
954  // check for further arguments
955  if (!--argc) {
956  // no arguments specified -> flag default argument as present if one is assigned
957  if (m_defaultArg) {
958  m_defaultArg->m_occurrences.emplace_back(0);
959  }
960  return;
961  }
962 
963  // check for completion mode: if first arg (after executable name) is "--bash-completion-for", bash completion for the following arguments is requested
964  const bool completionMode = !strcmp(*++argv, "--bash-completion-for");
965 
966  // determine the index of the current word for completion and the number of arguments to be passed to ArgumentReader
967  unsigned int currentWordIndex = 0, argcForReader;
968  if (completionMode) {
969  // the first argument after "--bash-completion-for" is the index of the current word
970  try {
971  currentWordIndex = (--argc ? stringToNumber<unsigned int, string>(*(++argv)) : 0);
972  if (argc) {
973  ++argv;
974  --argc;
975  }
976  } catch (const ConversionException &) {
977  currentWordIndex = static_cast<unsigned int>(argc - 1);
978  }
979  argcForReader = min(static_cast<unsigned int>(argc), currentWordIndex + 1);
980  } else {
981  argcForReader = static_cast<unsigned int>(argc);
982  }
983 
984  // read specified arguments
985  ArgumentReader reader(*this, argv, argv + argcForReader, completionMode);
986  const bool allArgsProcessed(reader.read());
987  m_noColorArg.apply();
988 
989  // fail when not all arguments could be processed, except when in completion mode
990  if (!completionMode && !allArgsProcessed) {
991  const auto suggestions(findSuggestions(argc, argv, static_cast<unsigned int>(argc - 1), reader));
992  throw ParseError(argsToString("The specified argument \"", *reader.argv, "\" is unknown.", suggestions));
993  }
994 
995  // print Bash completion and prevent the applicaton to continue with the regular execution
996  if (completionMode) {
997  printBashCompletion(argc, argv, currentWordIndex, reader);
998  invokeExit(0);
999  }
1000 }
1001 
1007 {
1008  for (Argument *arg : m_mainArgs) {
1009  arg->resetRecursively();
1010  }
1011  m_actualArgc = 0;
1012 }
1013 
1020 {
1021  for (Argument *arg : m_mainArgs) {
1022  if (arg->denotesOperation() && arg->isPresent()) {
1023  return arg;
1024  }
1025  }
1026  return nullptr;
1027 }
1028 
1033 {
1034  for (const Argument *arg : m_mainArgs) {
1035  if (!arg->isCombinable() && arg->isPresent()) {
1036  return true;
1037  }
1038  }
1039  return false;
1040 }
1041 
1042 #ifdef CPP_UTILITIES_DEBUG_BUILD
1057 void ArgumentParser::verifyArgs(const ArgumentVector &args)
1058 {
1059  vector<const Argument *> verifiedArgs;
1060  verifiedArgs.reserve(args.size());
1061  vector<char> abbreviations;
1062  abbreviations.reserve(abbreviations.size() + args.size());
1063  vector<const char *> names;
1064  names.reserve(names.size() + args.size());
1065  bool hasImplicit = false;
1066  for (const Argument *arg : args) {
1067  assert(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) == verifiedArgs.cend());
1068  verifiedArgs.push_back(arg);
1069  assert(!arg->isImplicit() || !hasImplicit);
1070  hasImplicit |= arg->isImplicit();
1071  assert(!arg->abbreviation() || find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) == abbreviations.cend());
1072  abbreviations.push_back(arg->abbreviation());
1073  assert(!arg->name() || find_if(names.cbegin(), names.cend(), [arg](const char *name) { return !strcmp(arg->name(), name); }) == names.cend());
1074  assert(arg->requiredValueCount() == 0 || arg->subArguments().size() == 0);
1075  names.emplace_back(arg->name());
1076  }
1077  for (const Argument *arg : args) {
1078  verifyArgs(arg->subArguments());
1079  }
1080 }
1081 #endif
1082 
1090 bool compareArgs(const Argument *arg1, const Argument *arg2)
1091 {
1092  if (arg1->denotesOperation() && !arg2->denotesOperation()) {
1093  return true;
1094  } else if (!arg1->denotesOperation() && arg2->denotesOperation()) {
1095  return false;
1096  } else {
1097  return strcmp(arg1->name(), arg2->name()) < 0;
1098  }
1099 }
1100 
1105 void insertSiblings(const ArgumentVector &siblings, list<const Argument *> &target)
1106 {
1107  bool onlyCombinable = false;
1108  for (const Argument *sibling : siblings) {
1109  if (sibling->isPresent() && !sibling->isCombinable()) {
1110  onlyCombinable = true;
1111  break;
1112  }
1113  }
1114  for (const Argument *sibling : siblings) {
1115  if ((!onlyCombinable || sibling->isCombinable()) && sibling->occurrences() < sibling->maxOccurrences()) {
1116  target.push_back(sibling);
1117  }
1118  }
1119 }
1120 
1124 ArgumentCompletionInfo ArgumentParser::determineCompletionInfo(
1125  int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader) const
1126 {
1127  ArgumentCompletionInfo completion(reader);
1128 
1129  // determine last detected arg
1130  if (completion.lastDetectedArg) {
1131  completion.lastDetectedArgIndex = static_cast<size_t>(reader.lastArgDenotation - argv);
1132  completion.lastDetectedArgPath = completion.lastDetectedArg->path(completion.lastDetectedArg->occurrences() - 1);
1133  }
1134 
1135  // determine last arg, omitting trailing empty args
1136  if (argc) {
1137  completion.lastSpecifiedArgIndex = static_cast<unsigned int>(argc) - 1;
1138  completion.lastSpecifiedArg = argv + completion.lastSpecifiedArgIndex;
1139  for (; completion.lastSpecifiedArg >= argv && **completion.lastSpecifiedArg == '\0';
1140  --completion.lastSpecifiedArg, --completion.lastSpecifiedArgIndex)
1141  ;
1142  }
1143 
1144  // just return main arguments if no args detected
1145  if (!completion.lastDetectedArg || !completion.lastDetectedArg->isPresent()) {
1146  completion.nextArgumentOrValue = true;
1147  insertSiblings(m_mainArgs, completion.relevantArgs);
1148  completion.relevantArgs.sort(compareArgs);
1149  return completion;
1150  }
1151 
1152  completion.nextArgumentOrValue = currentWordIndex > completion.lastDetectedArgIndex;
1153  if (!completion.nextArgumentOrValue) {
1154  // since the argument could be detected (hopefully unambiguously?) just return it for "final completion"
1155  completion.relevantArgs.push_back(completion.lastDetectedArg);
1156  completion.relevantArgs.sort(compareArgs);
1157  return completion;
1158  }
1159 
1160  // define function to add parameter values of argument as possible completions
1161  const auto addValueCompletionsForArg = [&completion](const Argument *arg) {
1162  if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::PreDefinedValues) {
1163  completion.relevantPreDefinedValues.push_back(arg);
1164  }
1165  if (!(arg->valueCompletionBehaviour() & ValueCompletionBehavior::FileSystemIfNoPreDefinedValues) || !arg->preDefinedCompletionValues()) {
1166  completion.completeFiles = completion.completeFiles || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Files;
1167  completion.completeDirs = completion.completeDirs || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Directories;
1168  }
1169  };
1170 
1171  // detect number of specified values
1172  auto currentValueCount = completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size();
1173  // ignore values which are specified after the current word
1174  if (currentValueCount) {
1175  const auto currentWordIndexRelativeToLastDetectedArg = currentWordIndex - completion.lastDetectedArgIndex;
1176  if (currentValueCount > currentWordIndexRelativeToLastDetectedArg) {
1177  currentValueCount -= currentWordIndexRelativeToLastDetectedArg;
1178  } else {
1179  currentValueCount = 0;
1180  }
1181  }
1182 
1183  // add value completions for implicit child if there are no value specified and there are no values required by the
1184  // last detected argument itself
1185  if (!currentValueCount && !completion.lastDetectedArg->requiredValueCount()) {
1186  for (const Argument *child : completion.lastDetectedArg->subArguments()) {
1187  if (child->isImplicit() && child->requiredValueCount()) {
1188  addValueCompletionsForArg(child);
1189  break;
1190  }
1191  }
1192  }
1193 
1194  // add value completions for last argument if there are further values required
1195  if (completion.lastDetectedArg->requiredValueCount() == Argument::varValueCount
1196  || (currentValueCount < completion.lastDetectedArg->requiredValueCount())) {
1197  addValueCompletionsForArg(completion.lastDetectedArg);
1198  }
1199 
1200  if (completion.lastDetectedArg->requiredValueCount() == Argument::varValueCount
1201  || completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size()
1202  >= completion.lastDetectedArg->requiredValueCount()) {
1203  // sub arguments of the last arg are possible completions
1204  for (const Argument *subArg : completion.lastDetectedArg->subArguments()) {
1205  if (subArg->occurrences() < subArg->maxOccurrences()) {
1206  completion.relevantArgs.push_back(subArg);
1207  }
1208  }
1209 
1210  // siblings of parents are possible completions as well
1211  for (auto parentArgument = completion.lastDetectedArgPath.crbegin(), end = completion.lastDetectedArgPath.crend();; ++parentArgument) {
1212  insertSiblings(parentArgument != end ? (*parentArgument)->subArguments() : m_mainArgs, completion.relevantArgs);
1213  if (parentArgument == end) {
1214  break;
1215  }
1216  }
1217  }
1218 
1219  return completion;
1220 }
1221 
1225 string ArgumentParser::findSuggestions(int argc, const char *const *argv, unsigned int cursorPos, const ArgumentReader &reader) const
1226 {
1227  // determine completion info
1228  const auto completionInfo(determineCompletionInfo(argc, argv, cursorPos, reader));
1229 
1230  // determine the unknown/misspelled argument
1231  const auto *unknownArg(*reader.argv);
1232  auto unknownArgSize(strlen(unknownArg));
1233  // -> refuse suggestions for long args to prevent huge memory allocation for Damerau-Levenshtein algo
1234  if (unknownArgSize > 16) {
1235  return string();
1236  }
1237  // -> remove dashes since argument names internally don't have them
1238  if (unknownArgSize >= 2 && unknownArg[0] == '-' && unknownArg[1] == '-') {
1239  unknownArg += 2;
1240  unknownArgSize -= 2;
1241  }
1242 
1243  // find best suggestions limiting the results to 2
1244  multiset<ArgumentSuggestion> bestSuggestions;
1245  // -> consider relevant arguments
1246  for (const Argument *const arg : completionInfo.relevantArgs) {
1247  ArgumentSuggestion(unknownArg, unknownArgSize, arg->name(), !arg->denotesOperation()).addTo(bestSuggestions, 2);
1248  }
1249  // -> consider relevant values
1250  for (const Argument *const arg : completionInfo.relevantPreDefinedValues) {
1251  if (!arg->preDefinedCompletionValues()) {
1252  continue;
1253  }
1254  for (const char *i = arg->preDefinedCompletionValues(); *i; ++i) {
1255  const char *const wordStart(i);
1256  const char *wordEnd(wordStart + 1);
1257  for (; *wordEnd && *wordEnd != ' '; ++wordEnd)
1258  ;
1259  ArgumentSuggestion(unknownArg, unknownArgSize, wordStart, static_cast<size_t>(wordEnd - wordStart), false).addTo(bestSuggestions, 2);
1260  i = wordEnd;
1261  }
1262  }
1263 
1264  // format suggestion
1265  string suggestionStr;
1266  if (const auto suggestionCount = bestSuggestions.size()) {
1267  // allocate memory
1268  size_t requiredSize = 15;
1269  for (const auto &suggestion : bestSuggestions) {
1270  requiredSize += suggestion.suggestionSize + 2;
1271  if (suggestion.hasDashPrefix) {
1272  requiredSize += 2;
1273  }
1274  }
1275  suggestionStr.reserve(requiredSize);
1276 
1277  // add each suggestion to end up with something like "Did you mean status (1), pause (3), cat (4), edit (5) or rescan-all (8)?"
1278  suggestionStr += "\nDid you mean ";
1279  size_t i = 0;
1280  for (const auto &suggestion : bestSuggestions) {
1281  if (++i == suggestionCount && suggestionCount != 1) {
1282  suggestionStr += " or ";
1283  } else if (i > 1) {
1284  suggestionStr += ", ";
1285  }
1286  if (suggestion.hasDashPrefix) {
1287  suggestionStr += "--";
1288  }
1289  suggestionStr.append(suggestion.suggestion, suggestion.suggestionSize);
1290  }
1291  suggestionStr += '?';
1292  }
1293  return suggestionStr;
1294 }
1295 
1301 void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader) const
1302 {
1303  // determine completion info and sort relevant arguments
1304  const auto completionInfo([&] {
1305  auto clutteredCompletionInfo(determineCompletionInfo(argc, argv, currentWordIndex, reader));
1306  clutteredCompletionInfo.relevantArgs.sort(compareArgs);
1307  return clutteredCompletionInfo;
1308  }());
1309 
1310  // read the "opening" (started but not finished argument denotation)
1311  const char *opening = nullptr;
1312  string compoundOpening;
1313  size_t openingLen = 0, compoundOpeningStartLen = 0;
1314  unsigned char openingDenotationType = Value;
1315  if (argc && completionInfo.nextArgumentOrValue) {
1316  if (currentWordIndex < static_cast<unsigned int>(argc)) {
1317  opening = argv[currentWordIndex];
1318  // For some reason completions for eg. "set --values disk=1 tag=a" are splitted so the
1319  // equation sign is an own argument ("set --values disk = 1 tag = a").
1320  // This is not how values are treated by the argument parser. Hence the opening
1321  // must be joined again. In this case only the part after the equation sign needs to be
1322  // provided for completion so compoundOpeningStartLen is set to number of characters to skip.
1323  const size_t minCurrentWordIndex = (completionInfo.lastDetectedArg ? completionInfo.lastDetectedArgIndex : 0);
1324  if (currentWordIndex > minCurrentWordIndex && !strcmp(opening, "=")) {
1325  compoundOpening.reserve(compoundOpeningStartLen = strlen(argv[--currentWordIndex]) + 1);
1326  compoundOpening = argv[currentWordIndex];
1327  compoundOpening += '=';
1328  } else if (currentWordIndex > (minCurrentWordIndex + 1) && !strcmp(argv[currentWordIndex - 1], "=")) {
1329  compoundOpening.reserve((compoundOpeningStartLen = strlen(argv[currentWordIndex -= 2]) + 1) + strlen(opening));
1330  compoundOpening = argv[currentWordIndex];
1331  compoundOpening += '=';
1332  compoundOpening += opening;
1333  }
1334  if (!compoundOpening.empty()) {
1335  opening = compoundOpening.data();
1336  }
1337  } else {
1338  opening = *completionInfo.lastSpecifiedArg;
1339  }
1340  if (*opening == '-') {
1341  ++opening;
1342  ++openingDenotationType;
1343  if (*opening == '-') {
1344  ++opening;
1345  ++openingDenotationType;
1346  }
1347  }
1348  openingLen = strlen(opening);
1349  }
1350 
1351  // print "COMPREPLY" bash array
1352  cout << "COMPREPLY=(";
1353  // -> completions for parameter values
1354  bool noWhitespace = false;
1355  for (const Argument *const arg : completionInfo.relevantPreDefinedValues) {
1356  if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::InvokeCallback && arg->m_callbackFunction) {
1357  arg->m_callbackFunction(arg->isPresent() ? arg->m_occurrences.front() : ArgumentOccurrence(Argument::varValueCount));
1358  }
1359  if (!arg->preDefinedCompletionValues()) {
1360  continue;
1361  }
1362  const bool appendEquationSign = arg->valueCompletionBehaviour() & ValueCompletionBehavior::AppendEquationSign;
1363  if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1364  if (openingDenotationType != Value) {
1365  continue;
1366  }
1367  bool wordStart = true, ok = false, equationSignAlreadyPresent = false;
1368  size_t wordIndex = 0;
1369  for (const char *i = arg->preDefinedCompletionValues(), *end = opening + openingLen; *i;) {
1370  if (wordStart) {
1371  const char *i1 = i, *i2 = opening;
1372  for (; *i1 && i2 != end && *i1 == *i2; ++i1, ++i2)
1373  ;
1374  if ((ok = (i2 == end))) {
1375  cout << '\'';
1376  }
1377  wordStart = false;
1378  wordIndex = 0;
1379  } else if ((wordStart = (*i == ' ') || (*i == '\n'))) {
1380  equationSignAlreadyPresent = false;
1381  if (ok) {
1382  cout << '\'' << ' ';
1383  }
1384  ++i;
1385  continue;
1386  } else if (*i == '=') {
1387  equationSignAlreadyPresent = true;
1388  }
1389  if (!ok) {
1390  ++i;
1391  continue;
1392  }
1393  if (!compoundOpeningStartLen || wordIndex >= compoundOpeningStartLen) {
1394  if (*i == '\'') {
1395  cout << "'\"'\"'";
1396  } else {
1397  cout << *i;
1398  }
1399  }
1400  ++i;
1401  ++wordIndex;
1402  switch (*i) {
1403  case ' ':
1404  case '\n':
1405  case '\0':
1406  if (appendEquationSign && !equationSignAlreadyPresent) {
1407  cout << '=';
1408  noWhitespace = true;
1409  equationSignAlreadyPresent = false;
1410  }
1411  if (*i == '\0') {
1412  cout << '\'';
1413  }
1414  }
1415  }
1416  cout << ' ';
1417  } else if (const char *i = arg->preDefinedCompletionValues()) {
1418  bool equationSignAlreadyPresent = false;
1419  cout << '\'';
1420  while (*i) {
1421  if (*i == '\'') {
1422  cout << "'\"'\"'";
1423  } else {
1424  cout << *i;
1425  }
1426  switch (*(++i)) {
1427  case '=':
1428  equationSignAlreadyPresent = true;
1429  break;
1430  case ' ':
1431  case '\n':
1432  case '\0':
1433  if (appendEquationSign && !equationSignAlreadyPresent) {
1434  cout << '=';
1435  equationSignAlreadyPresent = false;
1436  }
1437  if (*i != '\0') {
1438  cout << '\'';
1439  if (*(++i)) {
1440  cout << ' ' << '\'';
1441  }
1442  }
1443  }
1444  }
1445  cout << '\'' << ' ';
1446  }
1447  }
1448  // -> completions for further arguments
1449  for (const Argument *const arg : completionInfo.relevantArgs) {
1450  if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1451  switch (openingDenotationType) {
1452  case Value:
1453  if (!arg->denotesOperation() || strncmp(arg->name(), opening, openingLen)) {
1454  continue;
1455  }
1456  break;
1457  case Abbreviation:
1458  break;
1459  case FullName:
1460  if (strncmp(arg->name(), opening, openingLen)) {
1461  continue;
1462  }
1463  }
1464  }
1465 
1466  if (opening && openingDenotationType == Abbreviation && !completionInfo.nextArgumentOrValue) {
1467  // TODO: add test for this case
1468  cout << '\'' << '-' << opening << arg->abbreviation() << '\'' << ' ';
1469  } else if (completionInfo.lastDetectedArg && reader.argDenotationType == Abbreviation && !completionInfo.nextArgumentOrValue) {
1470  if (reader.argv == reader.end) {
1471  cout << '\'' << *(reader.argv - 1) << '\'' << ' ';
1472  }
1473  } else if (arg->denotesOperation()) {
1474  cout << '\'' << arg->name() << '\'' << ' ';
1475  } else {
1476  cout << '\'' << '-' << '-' << arg->name() << '\'' << ' ';
1477  }
1478  }
1479  // -> completions for files and dirs
1480  // -> if there's already an "opening", determine the dir part and the file part
1481  string actualDir, actualFile;
1482  bool haveFileOrDirCompletions = false;
1483  if (argc && currentWordIndex == completionInfo.lastSpecifiedArgIndex && opening) {
1484  // the "opening" might contain escaped characters which need to be unescaped first (let's hope this covers all possible escapings)
1485  string unescapedOpening(opening);
1486  findAndReplace<string>(unescapedOpening, "\\ ", " ");
1487  findAndReplace<string>(unescapedOpening, "\\,", ",");
1488  findAndReplace<string>(unescapedOpening, "\\[", "[");
1489  findAndReplace<string>(unescapedOpening, "\\]", "]");
1490  findAndReplace<string>(unescapedOpening, "\\!", "!");
1491  findAndReplace<string>(unescapedOpening, "\\#", "#");
1492  findAndReplace<string>(unescapedOpening, "\\$", "$");
1493  findAndReplace<string>(unescapedOpening, "\\'", "'");
1494  findAndReplace<string>(unescapedOpening, "\\\"", "\"");
1495  findAndReplace<string>(unescapedOpening, "\\\\", "\\");
1496  // determine the "directory" part
1497  string dir = directory(unescapedOpening);
1498  if (dir.empty()) {
1499  actualDir = ".";
1500  } else {
1501  if (dir[0] == '\"' || dir[0] == '\'') {
1502  dir.erase(0, 1);
1503  }
1504  if (dir.size() > 1 && (dir[dir.size() - 2] == '\"' || dir[dir.size() - 2] == '\'')) {
1505  dir.erase(dir.size() - 2, 1);
1506  }
1507  actualDir = move(dir);
1508  }
1509  // determine the "file" part
1510  string file = fileName(unescapedOpening);
1511  if (file[0] == '\"' || file[0] == '\'') {
1512  file.erase(0, 1);
1513  }
1514  if (file.size() > 1 && (file[dir.size() - 2] == '\"' || dir[file.size() - 2] == '\'')) {
1515  file.erase(file.size() - 2, 1);
1516  }
1517  actualFile = move(file);
1518  }
1519 
1520  // -> completion for files and dirs
1521 #ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
1522  if (completionInfo.completeFiles || completionInfo.completeDirs) {
1523  try {
1524  const auto replace = "'"s, with = "'\"'\"'"s;
1525  const auto useActualDir = argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening;
1526  const auto dirEntries = [&] {
1527  filesystem::directory_iterator i;
1528  if (useActualDir) {
1529  i = filesystem::directory_iterator(actualDir);
1530  findAndReplace(actualDir, replace, with);
1531  } else {
1532  i = filesystem::directory_iterator(".");
1533  }
1534  return i;
1535  }();
1536  for (const auto &dirEntry : dirEntries) {
1537  if (!completionInfo.completeDirs && dirEntry.is_directory()) {
1538  continue;
1539  }
1540  if (!completionInfo.completeFiles && !dirEntry.is_directory()) {
1541  continue;
1542  }
1543  auto dirEntryName = dirEntry.path().filename().string();
1544  auto hasStartingQuote = false;
1545  if (useActualDir) {
1546  if (!startsWith(dirEntryName, actualFile)) {
1547  continue;
1548  }
1549  cout << '\'';
1550  hasStartingQuote = true;
1551  if (actualDir != ".") {
1552  cout << actualDir;
1553  }
1554  }
1555  findAndReplace(dirEntryName, replace, with);
1556  if (!hasStartingQuote) {
1557  cout << '\'';
1558  }
1559  cout << dirEntryName << '\'' << ' ';
1560  haveFileOrDirCompletions = true;
1561  }
1562  } catch (const filesystem::filesystem_error &) {
1563  // ignore filesystem errors; there's no good way to report errors when printing bash completion
1564  }
1565  }
1566 #endif
1567  cout << ')';
1568 
1569  // ensure file or dir completions are formatted appropriately
1570  if (haveFileOrDirCompletions) {
1571  cout << "; compopt -o filenames";
1572  }
1573 
1574  // ensure trailing whitespace is ommitted
1575  if (noWhitespace) {
1576  cout << "; compopt -o nospace";
1577  }
1578 
1579  cout << endl;
1580 }
1581 
1587 {
1588  for (const Argument *arg : args) {
1589  const auto occurrences = arg->occurrences();
1590  if (arg->isParentPresent() && occurrences > arg->maxOccurrences()) {
1591  throw ParseError(argsToString("The argument \"", arg->name(), "\" mustn't be specified more than ", arg->maxOccurrences(),
1592  (arg->maxOccurrences() == 1 ? " time." : " times.")));
1593  }
1594  if (arg->isParentPresent() && occurrences < arg->minOccurrences()) {
1595  throw ParseError(argsToString("The argument \"", arg->name(), "\" must be specified at least ", arg->minOccurrences(),
1596  (arg->minOccurrences() == 1 ? " time." : " times.")));
1597  }
1598  Argument *conflictingArgument = nullptr;
1599  if (arg->isMainArgument()) {
1600  if (!arg->isCombinable() && arg->isPresent()) {
1601  conflictingArgument = firstPresentUncombinableArg(m_mainArgs, arg);
1602  }
1603  } else {
1604  conflictingArgument = arg->conflictsWithArgument();
1605  }
1606  if (conflictingArgument) {
1607  throw ParseError(argsToString("The argument \"", conflictingArgument->name(), "\" can not be combined with \"", arg->name(), "\"."));
1608  }
1609  for (size_t i = 0; i != occurrences; ++i) {
1610  if (arg->allRequiredValuesPresent(i)) {
1611  continue;
1612  }
1613  stringstream ss(stringstream::in | stringstream::out);
1614  ss << "Not all parameters for argument \"" << arg->name() << "\" ";
1615  if (i) {
1616  ss << " (" << (i + 1) << " occurrence) ";
1617  }
1618  ss << "provided. You have to provide the following parameters:";
1619  size_t valueNamesPrint = 0;
1620  for (const auto &name : arg->m_valueNames) {
1621  ss << ' ' << name;
1622  ++valueNamesPrint;
1623  }
1624  if (arg->m_requiredValueCount != Argument::varValueCount) {
1625  while (valueNamesPrint < arg->m_requiredValueCount) {
1626  ss << "\nvalue " << (++valueNamesPrint);
1627  }
1628  }
1629  throw ParseError(ss.str());
1630  }
1631 
1632  // check contraints of sub arguments recursively
1633  checkConstraints(arg->m_subArgs);
1634  }
1635 }
1636 
1645 {
1646  for (const Argument *arg : args) {
1647  // invoke the callback for each occurrence of the argument
1648  if (arg->m_callbackFunction) {
1649  for (const auto &occurrence : arg->m_occurrences) {
1650  arg->m_callbackFunction(occurrence);
1651  }
1652  }
1653  // invoke the callbacks for sub arguments recursively
1654  invokeCallbacks(arg->m_subArgs);
1655  }
1656 }
1657 
1661 void ArgumentParser::invokeExit(int code)
1662 {
1663  if (m_exitFunction) {
1664  m_exitFunction(code);
1665  return;
1666  }
1667  std::exit(code);
1668 }
1669 
1680  : Argument("help", 'h', "shows this information")
1681 {
1682  setCallback([&parser](const ArgumentOccurrence &) {
1684  parser.printHelp(cout);
1685  });
1686 }
1687 
1723 #ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1724  : Argument("no-color", '\0', "disables formatted/colorized output")
1725 #else
1726  : Argument("enable-color", '\0', "enables formatted/colorized output")
1727 #endif
1728 {
1729  setCombinable(true);
1730 
1731  // set the environmentvariable: note that this is not directly used and just assigned for printing help
1732  setEnvironmentVariable("ENABLE_ESCAPE_CODES");
1733 
1734  // default-initialize EscapeCodes::enabled from environment variable
1735  const char *envValue = getenv(environmentVariable());
1736  if (!envValue) {
1737  return;
1738  }
1739  for (; *envValue; ++envValue) {
1740  switch (*envValue) {
1741  case '0':
1742  case ' ':
1743  break;
1744  default:
1745  // enable escape codes if ENABLE_ESCAPE_CODES contains anything else than spaces or zeros
1746  EscapeCodes::enabled = true;
1747  return;
1748  }
1749  }
1750  // disable escape codes if ENABLE_ESCAPE_CODES is empty or only contains spaces and zeros
1751  EscapeCodes::enabled = false;
1752 }
1753 
1758 {
1759  if (isPresent()) {
1760 #ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1761  EscapeCodes::enabled = false;
1762 #else
1763  EscapeCodes::enabled = true;
1764 #endif
1765  }
1766 }
1767 
1771 void ValueConversion::Helper::ArgumentValueConversionError::throwFailure(const std::vector<Argument *> &argumentPath) const
1772 {
1773  throw ParseError(argumentPath.empty()
1774  ? argsToString("Conversion of top-level value \"", valueToConvert, "\" to type \"", targetTypeName, "\" failed: ", errorMessage)
1775  : argsToString("Conversion of value \"", valueToConvert, "\" (for argument --", argumentPath.back()->name(), ") to type \"",
1776  targetTypeName, "\" failed: ", errorMessage));
1777 }
1778 
1782 void ArgumentOccurrence::throwNumberOfValuesNotSufficient(unsigned long valuesToConvert) const
1783 {
1784  throw ParseError(path.empty()
1785  ? argsToString("Expected ", valuesToConvert, " top-level values to be present but only ", values.size(), " have been specified.")
1786  : argsToString("Expected ", valuesToConvert, " values for argument --", path.back()->name(), " to be present but only ", values.size(),
1787  " have been specified."));
1788 }
1789 
1790 } // namespace CppUtilities
#define CPP_UTILITIES_IF_DEBUG_BUILD(x)
Wraps debug-only lines conveniently.
Definition: global.h:102
The ArgumentParser class provides a means for handling command line arguments.
void checkConstraints()
Checks whether contraints are violated.
ArgumentParser()
Constructs a new ArgumentParser.
void readArgs(int argc, const char *const *argv)
Parses the specified command line arguments.
const ArgumentVector & mainArguments() const
Returns the main arguments.
bool isUncombinableMainArgPresent() const
Checks whether at least one uncombinable main argument is present.
void setMainArguments(const ArgumentInitializerList &mainArguments)
Sets the main arguments for the parser.
void printHelp(std::ostream &os) const
Prints help text for all assigned arguments.
void addMainArgument(Argument *argument)
Adds the specified argument to the main argument.
void parseArgs(int argc, const char *const *argv, ParseArgumentBehavior behavior=ParseArgumentBehavior::CheckConstraints|ParseArgumentBehavior::InvokeCallbacks|ParseArgumentBehavior::ExitOnFailure)
Parses the specified command line arguments.
void invokeCallbacks()
Invokes all assigned callbacks.
void resetArgs()
Resets all Argument instances assigned as mainArguments() and sub arguments.
Argument * specifiedOperation() const
Returns the first operation argument specified by the user or nullptr if no operation has been specif...
The ArgumentReader class internally encapsulates the process of reading command line arguments.
const char *const * lastArgDenotation
Points to the element in argv where lastArg was encountered. Unspecified if lastArg is not set.
const char * argDenotation
The currently processed abbreviation denotation (should be substring of one of the args in argv)....
ArgumentReader(ArgumentParser &parser, const char *const *argv, const char *const *end, bool completionMode=false)
Initializes the internal reader for the specified parser and arguments.
ArgumentVector & args
The Argument instances to store the results. Sub arguments of args are considered as well.
const char *const * argv
Points to the first argument denotation and will be incremented when a denotation has been processed.
Argument * lastArg
The last Argument instance which could be detected. Set to nullptr in the initial call....
bool completionMode
Whether completion mode is enabled. In this case reading args will be continued even if an denotation...
unsigned char argDenotationType
The type of the currently processed abbreviation denotation. Unspecified if argDenotation is not set.
std::size_t index
An index which is incremented when an argument is encountered (the current index is stored in the occ...
ArgumentReader & reset(const char *const *argv, const char *const *end)
Resets the ArgumentReader to continue reading new argv.
ArgumentParser & parser
The associated ArgumentParser instance.
bool read()
Reads the commands line arguments specified when constructing the object.
const char *const * end
Points to the end of the argv array.
The Argument class is a wrapper for command line argument information.
const char * description() const
Returns the description of the argument.
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.
bool isParentPresent() const
Returns whether at least one parent argument is present.
void addSubArgument(Argument *arg)
Adds arg as a secondary argument for this argument.
static constexpr std::size_t varValueCount
Denotes a variable number of values.
const std::vector< Argument * > & path(std::size_t occurrence=0) const
Returns the path of the specified occurrence.
bool isMainArgument() const
Returns an indication whether the argument is used as main argument.
const char * firstValue() const
Returns the first parameter value of the first occurrence of the argument.
const ArgumentVector & subArguments() const
Returns the secondary arguments for this argument.
const char * example() const
Returns the usage example of the argument.
const std::vector< const char * > & valueNames() const
Returns the names of the requried values.
char abbreviation() const
Returns the abbreviation of the argument.
std::size_t occurrences() const
Returns how often the argument could be detected when parsing.
Argument * specifiedOperation() const
Returns the first operation argument specified by the user or nullptr if no operation has been specif...
const char * name() const
Returns the name of the argument.
bool isCombinable() const
Returns an indication whether the argument is combinable.
bool denotesOperation() const
Returns whether the argument denotes an operation.
void resetRecursively()
Resets this argument and all sub arguments recursively.
std::size_t requiredValueCount() const
Returns the number of values which are required to be given for this argument.
const char * environmentVariable() const
Returns the environment variable queried when firstValue() is called.
Argument * wouldConflictWithArgument() const
Checks if this argument would conflict with other arguments if it was present.
void reset()
Resets occurrences (indices, values and paths).
~Argument()
Destroys the Argument.
bool isPresent() const
Returns an indication whether the argument could be detected when parsing.
Argument * conflictsWithArgument() const
Checks if this arguments conflicts with other arguments.
void setSubArguments(const ArgumentInitializerList &subArguments)
Sets the secondary arguments for this arguments.
void setCallback(CallbackFunction callback)
Sets a callback function which will be called by the parser if the argument could be found and no par...
bool isRequired() const
Returns an indication whether the argument is mandatory.
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 * firstValueOr(const char *fallback) const
Returns the first value like Argument::firstValue() but returns fallback instead of nullptr if there'...
The ConversionException class is thrown by the various conversion functions of this library when a co...
HelpArgument(ArgumentParser &parser)
Constructs a new help argument for the specified parser.
The Indentation class allows printing indentation conveniently, eg.
void apply() const
Sets EscapeCodes::enabled according to the presense of the first instantiation of NoColorArgument.
NoColorArgument()
Constructs a new NoColorArgument argument.
The ParseError class is thrown by an ArgumentParser when a parsing error occurs.
Definition: parseerror.h:11
The Wrapper class is internally used print text which might needs to be wrapped preserving the indent...
#define CMD_UTILS_START_CONSOLE
#define CPP_UTILITIES_EXPORT
Marks the symbol to be exported by the c++utilities library.
Encapsulates functions for formatted terminal output using ANSI escape codes.
CPP_UTILITIES_EXPORT bool enabled
Controls whether the functions inside the EscapeCodes namespace actually make use of escape codes.
void setStyle(std::ostream &stream, TextAttribute displayAttribute=TextAttribute::Reset)
Contains all utilities provides by the c++utilities library.
void findAndReplace(StringType1 &str, const StringType2 &find, const StringType3 &replace)
Replaces all occurences of find with relpace in the specified str.
CPP_UTILITIES_EXPORT TerminalSize determineTerminalSize()
Returns the current size of the terminal.
std::vector< Argument * > ArgumentVector
Argument * firstPresentUncombinableArg(const ArgumentVector &args, const Argument *except)
This function return the first present and uncombinable argument of the given list of arguments.
UnknownArgumentBehavior
The UnknownArgumentBehavior enum specifies the behavior of the argument parser when an unknown argume...
bool startsWith(const StringType &str, const StringType &phrase)
Returns whether str starts with phrase.
ParseArgumentBehavior
The ParseArgumentBehavior enum specifies the behavior when parsing arguments.
std::initializer_list< Argument * > ArgumentInitializerList
CPP_UTILITIES_EXPORT std::ostream & operator<<(std::ostream &out, Indentation indentation)
bool operator==(const AsHexNumber< T > &lhs, const AsHexNumber< T > &rhs)
Provides operator == required by CPPUNIT_ASSERT_EQUAL.
Definition: testutils.h:215
CPP_UTILITIES_EXPORT std::string directory(const std::string &path)
Returns the directory of the specified path string (including trailing slash).
Definition: path.cpp:35
StringType argsToString(Args &&...args)
CPP_UTILITIES_EXPORT std::string fileName(const std::string &path)
Returns the file name and extension of the specified path string.
Definition: path.cpp:15
constexpr T max(T first, T second)
Returns the greatest of the given items.
Definition: math.h:100
ValueCompletionBehavior
The ValueCompletionBehavior enum specifies the items to be considered when generating completion for ...
ArgumentDenotationType
The ArgumentDenotationType enum specifies the type of a given argument denotation.
constexpr T min(T first, T second)
Returns the smallest of the given items.
Definition: math.h:88
void insertSiblings(const ArgumentVector &siblings, list< const Argument * > &target)
Inserts the specified siblings in the target list.
bool compareArgs(const Argument *arg1, const Argument *arg2)
Returns whether arg1 should be listed before arg2 when printing completion.
CPP_UTILITIES_EXPORT std::size_t computeDamerauLevenshteinDistance(const char *str1, std::size_t size1, const char *str2, std::size_t size2)
CPP_UTILITIES_EXPORT ApplicationInfo applicationInfo
Stores global application info used by ArgumentParser::printHelp() and AboutDialog.
Stores information about an application.
std::vector< const char * > dependencyVersions
The ArgumentCompletionInfo struct holds information internally used for shell completion and suggesti...
list< const Argument * > relevantArgs
vector< Argument * > lastDetectedArgPath
list< const Argument * > relevantPreDefinedValues
The ArgumentOccurrence struct holds argument values for an occurrence of an argument.
std::vector< const char * > values
The parameter values which have been specified after the occurrence of the argument.
std::vector< Argument * > path
The "path" of the occurrence (the parent elements which have been specified before).
The TerminalSize struct describes a terminal size.
unsigned short columns
number of columns
constexpr int i