C++ Utilities  4.14.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 #include "./failure.h"
5 
6 #include "../conversion/stringbuilder.h"
7 #include "../conversion/stringconversion.h"
8 #include "../io/ansiescapecodes.h"
9 #include "../io/path.h"
10 #include "../misc/levenshtein.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 using namespace std;
21 using namespace std::placeholders;
22 using namespace ConversionUtilities;
23 using namespace EscapeCodes;
24 using namespace IoUtilities;
25 
30 namespace ApplicationUtilities {
31 
35 enum ArgumentDenotationType : unsigned char {
36  Value = 0,
38  FullName = 2
39 };
40 
46 
47  const Argument *const lastDetectedArg;
48  size_t lastDetectedArgIndex = 0;
49  vector<Argument *> lastDetectedArgPath;
50  list<const Argument *> relevantArgs;
51  list<const Argument *> relevantPreDefinedValues;
52  const char *const *lastSpecifiedArg = nullptr;
53  unsigned int lastSpecifiedArgIndex = 0;
54  bool nextArgumentOrValue = false;
55  bool completeFiles = false, completeDirs = false;
56 };
57 
62 ArgumentCompletionInfo::ArgumentCompletionInfo(const ArgumentReader &reader)
63  : lastDetectedArg(reader.lastArg)
64 {
65 }
66 
68  ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, bool hasDashPrefix);
69  ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, size_t suggestionSize, bool hasDashPrefix);
70  bool operator<(const ArgumentSuggestion &other) const;
71  bool operator==(const ArgumentSuggestion &other) const;
72  void addTo(multiset<ArgumentSuggestion> &suggestions, size_t limit) const;
73 
74  const char *const suggestion;
75  const size_t suggestionSize;
76  const size_t editingDistance;
77  const bool hasDashPrefix;
78 };
79 
80 ArgumentSuggestion::ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, size_t suggestionSize, bool isOperation)
81  : suggestion(suggestion)
82  , suggestionSize(suggestionSize)
83  , editingDistance(MiscUtilities::computeDamerauLevenshteinDistance(unknownArg, unknownArgSize, suggestion, suggestionSize))
84  , hasDashPrefix(isOperation)
85 {
86 }
87 
88 ArgumentSuggestion::ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, bool isOperation)
89  : ArgumentSuggestion(unknownArg, unknownArgSize, suggestion, strlen(suggestion), isOperation)
90 {
91 }
92 
94 {
95  return editingDistance < other.editingDistance;
96 }
97 
98 void ArgumentSuggestion::addTo(multiset<ArgumentSuggestion> &suggestions, size_t limit) const
99 {
100  if (suggestions.size() >= limit && !(*this < *--suggestions.end())) {
101  return;
102  }
103  suggestions.emplace(*this);
104  while (suggestions.size() > limit) {
105  suggestions.erase(--suggestions.end());
106  }
107 }
108 
121 ArgumentReader::ArgumentReader(ArgumentParser &parser, const char *const *argv, const char *const *end, bool completionMode)
122  : parser(parser)
123  , args(parser.m_mainArgs)
124  , index(0)
125  , argv(argv)
126  , end(end)
127  , lastArg(nullptr)
128  , argDenotation(nullptr)
129  , completionMode(completionMode)
130 {
131 }
132 
136 ArgumentReader &ArgumentReader::reset(const char *const *argv, const char *const *end)
137 {
138  this->argv = argv;
139  this->end = end;
140  index = 0;
141  lastArg = nullptr;
142  argDenotation = nullptr;
143  return *this;
144 }
145 
151 {
152  return read(args);
153 }
154 
158 bool Argument::matchesDenotation(const char *denotation, size_t denotationLength) const
159 {
160  return m_name && !strncmp(m_name, denotation, denotationLength) && *(m_name + denotationLength) == '\0';
161 }
162 
171 {
172  // method is called recursively for sub args to the last argument (which is nullptr in the initial call) is the current parent argument
173  Argument *const parentArg = lastArg;
174  // determine the current path
175  const vector<Argument *> &parentPath = parentArg ? parentArg->path(parentArg->occurrences() - 1) : vector<Argument *>();
176 
177  Argument *lastArgInLevel = nullptr;
178  vector<const char *> *values = nullptr;
179 
180  // iterate through all argument denotations; loop might exit earlier when an denotation is unknown
181  while (argv != end) {
182  // check whether there are still values to read
183  if (values && lastArgInLevel->requiredValueCount() != Argument::varValueCount && values->size() < lastArgInLevel->requiredValueCount()) {
184  // read arg as value and continue with next arg
185  values->emplace_back(argDenotation ? argDenotation : *argv);
186  ++index, ++argv, argDenotation = nullptr;
187  continue;
188  }
189 
190  // determine how denotation must be processed
191  bool abbreviationFound = false;
192  if (argDenotation) {
193  // continue reading childs for abbreviation denotation already detected
194  abbreviationFound = false;
196  } else {
197  // determine denotation type
198  argDenotation = *argv;
199  if (!*argDenotation && (!lastArgInLevel || values->size() >= lastArgInLevel->requiredValueCount())) {
200  // skip empty arguments
201  ++index, ++argv, argDenotation = nullptr;
202  continue;
203  }
204  abbreviationFound = false;
207  }
208 
209  // try to find matching Argument instance
210  Argument *matchingArg = nullptr;
211  if (argDenotationType != Value) {
212  // determine actual denotation length (everything before equation sign)
213  const char *const equationPos = strchr(argDenotation, '=');
214  const auto argDenotationLength = equationPos ? static_cast<size_t>(equationPos - argDenotation) : strlen(argDenotation);
215 
216  // loop through each "part" of the denotation
217  // names are read at once, but for abbreviations each character is considered individually
218  for (; argDenotationLength; matchingArg = nullptr) {
219  // search for arguments by abbreviation or name depending on the previously determined denotation type
221  for (Argument *const arg : args) {
222  if (arg->abbreviation() && arg->abbreviation() == *argDenotation) {
223  matchingArg = arg;
224  abbreviationFound = true;
225  break;
226  }
227  }
228  } else {
229  for (Argument *const arg : args) {
230  if (arg->matchesDenotation(argDenotation, argDenotationLength)) {
231  matchingArg = arg;
232  break;
233  }
234  }
235  }
236  if (!matchingArg) {
237  break;
238  }
239 
240  // an argument matched the specified denotation so add an occurrence
241  matchingArg->m_occurrences.emplace_back(index, parentPath, parentArg);
242 
243  // prepare reading parameter values
244  values = &matchingArg->m_occurrences.back().values;
245 
246  // read value after equation sign
247  if ((argDenotationType != Abbreviation && equationPos) || (++argDenotation == equationPos)) {
248  values->push_back(equationPos + 1);
249  argDenotation = nullptr;
250  }
251 
252  // read sub arguments, distinguish whether further abbreviations follow
253  ++index, ++parser.m_actualArgc, lastArg = lastArgInLevel = matchingArg, lastArgDenotation = argv;
255  // no further abbreviations follow -> read sub args for next argv
256  ++argv, argDenotation = nullptr;
257  read(lastArg->m_subArgs);
258  argDenotation = nullptr;
259  break;
260  } else {
261  // further abbreviations follow -> remember current arg value
262  const char *const *const currentArgValue = argv;
263  // don't increment argv, keep processing outstanding chars of argDenotation
264  read(lastArg->m_subArgs);
265  // stop further processing if the denotation has been consumed or even the next value has already been loaded
266  if (!argDenotation || currentArgValue != argv) {
267  argDenotation = nullptr;
268  break;
269  }
270  }
271  }
272 
273  // continue with next arg if we've got a match already
274  if (matchingArg) {
275  continue;
276  }
277 
278  // unknown argument might be a sibling of the parent element
279  for (auto parentArgument = parentPath.crbegin(), pathEnd = parentPath.crend();; ++parentArgument) {
280  for (Argument *const sibling : (parentArgument != pathEnd ? (*parentArgument)->subArguments() : parser.m_mainArgs)) {
281  if (sibling->occurrences() < sibling->maxOccurrences()) {
282  // check whether the denoted abbreviation matches the sibling's abbreviatiopn
283  if (argDenotationType == Abbreviation && (sibling->abbreviation() && sibling->abbreviation() == *argDenotation)) {
284  return false;
285  }
286  // check whether the denoted name matches the sibling's name
287  if (sibling->matchesDenotation(argDenotation, argDenotationLength)) {
288  return false;
289  }
290  }
291  }
292  if (parentArgument == pathEnd) {
293  break;
294  }
295  };
296  }
297 
298  // unknown argument might just be a parameter value of the last argument
299  if (lastArgInLevel && values->size() < lastArgInLevel->requiredValueCount()) {
300  values->emplace_back(abbreviationFound ? argDenotation : *argv);
301  ++index, ++argv, argDenotation = nullptr;
302  continue;
303  }
304 
305  // first value might denote "operation"
306  for (Argument *const arg : args) {
307  if (arg->denotesOperation() && arg->name() && !strcmp(arg->name(), *argv)) {
308  (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
310  ++index, ++argv;
311  break;
312  }
313  }
314 
315  // use the first default argument which is not already present if there is still no match
316  if (!matchingArg && (!completionMode || (argv + 1 != end))) {
317  const bool uncombinableMainArgPresent = parentArg ? false : parser.isUncombinableMainArgPresent();
318  for (Argument *const arg : args) {
319  if (arg->isImplicit() && !arg->isPresent() && !arg->wouldConflictWithArgument()
320  && (!uncombinableMainArgPresent || !arg->isMainArgument())) {
321  (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
322  break;
323  }
324  }
325  }
326 
327  if (matchingArg) {
328  // an argument matched the specified denotation
329  if (lastArgInLevel == matchingArg) {
330  break; // break required? -> TODO: add test for this condition
331  }
332 
333  // prepare reading parameter values
334  values = &matchingArg->m_occurrences.back().values;
335 
336  // read sub arguments
337  ++parser.m_actualArgc, lastArg = lastArgInLevel = matchingArg, argDenotation = nullptr;
338  read(lastArg->m_subArgs);
339  argDenotation = nullptr;
340  continue;
341  }
342 
343  // argument denotation is unknown -> handle error
344  if (parentArg) {
345  // continue with parent level
346  return false;
347  }
348  if (completionMode) {
349  // ignore unknown denotation
350  ++index, ++argv, argDenotation = nullptr;
351  } else {
352  switch (parser.m_unknownArgBehavior) {
354  cerr << Phrases::Warning << "The specified argument \"" << *argv << "\" is unknown and will be ignored." << Phrases::EndFlush;
355  FALLTHROUGH;
357  // ignore unknown denotation
358  ++index, ++argv, argDenotation = nullptr;
359  break;
361  return false;
362  }
363  }
364  } // while(argv != end)
365  return true;
366 }
367 
374 ostream &operator<<(ostream &os, const Wrapper &wrapper)
375 {
376  // determine max. number of columns
377  static const TerminalSize termSize(determineTerminalSize());
378  const auto maxColumns = termSize.columns ? termSize.columns : numeric_limits<unsigned short>::max();
379 
380  // print wrapped string considering indentation
381  unsigned short currentCol = wrapper.m_indentation.level;
382  for (const char *currentChar = wrapper.m_str; *currentChar; ++currentChar) {
383  const bool wrappingRequired = currentCol >= maxColumns;
384  if (wrappingRequired || *currentChar == '\n') {
385  // insert newline (TODO: wrap only at end of a word)
386  os << '\n';
387  // print indentation (if enough space)
388  if (wrapper.m_indentation.level < maxColumns) {
389  os << wrapper.m_indentation;
390  currentCol = wrapper.m_indentation.level;
391  } else {
392  currentCol = 0;
393  }
394  }
395  if (*currentChar != '\n' && (!wrappingRequired || *currentChar != ' ')) {
396  os << *currentChar;
397  ++currentCol;
398  }
399  }
400  return os;
401 }
402 
404 const char *applicationName = nullptr;
406 const char *applicationAuthor = nullptr;
408 const char *applicationVersion = nullptr;
410 const char *applicationUrl = nullptr;
413 std::initializer_list<const char *> dependencyVersions;
415 std::vector<const char *> dependencyVersions2;
416 
417 // TODO v5 use a struct for these properties
418 
423 void (*exitFunction)(int) = &exit;
424 
426 
427 inline bool notEmpty(const char *str)
428 {
429  return str && *str;
430 }
431 
433 
450 Argument::Argument(const char *name, char abbreviation, const char *description, const char *example)
451  : m_name(name)
452  , m_abbreviation(abbreviation)
453  , m_environmentVar(nullptr)
454  , m_description(description)
455  , m_example(example)
456  , m_minOccurrences(0)
457  , m_maxOccurrences(1)
458  , m_combinable(false)
459  , m_denotesOperation(false)
460  , m_requiredValueCount(0)
461  , m_implicit(false)
462  , m_isMainArg(false)
465  , m_preDefinedCompletionValues(nullptr)
466 {
467 }
468 
473 {
474 }
475 
483 const char *Argument::firstValue() const
484 {
485  if (!m_occurrences.empty() && !m_occurrences.front().values.empty()) {
486  return m_occurrences.front().values.front();
487  } else if (m_environmentVar) {
488  return getenv(m_environmentVar);
489  } else {
490  return nullptr;
491  }
492 }
493 
497 void Argument::printInfo(ostream &os, unsigned char indentation) const
498 {
499  Indentation ident(indentation);
500  os << ident;
502  if (notEmpty(name())) {
503  if (!denotesOperation()) {
504  os << '-' << '-';
505  }
506  os << name();
507  }
508  if (notEmpty(name()) && abbreviation()) {
509  os << ',' << ' ';
510  }
511  if (abbreviation()) {
512  os << '-' << abbreviation();
513  }
515  if (requiredValueCount()) {
516  unsigned int valueNamesPrint = 0;
517  for (auto i = valueNames().cbegin(), end = valueNames().cend(); i != end && valueNamesPrint < requiredValueCount(); ++i) {
518  os << ' ' << '[' << *i << ']';
519  ++valueNamesPrint;
520  }
522  os << " ...";
523  } else {
524  for (; valueNamesPrint < requiredValueCount(); ++valueNamesPrint) {
525  os << " [value " << (valueNamesPrint + 1) << ']';
526  }
527  }
528  }
529  ident.level += 2;
530  if (notEmpty(description())) {
531  os << '\n' << ident << Wrapper(description(), ident);
532  }
533  if (isRequired()) {
534  os << '\n' << ident << "particularities: mandatory";
535  if (!isMainArgument()) {
536  os << " if parent argument is present";
537  }
538  }
539  if (environmentVariable()) {
540  os << '\n' << ident << "default environment variable: " << Wrapper(environmentVariable(), ident + 30);
541  }
542  os << '\n';
543  for (const auto *arg : subArguments()) {
544  arg->printInfo(os, ident.level);
545  }
546  if (notEmpty(example())) {
547  if (ident.level == 2 && !subArguments().empty()) {
548  os << '\n';
549  }
550  os << ident << "example: " << Wrapper(example(), ident + 9);
551  os << '\n';
552  }
553 }
554 
561 {
562  for (Argument *arg : args) {
563  if (arg != except && arg->isPresent() && !arg->isCombinable()) {
564  return arg;
565  }
566  }
567  return nullptr;
568 }
569 
584 void Argument::setSubArguments(const ArgumentInitializerList &secondaryArguments)
585 {
586  // remove this argument from the parents list of the previous secondary arguments
587  for (Argument *arg : m_subArgs) {
588  arg->m_parents.erase(remove(arg->m_parents.begin(), arg->m_parents.end(), this), arg->m_parents.end());
589  }
590  // assign secondary arguments
591  m_subArgs.assign(secondaryArguments);
592  // add this argument to the parents list of the assigned secondary arguments
593  // and set the parser
594  for (Argument *arg : m_subArgs) {
595  if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
596  arg->m_parents.push_back(this);
597  }
598  }
599 }
600 
609 {
610  if (find(m_subArgs.cbegin(), m_subArgs.cend(), arg) == m_subArgs.cend()) {
611  m_subArgs.push_back(arg);
612  if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
613  arg->m_parents.push_back(this);
614  }
615  }
616 }
617 
623 {
624  if (isMainArgument()) {
625  return true;
626  }
627  for (const Argument *parent : m_parents) {
628  if (parent->isPresent()) {
629  return true;
630  }
631  }
632  return false;
633 }
634 
644 {
645  return isPresent() ? wouldConflictWithArgument() : nullptr;
646 }
647 
657 {
658  if (!isCombinable()) {
659  for (Argument *parent : m_parents) {
660  for (Argument *sibling : parent->subArguments()) {
661  if (sibling != this && sibling->isPresent() && !sibling->isCombinable()) {
662  return sibling;
663  }
664  }
665  }
666  }
667  return nullptr;
668 }
669 
675 {
676  for (Argument *arg : m_subArgs) {
677  if (arg->denotesOperation() && arg->isPresent()) {
678  return arg;
679  }
680  }
681  return nullptr;
682 }
683 
689 {
690  for (Argument *arg : m_subArgs) {
691  arg->resetRecursively();
692  }
693  reset();
694 }
695 
713  : m_actualArgc(0)
714  , m_executable(nullptr)
715  , m_unknownArgBehavior(UnknownArgumentBehavior::Fail)
716  , m_defaultArg(nullptr)
717 {
718 }
719 
730 {
731  if (mainArguments.size()) {
732  for (Argument *arg : mainArguments) {
733  arg->m_isMainArg = true;
734  }
735  m_mainArgs.assign(mainArguments);
736  if (!m_defaultArg) {
737  if (!(*mainArguments.begin())->requiredValueCount()) {
738  bool subArgsRequired = false;
739  for (const Argument *subArg : (*mainArguments.begin())->subArguments()) {
740  if (subArg->isRequired()) {
741  subArgsRequired = true;
742  break;
743  }
744  }
745  if (!subArgsRequired) {
746  m_defaultArg = *mainArguments.begin();
747  }
748  }
749  }
750  } else {
751  m_mainArgs.clear();
752  }
753 }
754 
762 {
763  argument->m_isMainArg = true;
764  m_mainArgs.push_back(argument);
765 }
766 
770 void ArgumentParser::printHelp(ostream &os) const
771 {
774  os << applicationName;
776  os << ',' << ' ';
777  }
778  }
780  os << "version " << applicationVersion;
781  }
782  if (dependencyVersions2.size()) {
784  os << '\n';
786  }
787  auto i = dependencyVersions2.begin(), end = dependencyVersions2.end();
788  os << "Linked against: " << *i;
789  for (++i; i != end; ++i) {
790  os << ',' << ' ' << *i;
791  }
792  }
794  os << '\n' << '\n';
795  }
797  if (!m_mainArgs.empty()) {
798  bool hasOperations = false;
799  for (const Argument *arg : m_mainArgs) {
800  if (arg->denotesOperation()) {
801  hasOperations = true;
802  break;
803  }
804  }
805 
806  // check whether operations are available
807  if (hasOperations) {
808  // split top-level operations and other configurations
809  os << "Available operations:";
810  for (const Argument *arg : m_mainArgs) {
811  if (arg->denotesOperation() && strcmp(arg->name(), "help")) {
812  os << '\n';
813  arg->printInfo(os);
814  }
815  }
816  os << "\nAvailable top-level options:";
817  for (const Argument *arg : m_mainArgs) {
818  if (!arg->denotesOperation() && strcmp(arg->name(), "help")) {
819  os << '\n';
820  arg->printInfo(os);
821  }
822  }
823  } else {
824  // just show all args if no operations are available
825  os << "Available arguments:";
826  for (const Argument *arg : m_mainArgs) {
827  if (strcmp(arg->name(), "help")) {
828  os << '\n';
829  arg->printInfo(os);
830  }
831  }
832  }
833  }
834  if (applicationUrl && *applicationUrl) {
835  os << "\nProject website: " << applicationUrl << endl;
836  }
837 }
838 
850 void ArgumentParser::parseArgs(int argc, const char *const *argv)
851 {
853 }
854 
864 void ArgumentParser::parseArgsOrExit(int argc, const char *const *argv)
865 {
866  parseArgsExt(argc, argv);
867 }
868 
886 void ArgumentParser::parseArgsExt(int argc, const char *const *argv, ParseArgumentBehavior behavior)
887 {
888  try {
889  readArgs(argc, argv);
890  if (!argc) {
891  return;
892  }
894  checkConstraints(m_mainArgs);
895  }
897  invokeCallbacks(m_mainArgs);
898  }
899  } catch (const Failure &failure) {
900  if (behavior & ParseArgumentBehavior::ExitOnFailure) {
902  cerr << failure;
903  exit(1);
904  }
905  throw;
906  }
907 }
908 
922 void ArgumentParser::readArgs(int argc, const char *const *argv)
923 {
924  IF_DEBUG_BUILD(verifyArgs(m_mainArgs);)
925  m_actualArgc = 0;
926 
927  // the first argument is the executable name
928  if (!argc) {
929  m_executable = nullptr;
930  return;
931  }
932  m_executable = *argv;
933 
934  // check for further arguments
935  if (!--argc) {
936  // no arguments specified -> flag default argument as present if one is assigned
937  if (m_defaultArg) {
938  m_defaultArg->m_occurrences.emplace_back(0);
939  }
940  return;
941  }
942 
943  // check for completion mode: if first arg (after executable name) is "--bash-completion-for", bash completion for the following arguments is requested
944  const bool completionMode = !strcmp(*++argv, "--bash-completion-for");
945 
946  // determine the index of the current word for completion and the number of arguments to be passed to ArgumentReader
947  unsigned int currentWordIndex, argcForReader;
948  if (completionMode) {
949  // the first argument after "--bash-completion-for" is the index of the current word
950  try {
951  currentWordIndex = (--argc ? stringToNumber<unsigned int, string>(*(++argv)) : 0);
952  if (argc) {
953  ++argv, --argc;
954  }
955  } catch (const ConversionException &) {
956  currentWordIndex = static_cast<unsigned int>(argc - 1);
957  }
958  argcForReader = min(static_cast<unsigned int>(argc), currentWordIndex + 1);
959  } else {
960  argcForReader = static_cast<unsigned int>(argc);
961  }
962 
963  // read specified arguments
964  ArgumentReader reader(*this, argv, argv + argcForReader, completionMode);
965  const bool allArgsProcessed(reader.read());
967 
968  // fail when not all arguments could be processed, except when in completion mode
969  if (!completionMode && !allArgsProcessed) {
970  const auto suggestions(findSuggestions(argc, argv, static_cast<unsigned int>(argc - 1), reader));
971  throw Failure(argsToString("The specified argument \"", *reader.argv, "\" is unknown.", suggestions));
972  }
973 
974  // print Bash completion and prevent the applicaton to continue with the regular execution
975  if (completionMode) {
976  printBashCompletion(argc, argv, currentWordIndex, reader);
977  exitFunction(0);
978  }
979 }
980 
986 {
987  for (Argument *arg : m_mainArgs) {
988  arg->resetRecursively();
989  }
990  m_actualArgc = 0;
991 }
992 
999 {
1000  for (Argument *arg : m_mainArgs) {
1001  if (arg->denotesOperation() && arg->isPresent()) {
1002  return arg;
1003  }
1004  }
1005  return nullptr;
1006 }
1007 
1012 {
1013  for (const Argument *arg : m_mainArgs) {
1014  if (!arg->isCombinable() && arg->isPresent()) {
1015  return true;
1016  }
1017  }
1018  return false;
1019 }
1020 
1021 #ifdef DEBUG_BUILD
1022 
1036 void ApplicationUtilities::ArgumentParser::verifyArgs(const ArgumentVector &args)
1037 {
1038  vector<const Argument *> verifiedArgs;
1039  verifiedArgs.reserve(args.size());
1040  vector<char> abbreviations;
1041  abbreviations.reserve(abbreviations.size() + args.size());
1042  vector<const char *> names;
1043  names.reserve(names.size() + args.size());
1044  bool hasImplicit = false;
1045  for (const Argument *arg : args) {
1046  assert(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) == verifiedArgs.cend());
1047  verifiedArgs.push_back(arg);
1048  assert(!arg->isImplicit() || !hasImplicit);
1049  hasImplicit |= arg->isImplicit();
1050  assert(!arg->abbreviation() || find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) == abbreviations.cend());
1051  abbreviations.push_back(arg->abbreviation());
1052  assert(!arg->name() || find_if(names.cbegin(), names.cend(), [arg](const char *name) { return !strcmp(arg->name(), name); }) == names.cend());
1053  assert(arg->requiredValueCount() == 0 || arg->subArguments().size() == 0);
1054  names.emplace_back(arg->name());
1055  }
1056  for (const Argument *arg : args) {
1057  verifyArgs(arg->subArguments());
1058  }
1059 }
1060 #endif
1061 
1069 bool compareArgs(const Argument *arg1, const Argument *arg2)
1070 {
1071  if (arg1->denotesOperation() && !arg2->denotesOperation()) {
1072  return true;
1073  } else if (!arg1->denotesOperation() && arg2->denotesOperation()) {
1074  return false;
1075  } else {
1076  return strcmp(arg1->name(), arg2->name()) < 0;
1077  }
1078 }
1079 
1084 void insertSiblings(const ArgumentVector &siblings, list<const Argument *> &target)
1085 {
1086  bool onlyCombinable = false;
1087  for (const Argument *sibling : siblings) {
1088  if (sibling->isPresent() && !sibling->isCombinable()) {
1089  onlyCombinable = true;
1090  break;
1091  }
1092  }
1093  for (const Argument *sibling : siblings) {
1094  if ((!onlyCombinable || sibling->isCombinable()) && sibling->occurrences() < sibling->maxOccurrences()) {
1095  target.push_back(sibling);
1096  }
1097  }
1098 }
1099 
1103 ArgumentCompletionInfo ArgumentParser::determineCompletionInfo(
1104  int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader) const
1105 {
1106  ArgumentCompletionInfo completion(reader);
1107 
1108  // determine last detected arg
1109  if (completion.lastDetectedArg) {
1110  completion.lastDetectedArgIndex = reader.lastArgDenotation - argv;
1111  completion.lastDetectedArgPath = completion.lastDetectedArg->path(completion.lastDetectedArg->occurrences() - 1);
1112  }
1113 
1114  // determine last arg, omitting trailing empty args
1115  if (argc) {
1116  completion.lastSpecifiedArgIndex = static_cast<unsigned int>(argc) - 1;
1117  completion.lastSpecifiedArg = argv + completion.lastSpecifiedArgIndex;
1118  for (; completion.lastSpecifiedArg >= argv && **completion.lastSpecifiedArg == '\0';
1119  --completion.lastSpecifiedArg, --completion.lastSpecifiedArgIndex)
1120  ;
1121  }
1122 
1123  // just return main arguments if no args detected
1124  if (!completion.lastDetectedArg || !completion.lastDetectedArg->isPresent()) {
1125  completion.nextArgumentOrValue = true;
1126  insertSiblings(m_mainArgs, completion.relevantArgs);
1127  completion.relevantArgs.sort(compareArgs);
1128  return completion;
1129  }
1130 
1131  completion.nextArgumentOrValue = currentWordIndex > completion.lastDetectedArgIndex;
1132  if (!completion.nextArgumentOrValue) {
1133  // since the argument could be detected (hopefully unambiguously?) just return it for "final completion"
1134  completion.relevantArgs.push_back(completion.lastDetectedArg);
1135  completion.relevantArgs.sort(compareArgs);
1136  return completion;
1137  }
1138 
1139  // define function to add parameter values of argument as possible completions
1140  const auto addValueCompletionsForArg = [&completion](const Argument *arg) {
1141  if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::PreDefinedValues) {
1142  completion.relevantPreDefinedValues.push_back(arg);
1143  }
1144  if (!(arg->valueCompletionBehaviour() & ValueCompletionBehavior::FileSystemIfNoPreDefinedValues) || !arg->preDefinedCompletionValues()) {
1145  completion.completeFiles = completion.completeFiles || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Files;
1146  completion.completeDirs = completion.completeDirs || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Directories;
1147  }
1148  };
1149 
1150  // detect number of specified values
1151  auto currentValueCount = completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size();
1152  // ignore values which are specified after the current word
1153  if (currentValueCount) {
1154  const auto currentWordIndexRelativeToLastDetectedArg = currentWordIndex - completion.lastDetectedArgIndex;
1155  if (currentValueCount > currentWordIndexRelativeToLastDetectedArg) {
1156  currentValueCount -= currentWordIndexRelativeToLastDetectedArg;
1157  } else {
1158  currentValueCount = 0;
1159  }
1160  }
1161 
1162  // add value completions for implicit child if there are no value specified and there are no values required by the
1163  // last detected argument itself
1164  if (!currentValueCount && !completion.lastDetectedArg->requiredValueCount()) {
1165  for (const Argument *child : completion.lastDetectedArg->subArguments()) {
1166  if (child->isImplicit() && child->requiredValueCount()) {
1167  addValueCompletionsForArg(child);
1168  break;
1169  }
1170  }
1171  }
1172 
1173  // add value completions for last argument if there are further values required
1174  if (completion.lastDetectedArg->requiredValueCount() == Argument::varValueCount
1175  || (currentValueCount < completion.lastDetectedArg->requiredValueCount())) {
1176  addValueCompletionsForArg(completion.lastDetectedArg);
1177  }
1178 
1179  if (completion.lastDetectedArg->requiredValueCount() == Argument::varValueCount
1180  || completion.lastDetectedArg->values(completion.lastDetectedArg->occurrences() - 1).size()
1181  >= completion.lastDetectedArg->requiredValueCount()) {
1182  // sub arguments of the last arg are possible completions
1183  for (const Argument *subArg : completion.lastDetectedArg->subArguments()) {
1184  if (subArg->occurrences() < subArg->maxOccurrences()) {
1185  completion.relevantArgs.push_back(subArg);
1186  }
1187  }
1188 
1189  // siblings of parents are possible completions as well
1190  for (auto parentArgument = completion.lastDetectedArgPath.crbegin(), end = completion.lastDetectedArgPath.crend();; ++parentArgument) {
1191  insertSiblings(parentArgument != end ? (*parentArgument)->subArguments() : m_mainArgs, completion.relevantArgs);
1192  if (parentArgument == end) {
1193  break;
1194  }
1195  }
1196  }
1197 
1198  return completion;
1199 }
1200 
1204 string ArgumentParser::findSuggestions(int argc, const char *const *argv, unsigned int cursorPos, const ArgumentReader &reader) const
1205 {
1206  // determine completion info
1207  const auto completionInfo(determineCompletionInfo(argc, argv, cursorPos, reader));
1208 
1209  // determine the unknown/misspelled argument
1210  const auto *unknownArg(*reader.argv);
1211  auto unknownArgSize(strlen(unknownArg));
1212  // -> refuse suggestions for long args to prevent huge memory allocation for Damerau-Levenshtein algo
1213  if (unknownArgSize > 16) {
1214  return string();
1215  }
1216  // -> remove dashes since argument names internally don't have them
1217  if (unknownArgSize >= 2 && unknownArg[0] == '-' && unknownArg[1] == '-') {
1218  unknownArg += 2;
1219  unknownArgSize -= 2;
1220  }
1221 
1222  // find best suggestions limiting the results to 2
1223  multiset<ArgumentSuggestion> bestSuggestions;
1224  // -> consider relevant arguments
1225  for (const Argument *const arg : completionInfo.relevantArgs) {
1226  ArgumentSuggestion(unknownArg, unknownArgSize, arg->name(), !arg->denotesOperation()).addTo(bestSuggestions, 2);
1227  }
1228  // -> consider relevant values
1229  for (const Argument *const arg : completionInfo.relevantPreDefinedValues) {
1230  for (const char *i = arg->preDefinedCompletionValues(); *i; ++i) {
1231  const char *const wordStart(i);
1232  const char *wordEnd(wordStart + 1);
1233  for (; *wordEnd && *wordEnd != ' '; ++wordEnd)
1234  ;
1235  ArgumentSuggestion(unknownArg, unknownArgSize, wordStart, static_cast<size_t>(wordEnd - wordStart), false).addTo(bestSuggestions, 2);
1236  i = wordEnd;
1237  }
1238  }
1239 
1240  // format suggestion
1241  string suggestionStr;
1242  if (const auto suggestionCount = bestSuggestions.size()) {
1243  // allocate memory
1244  size_t requiredSize = 15;
1245  for (const auto &suggestion : bestSuggestions) {
1246  requiredSize += suggestion.suggestionSize + 2;
1247  if (suggestion.hasDashPrefix) {
1248  requiredSize += 2;
1249  }
1250  }
1251  suggestionStr.reserve(requiredSize);
1252 
1253  // add each suggestion to end up with something like "Did you mean status (1), pause (3), cat (4), edit (5) or rescan-all (8)?"
1254  suggestionStr += "\nDid you mean ";
1255  size_t i = 0;
1256  for (const auto &suggestion : bestSuggestions) {
1257  if (++i == suggestionCount && suggestionCount != 1) {
1258  suggestionStr += " or ";
1259  } else if (i > 1) {
1260  suggestionStr += ", ";
1261  }
1262  if (suggestion.hasDashPrefix) {
1263  suggestionStr += "--";
1264  }
1265  suggestionStr.append(suggestion.suggestion, suggestion.suggestionSize);
1266  }
1267  suggestionStr += '?';
1268  }
1269  return suggestionStr;
1270 }
1271 
1277 void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader) const
1278 {
1279  // determine completion info and sort relevant arguments
1280  const auto completionInfo([&] {
1281  auto clutteredCompletionInfo(determineCompletionInfo(argc, argv, currentWordIndex, reader));
1282  clutteredCompletionInfo.relevantArgs.sort(compareArgs);
1283  return clutteredCompletionInfo;
1284  }());
1285 
1286  // read the "opening" (started but not finished argument denotation)
1287  const char *opening = nullptr;
1288  string compoundOpening;
1289  size_t openingLen = 0, compoundOpeningStartLen = 0;
1290  unsigned char openingDenotationType = Value;
1291  if (argc && completionInfo.nextArgumentOrValue) {
1292  if (currentWordIndex < static_cast<unsigned int>(argc)) {
1293  opening = argv[currentWordIndex];
1294  // For some reason completions for eg. "set --values disk=1 tag=a" are splitted so the
1295  // equation sign is an own argument ("set --values disk = 1 tag = a").
1296  // This is not how values are treated by the argument parser. Hence the opening
1297  // must be joined again. In this case only the part after the equation sign needs to be
1298  // provided for completion so compoundOpeningStartLen is set to number of characters to skip.
1299  const size_t minCurrentWordIndex = (completionInfo.lastDetectedArg ? completionInfo.lastDetectedArgIndex : 0);
1300  if (currentWordIndex > minCurrentWordIndex && !strcmp(opening, "=")) {
1301  compoundOpening.reserve(compoundOpeningStartLen = strlen(argv[--currentWordIndex]) + 1);
1302  compoundOpening = argv[currentWordIndex];
1303  compoundOpening += '=';
1304  } else if (currentWordIndex > (minCurrentWordIndex + 1) && !strcmp(argv[currentWordIndex - 1], "=")) {
1305  compoundOpening.reserve((compoundOpeningStartLen = strlen(argv[currentWordIndex -= 2]) + 1) + strlen(opening));
1306  compoundOpening = argv[currentWordIndex];
1307  compoundOpening += '=';
1308  compoundOpening += opening;
1309  }
1310  if (!compoundOpening.empty()) {
1311  opening = compoundOpening.data();
1312  }
1313  } else {
1314  opening = *completionInfo.lastSpecifiedArg;
1315  }
1316  *opening == '-' && (++opening, ++openingDenotationType) && *opening == '-' && (++opening, ++openingDenotationType);
1317  openingLen = strlen(opening);
1318  }
1319 
1320  // print "COMPREPLY" bash array
1321  cout << "COMPREPLY=(";
1322  // -> completions for parameter values
1323  bool noWhitespace = false;
1324  for (const Argument *const arg : completionInfo.relevantPreDefinedValues) {
1325  if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::InvokeCallback && arg->m_callbackFunction) {
1326  arg->m_callbackFunction(arg->isPresent() ? arg->m_occurrences.front() : ArgumentOccurrence(Argument::varValueCount));
1327  }
1328  if (!arg->preDefinedCompletionValues()) {
1329  continue;
1330  }
1331  const bool appendEquationSign = arg->valueCompletionBehaviour() & ValueCompletionBehavior::AppendEquationSign;
1332  if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1333  if (openingDenotationType != Value) {
1334  continue;
1335  }
1336  bool wordStart = true, ok = false, equationSignAlreadyPresent = false;
1337  size_t wordIndex = 0;
1338  for (const char *i = arg->preDefinedCompletionValues(), *end = opening + openingLen; *i;) {
1339  if (wordStart) {
1340  const char *i1 = i, *i2 = opening;
1341  for (; *i1 && i2 != end && *i1 == *i2; ++i1, ++i2)
1342  ;
1343  if ((ok = (i2 == end))) {
1344  cout << '\'';
1345  }
1346  wordStart = false;
1347  wordIndex = 0;
1348  } else if ((wordStart = (*i == ' ') || (*i == '\n'))) {
1349  equationSignAlreadyPresent = false;
1350  if (ok) {
1351  cout << '\'' << ' ';
1352  }
1353  ++i;
1354  continue;
1355  } else if (*i == '=') {
1356  equationSignAlreadyPresent = true;
1357  }
1358  if (!ok) {
1359  ++i;
1360  continue;
1361  }
1362  if (!compoundOpeningStartLen || wordIndex >= compoundOpeningStartLen) {
1363  if (*i == '\'') {
1364  cout << "'\"'\"'";
1365  } else {
1366  cout << *i;
1367  }
1368  }
1369  ++i, ++wordIndex;
1370  switch (*i) {
1371  case ' ':
1372  case '\n':
1373  case '\0':
1374  if (appendEquationSign && !equationSignAlreadyPresent) {
1375  cout << '=';
1376  noWhitespace = true;
1377  equationSignAlreadyPresent = false;
1378  }
1379  if (*i == '\0') {
1380  cout << '\'';
1381  }
1382  }
1383  }
1384  cout << ' ';
1385  } else if (const char *i = arg->preDefinedCompletionValues()) {
1386  bool equationSignAlreadyPresent = false;
1387  cout << '\'';
1388  while (*i) {
1389  if (*i == '\'') {
1390  cout << "'\"'\"'";
1391  } else {
1392  cout << *i;
1393  }
1394  switch (*(++i)) {
1395  case '=':
1396  equationSignAlreadyPresent = true;
1397  break;
1398  case ' ':
1399  case '\n':
1400  case '\0':
1401  if (appendEquationSign && !equationSignAlreadyPresent) {
1402  cout << '=';
1403  equationSignAlreadyPresent = false;
1404  }
1405  if (*i != '\0') {
1406  cout << '\'';
1407  if (*(++i)) {
1408  cout << ' ' << '\'';
1409  }
1410  }
1411  }
1412  }
1413  cout << '\'' << ' ';
1414  }
1415  }
1416  // -> completions for further arguments
1417  for (const Argument *const arg : completionInfo.relevantArgs) {
1418  if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1419  switch (openingDenotationType) {
1420  case Value:
1421  if (!arg->denotesOperation() || strncmp(arg->name(), opening, openingLen)) {
1422  continue;
1423  }
1424  break;
1425  case Abbreviation:
1426  break;
1427  case FullName:
1428  if (strncmp(arg->name(), opening, openingLen)) {
1429  continue;
1430  }
1431  }
1432  }
1433 
1434  if (opening && openingDenotationType == Abbreviation && !completionInfo.nextArgumentOrValue) {
1435  // TODO: add test for this case
1436  cout << '\'' << '-' << opening << arg->abbreviation() << '\'' << ' ';
1437  } else if (completionInfo.lastDetectedArg && reader.argDenotationType == Abbreviation && !completionInfo.nextArgumentOrValue) {
1438  if (reader.argv == reader.end) {
1439  cout << '\'' << *(reader.argv - 1) << '\'' << ' ';
1440  }
1441  } else if (arg->denotesOperation()) {
1442  cout << '\'' << arg->name() << '\'' << ' ';
1443  } else {
1444  cout << '\'' << '-' << '-' << arg->name() << '\'' << ' ';
1445  }
1446  }
1447  // -> completions for files and dirs
1448  // -> if there's already an "opening", determine the dir part and the file part
1449  string actualDir, actualFile;
1450  bool haveFileOrDirCompletions = false;
1451  if (argc && currentWordIndex == completionInfo.lastSpecifiedArgIndex && opening) {
1452  // the "opening" might contain escaped characters which need to be unescaped first (let's hope this covers all possible escapings)
1453  string unescapedOpening(opening);
1454  findAndReplace<string>(unescapedOpening, "\\ ", " ");
1455  findAndReplace<string>(unescapedOpening, "\\,", ",");
1456  findAndReplace<string>(unescapedOpening, "\\[", "[");
1457  findAndReplace<string>(unescapedOpening, "\\]", "]");
1458  findAndReplace<string>(unescapedOpening, "\\!", "!");
1459  findAndReplace<string>(unescapedOpening, "\\#", "#");
1460  findAndReplace<string>(unescapedOpening, "\\$", "$");
1461  findAndReplace<string>(unescapedOpening, "\\'", "'");
1462  findAndReplace<string>(unescapedOpening, "\\\"", "\"");
1463  findAndReplace<string>(unescapedOpening, "\\\\", "\\");
1464  // determine the "directory" part
1465  string dir = directory(unescapedOpening);
1466  if (dir.empty()) {
1467  actualDir = ".";
1468  } else {
1469  if (dir[0] == '\"' || dir[0] == '\'') {
1470  dir.erase(0, 1);
1471  }
1472  if (dir.size() > 1 && (dir[dir.size() - 2] == '\"' || dir[dir.size() - 2] == '\'')) {
1473  dir.erase(dir.size() - 2, 1);
1474  }
1475  actualDir = move(dir);
1476  }
1477  // determine the "file" part
1478  string file = fileName(unescapedOpening);
1479  if (file[0] == '\"' || file[0] == '\'') {
1480  file.erase(0, 1);
1481  }
1482  if (file.size() > 1 && (file[dir.size() - 2] == '\"' || dir[file.size() - 2] == '\'')) {
1483  file.erase(file.size() - 2, 1);
1484  }
1485  actualFile = move(file);
1486  }
1487 
1488  // -> completion for files and dirs
1490  if (completionInfo.completeFiles) {
1491  entryTypes |= DirectoryEntryType::File;
1492  }
1493  if (completionInfo.completeDirs) {
1494  entryTypes |= DirectoryEntryType::Directory;
1495  }
1496  if (entryTypes != DirectoryEntryType::None) {
1497  const string replace("'"), with("'\"'\"'");
1498  if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1499  list<string> entries = directoryEntries(actualDir.c_str(), entryTypes);
1500  findAndReplace(actualDir, replace, with);
1501  for (string &dirEntry : entries) {
1502  if (!startsWith(dirEntry, actualFile)) {
1503  continue;
1504  }
1505  cout << '\'';
1506  if (actualDir != ".") {
1507  cout << actualDir;
1508  }
1509  findAndReplace(dirEntry, replace, with);
1510  cout << dirEntry << '\'' << ' ';
1511  haveFileOrDirCompletions = true;
1512  }
1513  } else {
1514  for (string &dirEntry : directoryEntries(".", entryTypes)) {
1515  findAndReplace(dirEntry, replace, with);
1516  cout << '\'' << dirEntry << '\'' << ' ';
1517  haveFileOrDirCompletions = true;
1518  }
1519  }
1520  }
1521  cout << ')';
1522 
1523  // ensure file or dir completions are formatted appropriately
1524  if (haveFileOrDirCompletions) {
1525  cout << "; compopt -o filenames";
1526  }
1527 
1528  // ensure trailing whitespace is ommitted
1529  if (noWhitespace) {
1530  cout << "; compopt -o nospace";
1531  }
1532 
1533  cout << endl;
1534 }
1535 
1541 {
1542  for (const Argument *arg : args) {
1543  const auto occurrences = arg->occurrences();
1544  if (arg->isParentPresent() && occurrences > arg->maxOccurrences()) {
1545  throw Failure(argsToString("The argument \"", arg->name(), "\" mustn't be specified more than ", arg->maxOccurrences(),
1546  (arg->maxOccurrences() == 1 ? " time." : " times.")));
1547  }
1548  if (arg->isParentPresent() && occurrences < arg->minOccurrences()) {
1549  throw Failure(argsToString("The argument \"", arg->name(), "\" must be specified at least ", arg->minOccurrences(),
1550  (arg->minOccurrences() == 1 ? " time." : " times.")));
1551  }
1552  Argument *conflictingArgument = nullptr;
1553  if (arg->isMainArgument()) {
1554  if (!arg->isCombinable() && arg->isPresent()) {
1555  conflictingArgument = firstPresentUncombinableArg(m_mainArgs, arg);
1556  }
1557  } else {
1558  conflictingArgument = arg->conflictsWithArgument();
1559  }
1560  if (conflictingArgument) {
1561  throw Failure(argsToString("The argument \"", conflictingArgument->name(), "\" can not be combined with \"", arg->name(), "\"."));
1562  }
1563  for (size_t i = 0; i != occurrences; ++i) {
1564  if (!arg->allRequiredValuesPresent(i)) {
1565  stringstream ss(stringstream::in | stringstream::out);
1566  ss << "Not all parameter for argument \"" << arg->name() << "\" ";
1567  if (i) {
1568  ss << " (" << (i + 1) << " occurrence) ";
1569  }
1570  ss << "provided. You have to provide the following parameter:";
1571  size_t valueNamesPrint = 0;
1572  for (const auto &name : arg->m_valueNames) {
1573  ss << ' ' << name, ++valueNamesPrint;
1574  }
1575  if (arg->m_requiredValueCount != Argument::varValueCount) {
1576  while (valueNamesPrint < arg->m_requiredValueCount) {
1577  ss << "\nvalue " << (++valueNamesPrint);
1578  }
1579  }
1580  throw Failure(ss.str());
1581  }
1582  }
1583 
1584  // check contraints of sub arguments recursively
1585  checkConstraints(arg->m_subArgs);
1586  }
1587 }
1588 
1597 {
1598  for (const Argument *arg : args) {
1599  // invoke the callback for each occurrence of the argument
1600  if (arg->m_callbackFunction) {
1601  for (const auto &occurrence : arg->m_occurrences) {
1602  arg->m_callbackFunction(occurrence);
1603  }
1604  }
1605  // invoke the callbacks for sub arguments recursively
1606  invokeCallbacks(arg->m_subArgs);
1607  }
1608 }
1609 
1620  : Argument("help", 'h', "shows this information")
1621 {
1622  setCallback([&parser](const ArgumentOccurrence &) {
1624  parser.printHelp(cout);
1625  });
1626 }
1627 
1658 NoColorArgument *NoColorArgument::s_instance = nullptr;
1659 
1665 #ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1666  : Argument("no-color", '\0', "disables formatted/colorized output")
1667 #else
1668  : Argument("enable-color", '\0', "enables formatted/colorized output")
1669 #endif
1670 {
1671  setCombinable(true);
1672 
1673  if (s_instance) {
1674  return;
1675  }
1676  s_instance = this;
1677 
1678  // set the environmentvariable: note that this is not directly used and just assigned for printing help
1679  setEnvironmentVariable("ENABLE_ESCAPE_CODES");
1680 
1681  // default-initialize EscapeCodes::enabled from environment variable
1682  const char *envValue = getenv(environmentVariable());
1683  if (!envValue) {
1684  return;
1685  }
1686  for (; *envValue; ++envValue) {
1687  switch (*envValue) {
1688  case '0':
1689  case ' ':
1690  break;
1691  default:
1692  // enable escape codes if ENABLE_ESCAPE_CODES contains anything else than spaces or zeros
1693  EscapeCodes::enabled = true;
1694  return;
1695  }
1696  }
1697  // disable escape codes if ENABLE_ESCAPE_CODES is empty or only contains spaces and zeros
1698  EscapeCodes::enabled = false;
1699 }
1700 
1705 {
1706  if (s_instance == this) {
1707  s_instance = nullptr;
1708  }
1709 }
1710 
1715 {
1716  if (NoColorArgument::s_instance && NoColorArgument::s_instance->isPresent()) {
1717 #ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1718  EscapeCodes::enabled = false;
1719 #else
1720  EscapeCodes::enabled = true;
1721 #endif
1722  }
1723 }
1724 
1725 } // namespace ApplicationUtilities
bool isMainArgument() const
Returns an indication whether the argument is used as main argument.
CPP_UTILITIES_EXPORT const char * applicationUrl
Specifies the URL to the application website (used by ArgumentParser::printHelp()).
bool startsWith(const StringType &str, const StringType &phrase)
Returns whether str starts with phrase.
ValueCompletionBehavior
The ValueCompletionBehavior enum specifies the items to be considered when generating completion for ...
Argument * lastArg
The last Argument instance which could be detected. Set to nullptr in the initial call...
bool denotesOperation() const
Returns whether the argument denotes an operation.
void resetArgs()
Resets all Argument instances assigned as mainArguments() and sub arguments.
Encapsulates functions for formatted terminal output using ANSI escape codes.
std::initializer_list< Argument * > ArgumentInitializerList
#define IF_DEBUG_BUILD(x)
Wraps debug-only lines conveniently.
Definition: global.h:130
std::size_t requiredValueCount() const
Returns the number of values which are required to be given for this argument.
Argument * conflictsWithArgument() const
Checks if this arguments conflicts with other arguments.
const char * description() const
Returns the description of the argument.
bool isParentPresent() const
Returns whether at least one parent argument is present.
static void apply()
Sets EscapeCodes::enabled according to the presense of the first instantiation of NoColorArgument...
unsigned char argDenotationType
The type of the currently processed abbreviation denotation. Unspecified if argDenotation is not set...
ArgumentParser()
Constructs a new ArgumentParser.
void readArgs(int argc, const char *const *argv)
Parses the specified command line arguments.
void setStyle(std::ostream &stream, TextAttribute displayAttribute=TextAttribute::Reset)
CPP_UTILITIES_EXPORT std::list< std::string > directoryEntries(const char *path, DirectoryEntryType types=DirectoryEntryType::All)
Returns the names of the directory entries in the specified path with the specified types...
Definition: path.cpp:181
The ConversionException class is thrown by the various conversion functions of this library when a co...
Contains currently only ArgumentParser and related classes.
constexpr StringType argsToString(Args &&... args)
void setMainArguments(const ArgumentInitializerList &mainArguments)
Sets the main arguments for the parser.
Argument(const char *name, char abbreviation='\0', const char *description=nullptr, const char *example=nullptr)
Constructs an Argument with the given name, abbreviation and description.
STL namespace.
bool isRequired() const
Returns an indication whether the argument is mandatory.
Argument CPP_UTILITIES_EXPORT * firstPresentUncombinableArg(const ArgumentVector &args, const Argument *except)
This function return the first present and uncombinable argument of the given list of arguments...
The Wrapper class is internally used print text which might needs to be wrapped preserving the indent...
NoColorArgument()
Constructs a new NoColorArgument argument.
void parseArgs(int argc, const char *const *argv)
Parses the specified command line arguments.
const char * firstValue() const
Returns the first parameter value of the first occurrence of the argument.
CPP_UTILITIES_EXPORT const char * applicationVersion
Specifies the version of the application (used by ArgumentParser::printHelp()).
void invokeCallbacks()
Invokes all assigned callbacks.
const char *const * argv
Points to the first argument denotation and will be incremented when a denotation has been processed...
~Argument()
Destroys the Argument.
The Indentation class allows printing indentation conveniently, eg.
ArgumentReader(ArgumentParser &parser, const char *const *argv, const char *const *end, bool completionMode=false)
Initializes the internal reader for the specified parser and arguments.
#define CMD_UTILS_START_CONSOLE
The NoColorArgument class allows to specify whether use of escape codes or similar technique to provi...
void addMainArgument(Argument *argument)
Adds the specified argument to the main argument.
void parseArgsOrExit(int argc, const char *const *argv)
Parses the specified command line arguments.
#define FALLTHROUGH
Prevents clang from warning about missing break in switch-case.
Definition: global.h:142
void checkConstraints()
Checks whether contraints are violated.
const char * name() const
Returns the name of the argument.
const std::vector< const char * > & valueNames() const
Returns the names of the requried values.
const char * argDenotation
The currently processed abbreviation denotation (should be substring of one of the args in argv)...
HelpArgument(ArgumentParser &parser)
Constructs a new help argument for the specified parser.
void parseArgsExt(int argc, const char *const *argv, ParseArgumentBehavior behavior=ParseArgumentBehavior::CheckConstraints|ParseArgumentBehavior::InvokeCallbacks|ParseArgumentBehavior::ExitOnFailure)
Parses the specified command line arguments.
ArgumentParser & parser
The associated ArgumentParser instance.
const ArgumentVector & subArguments() const
Returns the secondary arguments for this argument.
constexpr T max(T first, T second)
Returns the greatest of the given items.
Definition: math.h:29
size_t index
An index which is incremented when an argument is encountered (the current index is stored in the occ...
bool operator==(const ArgumentSuggestion &other) const
Contains utility classes helping to read and write streams.
Definition: binaryreader.h:10
CPP_UTILITIES_EXPORT bool enabled
Controls whether the functions inside the EscapeCodes namespace actually make use of escape codes...
bool operator<(const ArgumentSuggestion &other) const
void printHelp(std::ostream &os) const
Prints help text for all assigned arguments.
void findAndReplace(StringType &str, const StringType &find, const StringType &replace)
Replaces all occurences of find with relpace in the specified str.
CPP_UTILITIES_EXPORT std::initializer_list< const char * > dependencyVersions
Specifies the dependency versions the application was linked against (used by ArgumentParser::printHe...
Contains several functions providing conversions between different data types.
The TerminalSize struct describes a terminal size.
CPP_UTILITIES_EXPORT void(* exitFunction)(int)
Specifies a function quit the application.
TerminalSize CPP_UTILITIES_EXPORT determineTerminalSize()
Returns the current size of the terminal.
ParseArgumentBehavior
The ParseArgumentBehavior enum specifies the behavior when parsing arguments.
Argument * wouldConflictWithArgument() const
Checks if this argument would conflict with other arguments if it was present.
std::size_t occurrences() const
Returns how often the argument could be detected when parsing.
The Argument class is a wrapper for command line argument information.
void setCallback(CallbackFunction callback)
Sets a callback function which will be called by the parser if the argument could be found and no par...
const std::vector< Argument * > & path(std::size_t occurrence=0) const
Returns the path of the specified occurrence.
static constexpr std::size_t varValueCount
Denotes a variable number of values.
bool isCombinable() const
Returns an indication whether the argument is combinable.
ApplicationUtilities::ArgumentReader & reset(const char *const *argv, const char *const *end)
Resets the ArgumentReader to continue reading new argv.
The ArgumentCompletionInfo struct holds information internally used for shell completion and suggesti...
ArgumentVector & args
The Argument instances to store the results. Sub arguments of args are considered as well...
constexpr T min(T first, T second)
Returns the smallest of the given items.
Definition: math.h:17
ArgumentDenotationType
The ArgumentDenotationType enum specifies the type of a given argument denotation.
UnknownArgumentBehavior
The UnknownArgumentBehavior enum specifies the behavior of the argument parser when an unknown argume...
void printInfo(std::ostream &os, unsigned char indentation=0) const
Writes the name, the abbreviation and other information about the Argument to the give ostream...
bool read()
Reads the commands line arguments specified when constructing the object.
CPP_UTILITIES_EXPORT std::string directory(const std::string &path)
Returns the directory of the specified path string (including trailing slash).
Definition: path.cpp:52
void addSubArgument(Argument *arg)
Adds arg as a secondary argument for this argument.
CPP_UTILITIES_EXPORT std::size_t computeDamerauLevenshteinDistance(const char *str1, std::size_t size1, const char *str2, std::size_t size2)
The ArgumentOccurrence struct holds argument values for an occurrence of an argument.
const char *const * end
Points to the end of the argv array.
CPP_UTILITIES_EXPORT const char * applicationName
Specifies the name of the application (used by ArgumentParser::printHelp()).
list< const Argument * > relevantPreDefinedValues
unsigned short columns
number of columns
bool isPresent() const
Returns an indication whether the argument could be detected when parsing.
The Failure class is thrown by an ArgumentParser when a parsing error occurs.
Definition: failure.h:12
CPP_UTILITIES_EXPORT std::vector< const char * > dependencyVersions2
Specifies the dependency versions the application was linked against (used by ArgumentParser::printHe...
const ArgumentVector & mainArguments() const
Returns the main arguments.
bool isUncombinableMainArgPresent() const
Checks whether at least one uncombinable main argument is present.
char abbreviation() const
Returns the abbreviation of the argument.
const char * example() const
Returns the usage example of the argument.
ArgumentSuggestion(const char *unknownArg, size_t unknownArgSize, const char *suggestion, bool hasDashPrefix)
void reset()
Resets occurrences (indices, values and paths).
Argument * specifiedOperation() const
Returns the first operation argument specified by the user or nullptr if no operation has been specif...
The MiscUtilities namespace contains various utilities such as computing Damerau–Levenshtein distanc...
Definition: multiarray.h:8
CPP_UTILITIES_EXPORT std::string fileName(const std::string &path)
Returns the file name and extension of the specified path string.
Definition: path.cpp:32
const char *const * lastArgDenotation
Points to the element in argv where lastArg was encountered. Unspecified if lastArg is not set...
CPP_UTILITIES_EXPORT const char * applicationAuthor
Specifies the author of the application (used by ArgumentParser::printHelp()).
DirectoryEntryType
The DirectoryEntryType enum specifies the type of a directory entry (file, directory or symlink)...
Definition: path.h:26
The ArgumentReader class internally encapsulates the process of reading command line arguments...
const char * environmentVariable() const
Returns the environment variable queried when firstValue() is called.
bool completionMode
Whether completion mode is enabled. In this case reading args will be continued even if an denotation...
void setSubArguments(const ArgumentInitializerList &subArguments)
Sets the secondary arguments for this arguments.
CPP_UTILITIES_EXPORT std::ostream & operator<<(std::ostream &out, Indentation indentation)
void addTo(multiset< ArgumentSuggestion > &suggestions, size_t limit) const
bool compareArgs(const Argument *arg1, const Argument *arg2)
Returns whether arg1 should be listed before arg2 when printing completion.
The ArgumentParser class provides a means for handling command line arguments.
Argument * specifiedOperation() const
Returns the first operation argument specified by the user or nullptr if no operation has been specif...
void resetRecursively()
Resets this argument and all sub arguments recursively.
void insertSiblings(const ArgumentVector &siblings, list< const Argument *> &target)
Inserts the specified siblings in the target list.
std::vector< Argument * > ArgumentVector