C++ Utilities  5.10.0
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) {
380  case UnknownArgumentBehavior::Warn:
381  cerr << Phrases::Warning << "The specified argument \"" << *argv << "\" is unknown and will be ignored." << Phrases::EndFlush;
382  [[fallthrough]];
383  case UnknownArgumentBehavior::Ignore:
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)
471  , m_valueCompletionBehavior(ValueCompletionBehavior::PreDefinedValues | ValueCompletionBehavior::Files | ValueCompletionBehavior::Directories
472  | ValueCompletionBehavior::FileSystemIfNoPreDefinedValues)
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;
524  EscapeCodes::setStyle(os, EscapeCodes::TextAttribute::Bold);
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 {
802  EscapeCodes::setStyle(os, EscapeCodes::TextAttribute::Bold);
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  }
913  if (behavior & ParseArgumentBehavior::CheckConstraints) {
914  checkConstraints(m_mainArgs);
915  }
916  if (behavior & ParseArgumentBehavior::InvokeCallbacks) {
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
1043 
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 parameter for argument \"" << arg->name() << "\" ";
1615  if (i) {
1616  ss << " (" << (i + 1) << " occurrence) ";
1617  }
1618  ss << "provided. You have to provide the following parameter:";
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
CppUtilities::Argument::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.
Definition: argumentparser.cpp:459
CppUtilities::ArgumentReader::end
const char *const * end
Points to the end of the argv array.
Definition: argumentparserprivate.h:25
CppUtilities::Argument::setSubArguments
void setSubArguments(const ArgumentInitializerList &subArguments)
Sets the secondary arguments for this arguments.
Definition: argumentparser.cpp:612
CppUtilities::Argument::varValueCount
static constexpr std::size_t varValueCount
Denotes a variable number of values.
Definition: argumentparser.h:362
CppUtilities::ApplicationInfo::dependencyVersions
std::vector< const char * > dependencyVersions
Definition: argumentparser.h:31
CppUtilities::Argument
The Argument class is a wrapper for command line argument information.
Definition: argumentparser.h:262
CppUtilities::ArgumentReader::index
std::size_t index
An index which is incremented when an argument is encountered (the current index is stored in the occ...
Definition: argumentparserprivate.h:21
CppUtilities::FullName
@ FullName
Definition: argumentparser.cpp:41
CppUtilities::ArgumentCompletionInfo
The ArgumentCompletionInfo struct holds information internally used for shell completion and suggesti...
Definition: argumentparser.cpp:47
CppUtilities::ArgumentReader::read
bool read()
Reads the commands line arguments specified when constructing the object.
Definition: argumentparser.cpp:155
CppUtilities::ArgumentParser::checkConstraints
void checkConstraints()
Checks whether contraints are violated.
Definition: argumentparser.h:1153
CppUtilities::Indentation
The Indentation class allows printing indentation conveniently, eg.
Definition: commandlineutils.h:66
CppUtilities::Argument::isParentPresent
bool isParentPresent() const
Returns whether at least one parent argument is present.
Definition: argumentparser.cpp:651
CppUtilities::IniFileParseOptions::None
@ None
CppUtilities::ParseError
The ParseError class is thrown by an ArgumentParser when a parsing error occurs.
Definition: parseerror.h:11
CppUtilities::ArgumentReader::argv
const char *const * argv
Points to the first argument denotation and will be incremented when a denotation has been processed.
Definition: argumentparserprivate.h:23
CppUtilities::ValueCompletionBehavior
ValueCompletionBehavior
The ValueCompletionBehavior enum specifies the items to be considered when generating completion for ...
Definition: argumentparser.h:116
CppUtilities::ArgumentParser::resetArgs
void resetArgs()
Resets all Argument instances assigned as mainArguments() and sub arguments.
Definition: argumentparser.cpp:1006
CppUtilities::startsWith
bool startsWith(const StringType &str, const StringType &phrase)
Returns whether str starts with phrase.
Definition: stringconversion.h:213
CppUtilities::Argument::name
const char * name() const
Returns the name of the argument.
Definition: argumentparser.h:514
CppUtilities::ValueCompletionBehavior::None
@ None
CppUtilities::applicationInfo
CPP_UTILITIES_EXPORT ApplicationInfo applicationInfo
Stores global application info used by ArgumentParser::printHelp() and AboutDialog.
Definition: argumentparser.cpp:432
CppUtilities::ApplicationInfo
Stores information about an application.
Definition: argumentparser.h:22
CppUtilities::Argument::occurrences
std::size_t occurrences() const
Returns how often the argument could be detected when parsing.
Definition: argumentparser.h:747
CppUtilities::ArgumentCompletionInfo::lastDetectedArgPath
vector< Argument * > lastDetectedArgPath
Definition: argumentparser.cpp:52
CppUtilities::HelpArgument::HelpArgument
HelpArgument(ArgumentParser &parser)
Constructs a new help argument for the specified parser.
Definition: argumentparser.cpp:1679
CppUtilities::Argument::conflictsWithArgument
Argument * conflictsWithArgument() const
Checks if this arguments conflicts with other arguments.
Definition: argumentparser.cpp:672
CppUtilities::Argument::environmentVariable
const char * environmentVariable() const
Returns the environment variable queried when firstValue() is called.
Definition: argumentparser.h:567
CppUtilities::ArgumentParser::setMainArguments
void setMainArguments(const ArgumentInitializerList &mainArguments)
Sets the main arguments for the parser.
Definition: argumentparser.cpp:760
CppUtilities::Abbreviation
@ Abbreviation
Definition: argumentparser.cpp:40
CppUtilities::ArgumentDenotationType
ArgumentDenotationType
The ArgumentDenotationType enum specifies the type of a given argument denotation.
Definition: argumentparser.cpp:38
CppUtilities::ArgumentParser::mainArguments
const ArgumentVector & mainArguments() const
Returns the main arguments.
Definition: argumentparser.h:1089
CppUtilities::ArgumentParser::parseArgs
void parseArgs(int argc, const char *const *argv, ParseArgumentBehavior behavior=ParseArgumentBehavior::CheckConstraints|ParseArgumentBehavior::InvokeCallbacks|ParseArgumentBehavior::ExitOnFailure)
Parses the specified command line arguments.
Definition: argumentparser.cpp:906
CppUtilities::Argument::isDeprecated
bool isDeprecated() const
Definition: argumentparser.h:780
CppUtilities::Argument::printInfo
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.
Definition: argumentparser.cpp:517
CppUtilities::Indentation::level
unsigned char level
Definition: commandlineutils.h:79
CppUtilities::Argument::firstValueOr
const char * firstValueOr(const char *fallback) const
Returns the first value like Argument::firstValue() but returns fallback instead of nullptr if there'...
Definition: argumentparser.cpp:505
CppUtilities::firstPresentUncombinableArg
Argument * firstPresentUncombinableArg(const ArgumentVector &args, const Argument *except)
This function return the first present and uncombinable argument of the given list of arguments.
Definition: argumentparser.cpp:588
CppUtilities::ArgumentCompletionInfo::lastDetectedArg
const Argument *const lastDetectedArg
Definition: argumentparser.cpp:50
CppUtilities::Argument::example
const char * example() const
Returns the usage example of the argument.
Definition: argumentparser.h:606
CppUtilities::Argument::subArguments
const ArgumentVector & subArguments() const
Returns the secondary arguments for this argument.
Definition: argumentparser.h:957
CppUtilities::Argument::setCallback
void setCallback(CallbackFunction callback)
Sets a callback function which will be called by the parser if the argument could be found and no par...
Definition: argumentparser.h:946
CppUtilities::TerminalSize::columns
unsigned short columns
number of columns
Definition: commandlineutils.h:46
CppUtilities::Argument::requiredValueCount
std::size_t requiredValueCount() const
Returns the number of values which are required to be given for this argument.
Definition: argumentparser.h:645
CppUtilities::TerminalSize
The TerminalSize struct describes a terminal size.
Definition: commandlineutils.h:40
CppUtilities::operator<<
CPP_UTILITIES_EXPORT std::ostream & operator<<(std::ostream &out, Indentation indentation)
Definition: commandlineutils.h:83
CppUtilities::ParseArgumentBehavior::ReadArguments
@ ReadArguments
CppUtilities::findAndReplace
void findAndReplace(StringType1 &str, const StringType2 &find, const StringType3 &replace)
Replaces all occurences of find with relpace in the specified str.
Definition: stringconversion.h:317
CppUtilities::NoColorArgument::apply
void apply() const
Sets EscapeCodes::enabled according to the presense of the first instantiation of NoColorArgument.
Definition: argumentparser.cpp:1757
CppUtilities::max
constexpr T max(T first, T second)
Returns the greatest of the given items.
Definition: math.h:100
CppUtilities::insertSiblings
void insertSiblings(const ArgumentVector &siblings, list< const Argument * > &target)
Inserts the specified siblings in the target list.
Definition: argumentparser.cpp:1105
CppUtilities::ArgumentInitializerList
std::initializer_list< Argument * > ArgumentInitializerList
Definition: argumentparser.h:67
CppUtilities::Argument::specifiedOperation
Argument * specifiedOperation() const
Returns the first operation argument specified by the user or nullptr if no operation has been specif...
Definition: argumentparser.cpp:704
CppUtilities::ArgumentReader::reset
ArgumentReader & reset(const char *const *argv, const char *const *end)
Resets the ArgumentReader to continue reading new argv.
Definition: argumentparser.cpp:141
CppUtilities::ArgumentParser::invokeCallbacks
void invokeCallbacks()
Invokes all assigned callbacks.
Definition: argumentparser.h:1162
CppUtilities::Argument::isRequired
bool isRequired() const
Returns an indication whether the argument is mandatory.
Definition: argumentparser.h:832
CppUtilities::directory
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
CppUtilities::Argument::firstValue
const char * firstValue() const
Returns the first parameter value of the first occurrence of the argument.
Definition: argumentparser.cpp:491
CppUtilities::ParseArgumentBehavior
ParseArgumentBehavior
The ParseArgumentBehavior enum specifies the behavior when parsing arguments.
Definition: argumentparser.h:87
CppUtilities::Argument::addSubArgument
void addSubArgument(Argument *arg)
Adds arg as a secondary argument for this argument.
Definition: argumentparser.cpp:636
CppUtilities::Argument::description
const char * description() const
Returns the description of the argument.
Definition: argumentparser.h:586
CppUtilities::NoColorArgument::NoColorArgument
NoColorArgument()
Constructs a new NoColorArgument argument.
Definition: argumentparser.cpp:1722
CppUtilities::ApplicationInfo::url
const char * url
Definition: argumentparser.h:26
CppUtilities::ArgumentReader::parser
ArgumentParser & parser
The associated ArgumentParser instance.
Definition: argumentparserprivate.h:17
CppUtilities
Contains all utilities provides by the c++utilities library.
Definition: argumentparser.h:17
i
constexpr int i
Definition: traitstests.cpp:106
CppUtilities::ApplicationInfo::version
const char * version
Definition: argumentparser.h:25
CppUtilities::ArgumentParser::addMainArgument
void addMainArgument(Argument *argument)
Adds the specified argument to the main argument.
Definition: argumentparser.cpp:791
CppUtilities::EscapeCodes
Encapsulates functions for formatted terminal output using ANSI escape codes.
Definition: ansiescapecodes.h:12
CppUtilities::Argument::isPresent
bool isPresent() const
Returns an indication whether the argument could be detected when parsing.
Definition: argumentparser.h:739
CppUtilities::UnknownArgumentBehavior
UnknownArgumentBehavior
The UnknownArgumentBehavior enum specifies the behavior of the argument parser when an unknown argume...
Definition: argumentparser.h:75
CppUtilities::Value
@ Value
Definition: argumentparser.cpp:39
CppUtilities::Argument::reset
void reset()
Resets occurrences (indices, values and paths).
Definition: argumentparser.h:1042
CppUtilities::computeDamerauLevenshteinDistance
CPP_UTILITIES_EXPORT std::size_t computeDamerauLevenshteinDistance(const char *str1, std::size_t size1, const char *str2, std::size_t size2)
CppUtilities::ApplicationInfo::description
const char * description
Definition: argumentparser.h:28
CppUtilities::Argument::isCombinable
bool isCombinable() const
Returns an indication whether the argument is combinable.
Definition: argumentparser.h:889
CppUtilities::compareArgs
bool compareArgs(const Argument *arg1, const Argument *arg2)
Returns whether arg1 should be listed before arg2 when printing completion.
Definition: argumentparser.cpp:1090
CppUtilities::ArgumentReader
The ArgumentReader class internally encapsulates the process of reading command line arguments.
Definition: argumentparserprivate.h:9
CppUtilities::Argument::path
const std::vector< Argument * > & path(std::size_t occurrence=0) const
Returns the path of the specified occurrence.
Definition: argumentparser.h:818
CppUtilities::argsToString
StringType argsToString(Args &&...args)
Definition: stringbuilder.h:258
CppUtilities::ConversionException
The ConversionException class is thrown by the various conversion functions of this library when a co...
Definition: conversionexception.h:11
CppUtilities::ArgumentReader::lastArgDenotation
const char *const * lastArgDenotation
Points to the element in argv where lastArg was encountered. Unspecified if lastArg is not set.
Definition: argumentparserprivate.h:29
CppUtilities::ArgumentReader::argDenotationType
unsigned char argDenotationType
The type of the currently processed abbreviation denotation. Unspecified if argDenotation is not set.
Definition: argumentparserprivate.h:33
CppUtilities::ArgumentParser::isUncombinableMainArgPresent
bool isUncombinableMainArgPresent() const
Checks whether at least one uncombinable main argument is present.
Definition: argumentparser.cpp:1032
CppUtilities::Argument::Flags
Flags
Definition: argumentparser.h:270
CppUtilities::min
constexpr T min(T first, T second)
Returns the smallest of the given items.
Definition: math.h:88
CppUtilities::ArgumentOccurrence::values
std::vector< const char * > values
The parameter values which have been specified after the occurrence of the argument.
Definition: argumentparser.h:205
argumentparser.h
CppUtilities::ArgumentParser::printHelp
void printHelp(std::ostream &os) const
Prints help text for all assigned arguments.
Definition: argumentparser.cpp:800
CppUtilities::Argument::valueNames
const std::vector< const char * > & valueNames() const
Returns the names of the requried values.
Definition: argumentparser.h:675
CppUtilities::Wrapper
The Wrapper class is internally used print text which might needs to be wrapped preserving the indent...
Definition: argumentparserprivate.h:42
CppUtilities::ArgumentReader::lastArg
Argument * lastArg
The last Argument instance which could be detected. Set to nullptr in the initial call....
Definition: argumentparserprivate.h:27
CppUtilities::Argument::resetRecursively
void resetRecursively()
Resets this argument and all sub arguments recursively.
Definition: argumentparser.cpp:718
CppUtilities::Argument::abbreviation
char abbreviation() const
Returns the abbreviation of the argument.
Definition: argumentparser.h:544
CppUtilities::ArgumentParser::readArgs
void readArgs(int argc, const char *const *argv)
Parses the specified command line arguments.
Definition: argumentparser.cpp:942
CppUtilities::Argument::wouldConflictWithArgument
Argument * wouldConflictWithArgument() const
Checks if this argument would conflict with other arguments if it was present.
Definition: argumentparser.cpp:685
CMD_UTILS_START_CONSOLE
#define CMD_UTILS_START_CONSOLE
Definition: commandlineutils.h:31
CppUtilities::ArgumentCompletionInfo::relevantArgs
list< const Argument * > relevantArgs
Definition: argumentparser.cpp:53
CppUtilities::ArgumentOccurrence
The ArgumentOccurrence struct holds argument values for an occurrence of an argument.
Definition: argumentparser.h:193
CppUtilities::ArgumentReader::args
ArgumentVector & args
The Argument instances to store the results. Sub arguments of args are considered as well.
Definition: argumentparserprivate.h:19
CppUtilities::ArgumentVector
std::vector< Argument * > ArgumentVector
Definition: argumentparser.h:68
CPP_UTILITIES_EXPORT
#define CPP_UTILITIES_EXPORT
Marks the symbol to be exported by the c++utilities library.
CppUtilities::Argument::isMainArgument
bool isMainArgument() const
Returns an indication whether the argument is used as main argument.
Definition: argumentparser.h:994
commandlineutils.h
CppUtilities::ArgumentParser::specifiedOperation
Argument * specifiedOperation() const
Returns the first operation argument specified by the user or nullptr if no operation has been specif...
Definition: argumentparser.cpp:1019
argumentparserprivate.h
CppUtilities::operator==
bool operator==(const AsHexNumber< T > &lhs, const AsHexNumber< T > &rhs)
Provides operator == required by CPPUNIT_ASSERT_EQUAL.
Definition: testutils.h:215
CppUtilities::ArgumentOccurrence::path
std::vector< Argument * > path
The "path" of the occurrence (the parent elements which have been specified before).
Definition: argumentparser.h:211
CppUtilities::EscapeCodes::setStyle
void setStyle(std::ostream &stream, TextAttribute displayAttribute=TextAttribute::Reset)
Definition: ansiescapecodes.h:34
CppUtilities::ArgumentReader::ArgumentReader
ArgumentReader(ArgumentParser &parser, const char *const *argv, const char *const *end, bool completionMode=false)
Initializes the internal reader for the specified parser and arguments.
Definition: argumentparser.cpp:126
CppUtilities::fileName
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
CppUtilities::ArgumentParser
The ArgumentParser class provides a means for handling command line arguments.
Definition: argumentparser.h:451
CppUtilities::ArgumentReader::completionMode
bool completionMode
Whether completion mode is enabled. In this case reading args will be continued even if an denotation...
Definition: argumentparserprivate.h:35
CppUtilities::determineTerminalSize
CPP_UTILITIES_EXPORT TerminalSize determineTerminalSize()
Returns the current size of the terminal.
Definition: commandlineutils.cpp:46
CppUtilities::UnknownArgumentBehavior::Ignore
@ Ignore
CppUtilities::ArgumentReader::argDenotation
const char * argDenotation
The currently processed abbreviation denotation (should be substring of one of the args in argv)....
Definition: argumentparserprivate.h:31
CppUtilities::ApplicationInfo::name
const char * name
Definition: argumentparser.h:23
CPP_UTILITIES_IF_DEBUG_BUILD
#define CPP_UTILITIES_IF_DEBUG_BUILD(x)
Wraps debug-only lines conveniently.
Definition: global.h:102
CppUtilities::Argument::~Argument
~Argument()
Destroys the Argument.
Definition: argumentparser.cpp:480
CppUtilities::ArgumentCompletionInfo::relevantPreDefinedValues
list< const Argument * > relevantPreDefinedValues
Definition: argumentparser.cpp:54
CppUtilities::ArgumentParser::ArgumentParser
ArgumentParser()
Constructs a new ArgumentParser.
Definition: argumentparser.cpp:742
CppUtilities::EscapeCodes::enabled
CPP_UTILITIES_EXPORT bool enabled
Controls whether the functions inside the EscapeCodes namespace actually make use of escape codes.
Definition: ansiescapecodes.cpp:22
CppUtilities::Argument::denotesOperation
bool denotesOperation() const
Returns whether the argument denotes an operation.
Definition: argumentparser.h:917