C++ Utilities  5.10.5
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 std::optional<bool> isEnvVariableSet(const char *variableName)
39 {
40  const char *envValue = std::getenv(variableName);
41  if (!envValue) {
42  return std::nullopt;
43  }
44  for (; *envValue; ++envValue) {
45  switch (*envValue) {
46  case '0':
47  case ' ':
48  break;
49  default:
50  return true;
51  }
52  }
53  return false;
54 }
55 
59 enum ArgumentDenotationType : unsigned char {
60  Value = 0,
62  FullName = 2
63 };
64 
70 
71  const Argument *const lastDetectedArg;
72  size_t lastDetectedArgIndex = 0;
73  vector<Argument *> lastDetectedArgPath;
74  list<const Argument *> relevantArgs;
75  list<const Argument *> relevantPreDefinedValues;
76  const char *const *lastSpecifiedArg = nullptr;
77  unsigned int lastSpecifiedArgIndex = 0;
78  bool nextArgumentOrValue = false;
79  bool completeFiles = false, completeDirs = false;
80 };
81 
86 ArgumentCompletionInfo::ArgumentCompletionInfo(const ArgumentReader &reader)
87  : lastDetectedArg(reader.lastArg)
88 {
89 }
90 
92 struct ArgumentSuggestion {
93  ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, bool hasDashPrefix);
94  ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, size_t suggestionSize, bool hasDashPrefix);
95  bool operator<(const ArgumentSuggestion &other) const;
96  bool operator==(const ArgumentSuggestion &other) const;
97  void addTo(multiset<ArgumentSuggestion> &suggestions, size_t limit) const;
98 
99  const char *const suggestion;
100  const size_t suggestionSize;
101  const size_t editingDistance;
102  const bool hasDashPrefix;
103 };
104 
105 ArgumentSuggestion::ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, size_t suggestionSize, bool isOperation)
106  : suggestion(suggestion)
107  , suggestionSize(suggestionSize)
108  , editingDistance(computeDamerauLevenshteinDistance(unknownArg, unknownArgSize, suggestion, suggestionSize))
109  , hasDashPrefix(isOperation)
110 {
111 }
112 
113 ArgumentSuggestion::ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, bool isOperation)
114  : ArgumentSuggestion(unknownArg, unknownArgSize, suggestion, strlen(suggestion), isOperation)
115 {
116 }
117 
118 bool ArgumentSuggestion::operator<(const ArgumentSuggestion &other) const
119 {
120  return editingDistance < other.editingDistance;
121 }
122 
123 void ArgumentSuggestion::addTo(multiset<ArgumentSuggestion> &suggestions, size_t limit) const
124 {
125  if (suggestions.size() >= limit && !(*this < *--suggestions.end())) {
126  return;
127  }
128  suggestions.emplace(*this);
129  while (suggestions.size() > limit) {
130  suggestions.erase(--suggestions.end());
131  }
132 }
134 
147 ArgumentReader::ArgumentReader(ArgumentParser &parser, const char *const *argv, const char *const *end, bool completionMode)
148  : parser(parser)
149  , args(parser.m_mainArgs)
150  , index(0)
151  , argv(argv)
152  , end(end)
153  , lastArg(nullptr)
154  , argDenotation(nullptr)
155  , completionMode(completionMode)
156 {
157 }
158 
162 ArgumentReader &ArgumentReader::reset(const char *const *argv, const char *const *end)
163 {
164  this->argv = argv;
165  this->end = end;
166  index = 0;
167  lastArg = nullptr;
168  argDenotation = nullptr;
169  return *this;
170 }
171 
177 {
178  return read(args);
179 }
180 
184 bool Argument::matchesDenotation(const char *denotation, size_t denotationLength) const
185 {
186  return m_name && !strncmp(m_name, denotation, denotationLength) && *(m_name + denotationLength) == '\0';
187 }
188 
197 {
198  // method is called recursively for sub args to the last argument (which is nullptr in the initial call) is the current parent argument
199  Argument *const parentArg = lastArg;
200  // determine the current path
201  const vector<Argument *> &parentPath = parentArg ? parentArg->path(parentArg->occurrences() - 1) : vector<Argument *>();
202 
203  Argument *lastArgInLevel = nullptr;
204  vector<const char *> *values = nullptr;
205 
206  // iterate through all argument denotations; loop might exit earlier when an denotation is unknown
207  while (argv != end) {
208  // check whether there are still values to read
209  if (values && lastArgInLevel->requiredValueCount() != Argument::varValueCount && values->size() < lastArgInLevel->requiredValueCount()) {
210  // read arg as value and continue with next arg
211  values->emplace_back(argDenotation ? argDenotation : *argv);
212  ++index;
213  ++argv;
214  argDenotation = nullptr;
215  continue;
216  }
217 
218  // determine how denotation must be processed
219  bool abbreviationFound = false;
220  if (argDenotation) {
221  // continue reading children for abbreviation denotation already detected
222  abbreviationFound = false;
224  } else {
225  // determine denotation type
226  argDenotation = *argv;
227  if (!*argDenotation && (!lastArgInLevel || values->size() >= lastArgInLevel->requiredValueCount())) {
228  // skip empty arguments
229  ++index;
230  ++argv;
231  argDenotation = nullptr;
232  continue;
233  }
234  abbreviationFound = false;
236  if (*argDenotation == '-') {
237  ++argDenotation;
239  if (*argDenotation == '-') {
240  ++argDenotation;
242  }
243  }
244  }
245 
246  // try to find matching Argument instance
247  Argument *matchingArg = nullptr;
248  if (argDenotationType != Value) {
249  // determine actual denotation length (everything before equation sign)
250  const char *const equationPos = strchr(argDenotation, '=');
251  const auto argDenotationLength = equationPos ? static_cast<size_t>(equationPos - argDenotation) : strlen(argDenotation);
252 
253  // loop through each "part" of the denotation
254  // names are read at once, but for abbreviations each character is considered individually
255  for (; argDenotationLength; matchingArg = nullptr) {
256  // search for arguments by abbreviation or name depending on the previously determined denotation type
258  for (Argument *const arg : args) {
259  if (arg->abbreviation() && arg->abbreviation() == *argDenotation) {
260  matchingArg = arg;
261  abbreviationFound = true;
262  break;
263  }
264  }
265  } else {
266  for (Argument *const arg : args) {
267  if (arg->matchesDenotation(argDenotation, argDenotationLength)) {
268  matchingArg = arg;
269  break;
270  }
271  }
272  }
273  if (!matchingArg) {
274  break;
275  }
276 
277  // an argument matched the specified denotation so add an occurrence
278  matchingArg->m_occurrences.emplace_back(index, parentPath, parentArg);
279 
280  // prepare reading parameter values
281  values = &matchingArg->m_occurrences.back().values;
282 
283  // read value after equation sign
284  if ((argDenotationType != Abbreviation && equationPos) || (++argDenotation == equationPos)) {
285  values->push_back(equationPos + 1);
286  argDenotation = nullptr;
287  }
288 
289  // read sub arguments, distinguish whether further abbreviations follow
290  ++index;
291  ++parser.m_actualArgc;
292  lastArg = lastArgInLevel = matchingArg;
295  // no further abbreviations follow -> read sub args for next argv
296  ++argv;
297  argDenotation = nullptr;
298  read(lastArg->m_subArgs);
299  argDenotation = nullptr;
300  break;
301  } else {
302  // further abbreviations follow -> remember current arg value
303  const char *const *const currentArgValue = argv;
304  // don't increment argv, keep processing outstanding chars of argDenotation
305  read(lastArg->m_subArgs);
306  // stop further processing if the denotation has been consumed or even the next value has already been loaded
307  if (!argDenotation || currentArgValue != argv) {
308  argDenotation = nullptr;
309  break;
310  }
311  }
312  }
313 
314  // continue with next arg if we've got a match already
315  if (matchingArg) {
316  continue;
317  }
318 
319  // unknown argument might be a sibling of the parent element
320  for (auto parentArgument = parentPath.crbegin(), pathEnd = parentPath.crend();; ++parentArgument) {
321  for (Argument *const sibling : (parentArgument != pathEnd ? (*parentArgument)->subArguments() : parser.m_mainArgs)) {
322  if (sibling->occurrences() < sibling->maxOccurrences()) {
323  // check whether the denoted abbreviation matches the sibling's abbreviatiopn
324  if (argDenotationType == Abbreviation && (sibling->abbreviation() && sibling->abbreviation() == *argDenotation)) {
325  return false;
326  }
327  // check whether the denoted name matches the sibling's name
328  if (sibling->matchesDenotation(argDenotation, argDenotationLength)) {
329  return false;
330  }
331  }
332  }
333  if (parentArgument == pathEnd) {
334  break;
335  }
336  }
337  }
338 
339  // unknown argument might just be a parameter value of the last argument
340  if (lastArgInLevel && values->size() < lastArgInLevel->requiredValueCount()) {
341  values->emplace_back(abbreviationFound ? argDenotation : *argv);
342  ++index;
343  ++argv;
344  argDenotation = nullptr;
345  continue;
346  }
347 
348  // first value might denote "operation"
349  for (Argument *const arg : args) {
350  if (arg->denotesOperation() && arg->name() && !strcmp(arg->name(), *argv)) {
351  (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
353  ++index;
354  ++argv;
355  break;
356  }
357  }
358 
359  // use the first default argument which is not already present if there is still no match
360  if (!matchingArg && (!completionMode || (argv + 1 != end))) {
361  const bool uncombinableMainArgPresent = parentArg ? false : parser.isUncombinableMainArgPresent();
362  for (Argument *const arg : args) {
363  if (arg->isImplicit() && !arg->isPresent() && !arg->wouldConflictWithArgument()
364  && (!uncombinableMainArgPresent || !arg->isMainArgument())) {
365  (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
366  break;
367  }
368  }
369  }
370 
371  if (matchingArg) {
372  // an argument matched the specified denotation
373  if (lastArgInLevel == matchingArg) {
374  break; // break required? -> TODO: add test for this condition
375  }
376 
377  // prepare reading parameter values
378  values = &matchingArg->m_occurrences.back().values;
379 
380  // read sub arguments
381  ++parser.m_actualArgc;
382  lastArg = lastArgInLevel = matchingArg;
383  argDenotation = nullptr;
384  read(lastArg->m_subArgs);
385  argDenotation = nullptr;
386  continue;
387  }
388 
389  // argument denotation is unknown -> handle error
390  if (parentArg) {
391  // continue with parent level
392  return false;
393  }
394  if (completionMode) {
395  // ignore unknown denotation
396  ++index;
397  ++argv;
398  argDenotation = nullptr;
399  } else {
400  switch (parser.m_unknownArgBehavior) {
402  cerr << Phrases::Warning << "The specified argument \"" << *argv << "\" is unknown and will be ignored." << Phrases::EndFlush;
403  [[fallthrough]];
405  // ignore unknown denotation
406  ++index;
407  ++argv;
408  argDenotation = nullptr;
409  break;
411  return false;
412  }
413  }
414  } // while(argv != end)
415  return true;
416 }
417 
424 ostream &operator<<(ostream &os, const Wrapper &wrapper)
425 {
426  // determine max. number of columns
427  static const TerminalSize termSize(determineTerminalSize());
428  const auto maxColumns = termSize.columns ? termSize.columns : numeric_limits<unsigned short>::max();
429 
430  // print wrapped string considering indentation
431  unsigned short currentCol = wrapper.m_indentation.level;
432  for (const char *currentChar = wrapper.m_str; *currentChar; ++currentChar) {
433  const bool wrappingRequired = currentCol >= maxColumns;
434  if (wrappingRequired || *currentChar == '\n') {
435  // insert newline (TODO: wrap only at end of a word)
436  os << '\n';
437  // print indentation (if enough space)
438  if (wrapper.m_indentation.level < maxColumns) {
439  os << wrapper.m_indentation;
440  currentCol = wrapper.m_indentation.level;
441  } else {
442  currentCol = 0;
443  }
444  }
445  if (*currentChar != '\n' && (!wrappingRequired || *currentChar != ' ')) {
446  os << *currentChar;
447  ++currentCol;
448  }
449  }
450  return os;
451 }
452 
454 
456 
457 inline bool notEmpty(const char *str)
458 {
459  return str && *str;
460 }
461 
463 
480 Argument::Argument(const char *name, char abbreviation, const char *description, const char *example)
481  : m_name(name)
482  , m_abbreviation(abbreviation)
483  , m_environmentVar(nullptr)
484  , m_description(description)
485  , m_example(example)
486  , m_minOccurrences(0)
487  , m_maxOccurrences(1)
488  , m_requiredValueCount(0)
489  , m_flags(Flags::None)
490  , m_deprecatedBy(nullptr)
491  , m_isMainArg(false)
494  , m_preDefinedCompletionValues(nullptr)
495 {
496 }
497 
502 {
503 }
504 
512 const char *Argument::firstValue() const
513 {
514  if (!m_occurrences.empty() && !m_occurrences.front().values.empty()) {
515  return m_occurrences.front().values.front();
516  } else if (m_environmentVar) {
517  return getenv(m_environmentVar);
518  } else {
519  return nullptr;
520  }
521 }
522 
526 const char *Argument::firstValueOr(const char *fallback) const
527 {
528  if (const auto *const v = firstValue()) {
529  return v;
530  } else {
531  return fallback;
532  }
533 }
534 
538 void Argument::printInfo(ostream &os, unsigned char indentation) const
539 {
540  if (isDeprecated()) {
541  return;
542  }
543  Indentation ident(indentation);
544  os << ident;
546  if (notEmpty(name())) {
547  if (!denotesOperation()) {
548  os << '-' << '-';
549  }
550  os << name();
551  }
552  if (notEmpty(name()) && abbreviation()) {
553  os << ',' << ' ';
554  }
555  if (abbreviation()) {
556  os << '-' << abbreviation();
557  }
559  if (requiredValueCount()) {
560  unsigned int valueNamesPrint = 0;
561  for (auto i = valueNames().cbegin(), end = valueNames().cend(); i != end && valueNamesPrint < requiredValueCount(); ++i) {
562  os << ' ' << '[' << *i << ']';
563  ++valueNamesPrint;
564  }
566  os << " ...";
567  } else {
568  for (; valueNamesPrint < requiredValueCount(); ++valueNamesPrint) {
569  os << " [value " << (valueNamesPrint + 1) << ']';
570  }
571  }
572  }
573  ident.level += 2;
574  if (notEmpty(description())) {
575  os << '\n' << ident << Wrapper(description(), ident);
576  }
577  if (isRequired()) {
578  os << '\n' << ident << "particularities: mandatory";
579  if (!isMainArgument()) {
580  os << " if parent argument is present";
581  }
582  }
583  if (environmentVariable()) {
584  os << '\n' << ident << "default environment variable: " << Wrapper(environmentVariable(), ident + 30);
585  }
586  os << '\n';
587  bool hasSubArgs = false;
588  for (const auto *const arg : subArguments()) {
589  if (arg->isDeprecated()) {
590  continue;
591  }
592  hasSubArgs = true;
593  arg->printInfo(os, ident.level);
594  }
595  if (notEmpty(example())) {
596  if (ident.level == 2 && hasSubArgs) {
597  os << '\n';
598  }
599  os << ident << "example: " << Wrapper(example(), ident + 9);
600  os << '\n';
601  }
602 }
603 
610 {
611  for (Argument *arg : args) {
612  if (arg != except && arg->isPresent() && !arg->isCombinable()) {
613  return arg;
614  }
615  }
616  return nullptr;
617 }
618 
633 void Argument::setSubArguments(const ArgumentInitializerList &secondaryArguments)
634 {
635  // remove this argument from the parents list of the previous secondary arguments
636  for (Argument *arg : m_subArgs) {
637  arg->m_parents.erase(remove(arg->m_parents.begin(), arg->m_parents.end(), this), arg->m_parents.end());
638  }
639  // assign secondary arguments
640  m_subArgs.assign(secondaryArguments);
641  // add this argument to the parents list of the assigned secondary arguments
642  // and set the parser
643  for (Argument *arg : m_subArgs) {
644  if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
645  arg->m_parents.push_back(this);
646  }
647  }
648 }
649 
658 {
659  if (find(m_subArgs.cbegin(), m_subArgs.cend(), arg) != m_subArgs.cend()) {
660  return;
661  }
662  m_subArgs.push_back(arg);
663  if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
664  arg->m_parents.push_back(this);
665  }
666 }
667 
673 {
674  if (isMainArgument()) {
675  return true;
676  }
677  for (const Argument *parent : m_parents) {
678  if (parent->isPresent()) {
679  return true;
680  }
681  }
682  return false;
683 }
684 
694 {
695  return isPresent() ? wouldConflictWithArgument() : nullptr;
696 }
697 
707 {
708  if (isCombinable()) {
709  return nullptr;
710  }
711  for (Argument *parent : m_parents) {
712  for (Argument *sibling : parent->subArguments()) {
713  if (sibling != this && sibling->isPresent() && !sibling->isCombinable()) {
714  return sibling;
715  }
716  }
717  }
718  return nullptr;
719 }
720 
726 {
727  for (Argument *arg : m_subArgs) {
728  if (arg->denotesOperation() && arg->isPresent()) {
729  return arg;
730  }
731  }
732  return nullptr;
733 }
734 
740 {
741  for (Argument *arg : m_subArgs) {
742  arg->resetRecursively();
743  }
744  reset();
745 }
746 
764  : m_actualArgc(0)
765  , m_executable(nullptr)
766  , m_unknownArgBehavior(UnknownArgumentBehavior::Fail)
767  , m_defaultArg(nullptr)
768  , m_helpArg(*this)
769 {
770 }
771 
782 {
783  if (!mainArguments.size()) {
784  m_mainArgs.clear();
785  return;
786  }
787  for (Argument *arg : mainArguments) {
788  arg->m_isMainArg = true;
789  }
790  m_mainArgs.assign(mainArguments);
791  if (m_defaultArg || (*mainArguments.begin())->requiredValueCount()) {
792  return;
793  }
794  bool subArgsRequired = false;
795  for (const Argument *subArg : (*mainArguments.begin())->subArguments()) {
796  if (subArg->isRequired()) {
797  subArgsRequired = true;
798  break;
799  }
800  }
801  if (!subArgsRequired) {
802  m_defaultArg = *mainArguments.begin();
803  }
804 }
805 
813 {
814  argument->m_isMainArg = true;
815  m_mainArgs.push_back(argument);
816 }
817 
821 void ArgumentParser::printHelp(ostream &os) const
822 {
824  bool wroteLine = false;
826  os << applicationInfo.name;
828  os << ',' << ' ';
829  }
830  wroteLine = true;
831  }
833  os << "version " << applicationInfo.version;
834  wroteLine = true;
835  }
836  if (wroteLine) {
837  os << '\n' << '\n';
838  }
840 
843  wroteLine = true;
844  }
845  if (wroteLine) {
846  os << '\n' << '\n';
847  }
848 
849  if (!m_mainArgs.empty()) {
850  bool hasOperations = false, hasTopLevelOptions = false;
851  for (const Argument *const arg : m_mainArgs) {
852  if (arg->denotesOperation()) {
853  hasOperations = true;
854  } else if (strcmp(arg->name(), "help")) {
855  hasTopLevelOptions = true;
856  }
857  if (hasOperations && hasTopLevelOptions) {
858  break;
859  }
860  }
861 
862  // check whether operations are available
863  if (hasOperations) {
864  // split top-level operations and other configurations
865  os << "Available operations:";
866  for (const Argument *const arg : m_mainArgs) {
867  if (!arg->denotesOperation() || arg->isDeprecated() || !strcmp(arg->name(), "help")) {
868  continue;
869  }
870  os << '\n';
871  arg->printInfo(os);
872  }
873  if (hasTopLevelOptions) {
874  os << "\nAvailable top-level options:";
875  for (const Argument *const arg : m_mainArgs) {
876  if (arg->denotesOperation() || arg->isDeprecated() || !strcmp(arg->name(), "help")) {
877  continue;
878  }
879  os << '\n';
880  arg->printInfo(os);
881  }
882  }
883  } else {
884  // just show all args if no operations are available
885  os << "Available arguments:";
886  for (const Argument *const arg : m_mainArgs) {
887  if (arg->isDeprecated() || !strcmp(arg->name(), "help")) {
888  continue;
889  }
890  os << '\n';
891  arg->printInfo(os);
892  }
893  }
894  }
895 
896  if (!applicationInfo.dependencyVersions.empty()) {
897  os << '\n';
899  os << "Linked against: " << *i;
900  for (++i; i != end; ++i) {
901  os << ',' << ' ' << *i;
902  }
903  os << '\n';
904  }
905 
907  os << "\nProject website: " << applicationInfo.url << endl;
908  }
909 }
910 
927 void ArgumentParser::parseArgs(int argc, const char *const *argv, ParseArgumentBehavior behavior)
928 {
929  try {
930  readArgs(argc, argv);
931  if (!argc) {
932  return;
933  }
935  checkConstraints(m_mainArgs);
936  }
938  invokeCallbacks(m_mainArgs);
939  }
940  } catch (const ParseError &failure) {
941  if (behavior & ParseArgumentBehavior::ExitOnFailure) {
943  cerr << failure;
944  invokeExit(1);
945  }
946  throw;
947  }
948 }
949 
963 void ArgumentParser::readArgs(int argc, const char *const *argv)
964 {
965  CPP_UTILITIES_IF_DEBUG_BUILD(verifyArgs(m_mainArgs);)
966  m_actualArgc = 0;
967 
968  // the first argument is the executable name
969  if (!argc) {
970  m_executable = nullptr;
971  return;
972  }
973  m_executable = *argv;
974 
975  // check for further arguments
976  if (!--argc) {
977  // no arguments specified -> flag default argument as present if one is assigned
978  if (m_defaultArg) {
979  m_defaultArg->m_occurrences.emplace_back(0);
980  }
981  return;
982  }
983 
984  // check for completion mode: if first arg (after executable name) is "--bash-completion-for", bash completion for the following arguments is requested
985  const bool completionMode = !strcmp(*++argv, "--bash-completion-for");
986 
987  // determine the index of the current word for completion and the number of arguments to be passed to ArgumentReader
988  unsigned int currentWordIndex = 0, argcForReader;
989  if (completionMode) {
990  // the first argument after "--bash-completion-for" is the index of the current word
991  try {
992  currentWordIndex = (--argc ? stringToNumber<unsigned int, string>(*(++argv)) : 0);
993  if (argc) {
994  ++argv;
995  --argc;
996  }
997  } catch (const ConversionException &) {
998  currentWordIndex = static_cast<unsigned int>(argc - 1);
999  }
1000  argcForReader = min(static_cast<unsigned int>(argc), currentWordIndex + 1);
1001  } else {
1002  argcForReader = static_cast<unsigned int>(argc);
1003  }
1004 
1005  // read specified arguments
1006  ArgumentReader reader(*this, argv, argv + argcForReader, completionMode);
1007  const bool allArgsProcessed(reader.read());
1008  m_noColorArg.apply();
1009 
1010  // fail when not all arguments could be processed, except when in completion mode
1011  if (!completionMode && !allArgsProcessed) {
1012  const auto suggestions(findSuggestions(argc, argv, static_cast<unsigned int>(argc - 1), reader));
1013  throw ParseError(argsToString("The specified argument \"", *reader.argv, "\" is unknown.", suggestions));
1014  }
1015 
1016  // print Bash completion and prevent the application to continue with the regular execution
1017  if (completionMode) {
1018  printBashCompletion(argc, argv, currentWordIndex, reader);
1019  invokeExit(0);
1020  }
1021 }
1022 
1028 {
1029  for (Argument *arg : m_mainArgs) {
1030  arg->resetRecursively();
1031  }
1032  m_actualArgc = 0;
1033 }
1034 
1041 {
1042  for (Argument *arg : m_mainArgs) {
1043  if (arg->denotesOperation() && arg->isPresent()) {
1044  return arg;
1045  }
1046  }
1047  return nullptr;
1048 }
1049 
1054 {
1055  for (const Argument *arg : m_mainArgs) {
1056  if (!arg->isCombinable() && arg->isPresent()) {
1057  return true;
1058  }
1059  }
1060  return false;
1061 }
1062 
1063 #ifdef CPP_UTILITIES_DEBUG_BUILD
1078 void ArgumentParser::verifyArgs(const ArgumentVector &args)
1079 {
1080  vector<const Argument *> verifiedArgs;
1081  verifiedArgs.reserve(args.size());
1082  vector<char> abbreviations;
1083  abbreviations.reserve(abbreviations.size() + args.size());
1084  vector<const char *> names;
1085  names.reserve(names.size() + args.size());
1086  bool hasImplicit = false;
1087  for (const Argument *arg : args) {
1088  assert(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) == verifiedArgs.cend());
1089  verifiedArgs.push_back(arg);
1090  assert(!arg->isImplicit() || !hasImplicit);
1091  hasImplicit |= arg->isImplicit();
1092  assert(!arg->abbreviation() || find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) == abbreviations.cend());
1093  abbreviations.push_back(arg->abbreviation());
1094  assert(!arg->name() || find_if(names.cbegin(), names.cend(), [arg](const char *name) { return !strcmp(arg->name(), name); }) == names.cend());
1095  assert(arg->requiredValueCount() == 0 || arg->subArguments().size() == 0);
1096  names.emplace_back(arg->name());
1097  }
1098  for (const Argument *arg : args) {
1099  verifyArgs(arg->subArguments());
1100  }
1101 }
1102 #endif
1103 
1111 bool compareArgs(const Argument *arg1, const Argument *arg2)
1112 {
1113  if (arg1->denotesOperation() && !arg2->denotesOperation()) {
1114  return true;
1115  } else if (!arg1->denotesOperation() && arg2->denotesOperation()) {
1116  return false;
1117  } else {
1118  return strcmp(arg1->name(), arg2->name()) < 0;
1119  }
1120 }
1121 
1126 void insertSiblings(const ArgumentVector &siblings, list<const Argument *> &target)
1127 {
1128  bool onlyCombinable = false;
1129  for (const Argument *sibling : siblings) {
1130  if (sibling->isPresent() && !sibling->isCombinable()) {
1131  onlyCombinable = true;
1132  break;
1133  }
1134  }
1135  for (const Argument *sibling : siblings) {
1136  if ((!onlyCombinable || sibling->isCombinable()) && sibling->occurrences() < sibling->maxOccurrences()) {
1137  target.push_back(sibling);
1138  }
1139  }
1140 }
1141 
1145 ArgumentCompletionInfo ArgumentParser::determineCompletionInfo(
1146  int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader) const
1147 {
1148  ArgumentCompletionInfo completion(reader);
1149 
1150  // determine last detected arg
1151  if (completion.lastDetectedArg) {
1152  completion.lastDetectedArgIndex = static_cast<size_t>(reader.lastArgDenotation - argv);
1153  completion.lastDetectedArgPath = completion.lastDetectedArg->path(completion.lastDetectedArg->occurrences() - 1);
1154  }
1155 
1156  // determine last arg, omitting trailing empty args
1157  if (argc) {
1158  completion.lastSpecifiedArgIndex = static_cast<unsigned int>(argc) - 1;
1159  completion.lastSpecifiedArg = argv + completion.lastSpecifiedArgIndex;
1160  for (; completion.lastSpecifiedArg >= argv && **completion.lastSpecifiedArg == '\0';
1161  --completion.lastSpecifiedArg, --completion.lastSpecifiedArgIndex)
1162  ;
1163  }
1164 
1165  // just return main arguments if no args detected
1166  if (!completion.lastDetectedArg || !completion.lastDetectedArg->isPresent()) {
1167  completion.nextArgumentOrValue = true;
1168  insertSiblings(m_mainArgs, completion.relevantArgs);
1169  completion.relevantArgs.sort(compareArgs);
1170  return completion;
1171  }
1172 
1173  completion.nextArgumentOrValue = currentWordIndex > completion.lastDetectedArgIndex;
1174  if (!completion.nextArgumentOrValue) {
1175  // since the argument could be detected (hopefully unambiguously?) just return it for "final completion"
1176  completion.relevantArgs.push_back(completion.lastDetectedArg);
1177  completion.relevantArgs.sort(compareArgs);
1178  return completion;
1179  }
1180 
1181  // define function to add parameter values of argument as possible completions
1182  const auto addValueCompletionsForArg = [&completion](const Argument *arg) {
1183  if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::PreDefinedValues) {
1184  completion.relevantPreDefinedValues.push_back(arg);
1185  }
1186  if (!(arg->valueCompletionBehaviour() & ValueCompletionBehavior::FileSystemIfNoPreDefinedValues) || !arg->preDefinedCompletionValues()) {
1187  completion.completeFiles = completion.completeFiles || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Files;
1188  completion.completeDirs = completion.completeDirs || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Directories;
1189  }
1190  };
1191 
1192  // detect number of specified values
1193  auto currentValueCount = completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size();
1194  // ignore values which are specified after the current word
1195  if (currentValueCount) {
1196  const auto currentWordIndexRelativeToLastDetectedArg = currentWordIndex - completion.lastDetectedArgIndex;
1197  if (currentValueCount > currentWordIndexRelativeToLastDetectedArg) {
1198  currentValueCount -= currentWordIndexRelativeToLastDetectedArg;
1199  } else {
1200  currentValueCount = 0;
1201  }
1202  }
1203 
1204  // add value completions for implicit child if there are no value specified and there are no values required by the
1205  // last detected argument itself
1206  if (!currentValueCount && !completion.lastDetectedArg->requiredValueCount()) {
1207  for (const Argument *child : completion.lastDetectedArg->subArguments()) {
1208  if (child->isImplicit() && child->requiredValueCount()) {
1209  addValueCompletionsForArg(child);
1210  break;
1211  }
1212  }
1213  }
1214 
1215  // add value completions for last argument if there are further values required
1216  if (completion.lastDetectedArg->requiredValueCount() == Argument::varValueCount
1217  || (currentValueCount < completion.lastDetectedArg->requiredValueCount())) {
1218  addValueCompletionsForArg(completion.lastDetectedArg);
1219  }
1220 
1221  if (completion.lastDetectedArg->requiredValueCount() == Argument::varValueCount
1222  || completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size()
1223  >= completion.lastDetectedArg->requiredValueCount()) {
1224  // sub arguments of the last arg are possible completions
1225  for (const Argument *subArg : completion.lastDetectedArg->subArguments()) {
1226  if (subArg->occurrences() < subArg->maxOccurrences()) {
1227  completion.relevantArgs.push_back(subArg);
1228  }
1229  }
1230 
1231  // siblings of parents are possible completions as well
1232  for (auto parentArgument = completion.lastDetectedArgPath.crbegin(), end = completion.lastDetectedArgPath.crend();; ++parentArgument) {
1233  insertSiblings(parentArgument != end ? (*parentArgument)->subArguments() : m_mainArgs, completion.relevantArgs);
1234  if (parentArgument == end) {
1235  break;
1236  }
1237  }
1238  }
1239 
1240  return completion;
1241 }
1242 
1246 string ArgumentParser::findSuggestions(int argc, const char *const *argv, unsigned int cursorPos, const ArgumentReader &reader) const
1247 {
1248  // determine completion info
1249  const auto completionInfo(determineCompletionInfo(argc, argv, cursorPos, reader));
1250 
1251  // determine the unknown/misspelled argument
1252  const auto *unknownArg(*reader.argv);
1253  auto unknownArgSize(strlen(unknownArg));
1254  // -> refuse suggestions for long args to prevent huge memory allocation for Damerau-Levenshtein algo
1255  if (unknownArgSize > 16) {
1256  return string();
1257  }
1258  // -> remove dashes since argument names internally don't have them
1259  if (unknownArgSize >= 2 && unknownArg[0] == '-' && unknownArg[1] == '-') {
1260  unknownArg += 2;
1261  unknownArgSize -= 2;
1262  }
1263 
1264  // find best suggestions limiting the results to 2
1265  multiset<ArgumentSuggestion> bestSuggestions;
1266  // -> consider relevant arguments
1267  for (const Argument *const arg : completionInfo.relevantArgs) {
1268  ArgumentSuggestion(unknownArg, unknownArgSize, arg->name(), !arg->denotesOperation()).addTo(bestSuggestions, 2);
1269  }
1270  // -> consider relevant values
1271  for (const Argument *const arg : completionInfo.relevantPreDefinedValues) {
1272  if (!arg->preDefinedCompletionValues()) {
1273  continue;
1274  }
1275  for (const char *i = arg->preDefinedCompletionValues(); *i; ++i) {
1276  const char *const wordStart(i);
1277  const char *wordEnd(wordStart + 1);
1278  for (; *wordEnd && *wordEnd != ' '; ++wordEnd)
1279  ;
1280  ArgumentSuggestion(unknownArg, unknownArgSize, wordStart, static_cast<size_t>(wordEnd - wordStart), false).addTo(bestSuggestions, 2);
1281  i = wordEnd;
1282  }
1283  }
1284 
1285  // format suggestion
1286  string suggestionStr;
1287  if (const auto suggestionCount = bestSuggestions.size()) {
1288  // allocate memory
1289  size_t requiredSize = 15;
1290  for (const auto &suggestion : bestSuggestions) {
1291  requiredSize += suggestion.suggestionSize + 2;
1292  if (suggestion.hasDashPrefix) {
1293  requiredSize += 2;
1294  }
1295  }
1296  suggestionStr.reserve(requiredSize);
1297 
1298  // add each suggestion to end up with something like "Did you mean status (1), pause (3), cat (4), edit (5) or rescan-all (8)?"
1299  suggestionStr += "\nDid you mean ";
1300  size_t i = 0;
1301  for (const auto &suggestion : bestSuggestions) {
1302  if (++i == suggestionCount && suggestionCount != 1) {
1303  suggestionStr += " or ";
1304  } else if (i > 1) {
1305  suggestionStr += ", ";
1306  }
1307  if (suggestion.hasDashPrefix) {
1308  suggestionStr += "--";
1309  }
1310  suggestionStr.append(suggestion.suggestion, suggestion.suggestionSize);
1311  }
1312  suggestionStr += '?';
1313  }
1314  return suggestionStr;
1315 }
1316 
1322 void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader) const
1323 {
1324  // determine completion info and sort relevant arguments
1325  const auto completionInfo([&] {
1326  auto clutteredCompletionInfo(determineCompletionInfo(argc, argv, currentWordIndex, reader));
1327  clutteredCompletionInfo.relevantArgs.sort(compareArgs);
1328  return clutteredCompletionInfo;
1329  }());
1330 
1331  // read the "opening" (started but not finished argument denotation)
1332  const char *opening = nullptr;
1333  string compoundOpening;
1334  size_t openingLen = 0, compoundOpeningStartLen = 0;
1335  unsigned char openingDenotationType = Value;
1336  if (argc && completionInfo.nextArgumentOrValue) {
1337  if (currentWordIndex < static_cast<unsigned int>(argc)) {
1338  opening = argv[currentWordIndex];
1339  // For some reason completions for eg. "set --values disk=1 tag=a" are split so the
1340  // equation sign is an own argument ("set --values disk = 1 tag = a").
1341  // This is not how values are treated by the argument parser. Hence the opening
1342  // must be joined again. In this case only the part after the equation sign needs to be
1343  // provided for completion so compoundOpeningStartLen is set to number of characters to skip.
1344  const size_t minCurrentWordIndex = (completionInfo.lastDetectedArg ? completionInfo.lastDetectedArgIndex : 0);
1345  if (currentWordIndex > minCurrentWordIndex && !strcmp(opening, "=")) {
1346  compoundOpening.reserve(compoundOpeningStartLen = strlen(argv[--currentWordIndex]) + 1);
1347  compoundOpening = argv[currentWordIndex];
1348  compoundOpening += '=';
1349  } else if (currentWordIndex > (minCurrentWordIndex + 1) && !strcmp(argv[currentWordIndex - 1], "=")) {
1350  compoundOpening.reserve((compoundOpeningStartLen = strlen(argv[currentWordIndex -= 2]) + 1) + strlen(opening));
1351  compoundOpening = argv[currentWordIndex];
1352  compoundOpening += '=';
1353  compoundOpening += opening;
1354  }
1355  if (!compoundOpening.empty()) {
1356  opening = compoundOpening.data();
1357  }
1358  } else {
1359  opening = *completionInfo.lastSpecifiedArg;
1360  }
1361  if (*opening == '-') {
1362  ++opening;
1363  ++openingDenotationType;
1364  if (*opening == '-') {
1365  ++opening;
1366  ++openingDenotationType;
1367  }
1368  }
1369  openingLen = strlen(opening);
1370  }
1371 
1372  // print "COMPREPLY" bash array
1373  cout << "COMPREPLY=(";
1374  // -> completions for parameter values
1375  bool noWhitespace = false;
1376  for (const Argument *const arg : completionInfo.relevantPreDefinedValues) {
1377  if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::InvokeCallback && arg->m_callbackFunction) {
1378  arg->m_callbackFunction(arg->isPresent() ? arg->m_occurrences.front() : ArgumentOccurrence(Argument::varValueCount));
1379  }
1380  if (!arg->preDefinedCompletionValues()) {
1381  continue;
1382  }
1383  const bool appendEquationSign = arg->valueCompletionBehaviour() & ValueCompletionBehavior::AppendEquationSign;
1384  if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1385  if (openingDenotationType != Value) {
1386  continue;
1387  }
1388  bool wordStart = true, ok = false, equationSignAlreadyPresent = false;
1389  size_t wordIndex = 0;
1390  for (const char *i = arg->preDefinedCompletionValues(), *end = opening + openingLen; *i;) {
1391  if (wordStart) {
1392  const char *i1 = i, *i2 = opening;
1393  for (; *i1 && i2 != end && *i1 == *i2; ++i1, ++i2)
1394  ;
1395  if ((ok = (i2 == end))) {
1396  cout << '\'';
1397  }
1398  wordStart = false;
1399  wordIndex = 0;
1400  } else if ((wordStart = (*i == ' ') || (*i == '\n'))) {
1401  equationSignAlreadyPresent = false;
1402  if (ok) {
1403  cout << '\'' << ' ';
1404  }
1405  ++i;
1406  continue;
1407  } else if (*i == '=') {
1408  equationSignAlreadyPresent = true;
1409  }
1410  if (!ok) {
1411  ++i;
1412  continue;
1413  }
1414  if (!compoundOpeningStartLen || wordIndex >= compoundOpeningStartLen) {
1415  if (*i == '\'') {
1416  cout << "'\"'\"'";
1417  } else {
1418  cout << *i;
1419  }
1420  }
1421  ++i;
1422  ++wordIndex;
1423  switch (*i) {
1424  case ' ':
1425  case '\n':
1426  case '\0':
1427  if (appendEquationSign && !equationSignAlreadyPresent) {
1428  cout << '=';
1429  noWhitespace = true;
1430  equationSignAlreadyPresent = false;
1431  }
1432  if (*i == '\0') {
1433  cout << '\'';
1434  }
1435  }
1436  }
1437  cout << ' ';
1438  } else if (const char *i = arg->preDefinedCompletionValues()) {
1439  bool equationSignAlreadyPresent = false;
1440  cout << '\'';
1441  while (*i) {
1442  if (*i == '\'') {
1443  cout << "'\"'\"'";
1444  } else {
1445  cout << *i;
1446  }
1447  switch (*(++i)) {
1448  case '=':
1449  equationSignAlreadyPresent = true;
1450  break;
1451  case ' ':
1452  case '\n':
1453  case '\0':
1454  if (appendEquationSign && !equationSignAlreadyPresent) {
1455  cout << '=';
1456  equationSignAlreadyPresent = false;
1457  }
1458  if (*i != '\0') {
1459  cout << '\'';
1460  if (*(++i)) {
1461  cout << ' ' << '\'';
1462  }
1463  }
1464  }
1465  }
1466  cout << '\'' << ' ';
1467  }
1468  }
1469  // -> completions for further arguments
1470  for (const Argument *const arg : completionInfo.relevantArgs) {
1471  if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1472  switch (openingDenotationType) {
1473  case Value:
1474  if (!arg->denotesOperation() || strncmp(arg->name(), opening, openingLen)) {
1475  continue;
1476  }
1477  break;
1478  case Abbreviation:
1479  break;
1480  case FullName:
1481  if (strncmp(arg->name(), opening, openingLen)) {
1482  continue;
1483  }
1484  }
1485  }
1486 
1487  if (opening && openingDenotationType == Abbreviation && !completionInfo.nextArgumentOrValue) {
1488  // TODO: add test for this case
1489  cout << '\'' << '-' << opening << arg->abbreviation() << '\'' << ' ';
1490  } else if (completionInfo.lastDetectedArg && reader.argDenotationType == Abbreviation && !completionInfo.nextArgumentOrValue) {
1491  if (reader.argv == reader.end) {
1492  cout << '\'' << *(reader.argv - 1) << '\'' << ' ';
1493  }
1494  } else if (arg->denotesOperation()) {
1495  cout << '\'' << arg->name() << '\'' << ' ';
1496  } else {
1497  cout << '\'' << '-' << '-' << arg->name() << '\'' << ' ';
1498  }
1499  }
1500  // -> completions for files and dirs
1501  // -> if there's already an "opening", determine the dir part and the file part
1502  string actualDir, actualFile;
1503  bool haveFileOrDirCompletions = false;
1504  if (argc && currentWordIndex == completionInfo.lastSpecifiedArgIndex && opening) {
1505  // the "opening" might contain escaped characters which need to be unescaped first (let's hope this covers all possible escapings)
1506  string unescapedOpening(opening);
1507  findAndReplace<string>(unescapedOpening, "\\ ", " ");
1508  findAndReplace<string>(unescapedOpening, "\\,", ",");
1509  findAndReplace<string>(unescapedOpening, "\\[", "[");
1510  findAndReplace<string>(unescapedOpening, "\\]", "]");
1511  findAndReplace<string>(unescapedOpening, "\\!", "!");
1512  findAndReplace<string>(unescapedOpening, "\\#", "#");
1513  findAndReplace<string>(unescapedOpening, "\\$", "$");
1514  findAndReplace<string>(unescapedOpening, "\\'", "'");
1515  findAndReplace<string>(unescapedOpening, "\\\"", "\"");
1516  findAndReplace<string>(unescapedOpening, "\\\\", "\\");
1517  // determine the "directory" part
1518  string dir = directory(unescapedOpening);
1519  if (dir.empty()) {
1520  actualDir = ".";
1521  } else {
1522  if (dir[0] == '\"' || dir[0] == '\'') {
1523  dir.erase(0, 1);
1524  }
1525  if (dir.size() > 1 && (dir[dir.size() - 2] == '\"' || dir[dir.size() - 2] == '\'')) {
1526  dir.erase(dir.size() - 2, 1);
1527  }
1528  actualDir = move(dir);
1529  }
1530  // determine the "file" part
1531  string file = fileName(unescapedOpening);
1532  if (file[0] == '\"' || file[0] == '\'') {
1533  file.erase(0, 1);
1534  }
1535  if (file.size() > 1 && (file[file.size() - 2] == '\"' || file[file.size() - 2] == '\'')) {
1536  file.erase(file.size() - 2, 1);
1537  }
1538  actualFile = move(file);
1539  }
1540 
1541  // -> completion for files and dirs
1542 #ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
1543  if (completionInfo.completeFiles || completionInfo.completeDirs) {
1544  try {
1545  const auto replace = "'"s, with = "'\"'\"'"s;
1546  const auto useActualDir = argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening;
1547  const auto dirEntries = [&] {
1548  filesystem::directory_iterator i;
1549  if (useActualDir) {
1550  i = filesystem::directory_iterator(actualDir);
1551  findAndReplace(actualDir, replace, with);
1552  } else {
1553  i = filesystem::directory_iterator(".");
1554  }
1555  return i;
1556  }();
1557  for (const auto &dirEntry : dirEntries) {
1558  if (!completionInfo.completeDirs && dirEntry.is_directory()) {
1559  continue;
1560  }
1561  if (!completionInfo.completeFiles && !dirEntry.is_directory()) {
1562  continue;
1563  }
1564  auto dirEntryName = dirEntry.path().filename().string();
1565  auto hasStartingQuote = false;
1566  if (useActualDir) {
1567  if (!startsWith(dirEntryName, actualFile)) {
1568  continue;
1569  }
1570  cout << '\'';
1571  hasStartingQuote = true;
1572  if (actualDir != ".") {
1573  cout << actualDir;
1574  }
1575  }
1576  findAndReplace(dirEntryName, replace, with);
1577  if (!hasStartingQuote) {
1578  cout << '\'';
1579  }
1580  cout << dirEntryName << '\'' << ' ';
1581  haveFileOrDirCompletions = true;
1582  }
1583  } catch (const filesystem::filesystem_error &) {
1584  // ignore filesystem errors; there's no good way to report errors when printing bash completion
1585  }
1586  }
1587 #endif
1588  cout << ')';
1589 
1590  // ensure file or dir completions are formatted appropriately
1591  if (haveFileOrDirCompletions) {
1592  cout << "; compopt -o filenames";
1593  }
1594 
1595  // ensure trailing whitespace is omitted
1596  if (noWhitespace) {
1597  cout << "; compopt -o nospace";
1598  }
1599 
1600  cout << endl;
1601 }
1602 
1608 {
1609  for (const Argument *arg : args) {
1610  const auto occurrences = arg->occurrences();
1611  if (arg->isParentPresent() && occurrences > arg->maxOccurrences()) {
1612  throw ParseError(argsToString("The argument \"", arg->name(), "\" mustn't be specified more than ", arg->maxOccurrences(),
1613  (arg->maxOccurrences() == 1 ? " time." : " times.")));
1614  }
1615  if (arg->isParentPresent() && occurrences < arg->minOccurrences()) {
1616  throw ParseError(argsToString("The argument \"", arg->name(), "\" must be specified at least ", arg->minOccurrences(),
1617  (arg->minOccurrences() == 1 ? " time." : " times.")));
1618  }
1619  Argument *conflictingArgument = nullptr;
1620  if (arg->isMainArgument()) {
1621  if (!arg->isCombinable() && arg->isPresent()) {
1622  conflictingArgument = firstPresentUncombinableArg(m_mainArgs, arg);
1623  }
1624  } else {
1625  conflictingArgument = arg->conflictsWithArgument();
1626  }
1627  if (conflictingArgument) {
1628  throw ParseError(argsToString("The argument \"", conflictingArgument->name(), "\" can not be combined with \"", arg->name(), "\"."));
1629  }
1630  for (size_t i = 0; i != occurrences; ++i) {
1631  if (arg->allRequiredValuesPresent(i)) {
1632  continue;
1633  }
1634  stringstream ss(stringstream::in | stringstream::out);
1635  ss << "Not all parameters for argument \"" << arg->name() << "\" ";
1636  if (i) {
1637  ss << " (" << (i + 1) << " occurrence) ";
1638  }
1639  ss << "provided. You have to provide the following parameters:";
1640  size_t valueNamesPrint = 0;
1641  for (const auto &name : arg->m_valueNames) {
1642  ss << ' ' << name;
1643  ++valueNamesPrint;
1644  }
1645  if (arg->m_requiredValueCount != Argument::varValueCount) {
1646  while (valueNamesPrint < arg->m_requiredValueCount) {
1647  ss << "\nvalue " << (++valueNamesPrint);
1648  }
1649  }
1650  throw ParseError(ss.str());
1651  }
1652 
1653  // check constraints of sub arguments recursively
1654  checkConstraints(arg->m_subArgs);
1655  }
1656 }
1657 
1666 {
1667  for (const Argument *arg : args) {
1668  // invoke the callback for each occurrence of the argument
1669  if (arg->m_callbackFunction) {
1670  for (const auto &occurrence : arg->m_occurrences) {
1671  arg->m_callbackFunction(occurrence);
1672  }
1673  }
1674  // invoke the callbacks for sub arguments recursively
1675  invokeCallbacks(arg->m_subArgs);
1676  }
1677 }
1678 
1682 void ArgumentParser::invokeExit(int code)
1683 {
1684  if (m_exitFunction) {
1685  m_exitFunction(code);
1686  return;
1687  }
1688  std::exit(code);
1689 }
1690 
1701  : Argument("help", 'h', "shows this information")
1702 {
1703  setCallback([&parser](const ArgumentOccurrence &) {
1705  parser.printHelp(cout);
1706  });
1707 }
1708 
1744 #ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1745  : Argument("no-color", '\0', "disables formatted/colorized output")
1746 #else
1747  : Argument("enable-color", '\0', "enables formatted/colorized output")
1748 #endif
1749 {
1750  setCombinable(true);
1751 
1752  // set the environment variable (not directly used and just assigned for printing help)
1753  setEnvironmentVariable("ENABLE_ESCAPE_CODES");
1754 
1755  // initialize EscapeCodes::enabled from environment variable
1756  const auto escapeCodesEnabled = isEnvVariableSet(environmentVariable());
1757  if (escapeCodesEnabled.has_value()) {
1758  EscapeCodes::enabled = escapeCodesEnabled.value();
1759  }
1760 }
1761 
1766 {
1767  if (isPresent()) {
1768 #ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1769  EscapeCodes::enabled = false;
1770 #else
1771  EscapeCodes::enabled = true;
1772 #endif
1773  }
1774 }
1775 
1779 void ValueConversion::Helper::ArgumentValueConversionError::throwFailure(const std::vector<Argument *> &argumentPath) const
1780 {
1781  throw ParseError(argumentPath.empty()
1782  ? argsToString("Conversion of top-level value \"", valueToConvert, "\" to type \"", targetTypeName, "\" failed: ", errorMessage)
1783  : argsToString("Conversion of value \"", valueToConvert, "\" (for argument --", argumentPath.back()->name(), ") to type \"",
1784  targetTypeName, "\" failed: ", errorMessage));
1785 }
1786 
1790 void ArgumentOccurrence::throwNumberOfValuesNotSufficient(unsigned long valuesToConvert) const
1791 {
1792  throw ParseError(path.empty()
1793  ? argsToString("Expected ", valuesToConvert, " top-level values to be present but only ", values.size(), " have been specified.")
1794  : argsToString("Expected ", valuesToConvert, " values for argument --", path.back()->name(), " to be present but only ", values.size(),
1795  " have been specified."));
1796 }
1797 
1798 } // 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 constraints 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 required 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 presence 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 occurrences 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.
std::optional< bool > isEnvVariableSet(const char *variableName)
Returns whether the specified env variable is set to a non-zero and non-white-space-only value.
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