C++ Utilities  4.17.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  if (!arg->preDefinedCompletionValues()) {
1231  continue;
1232  }
1233  for (const char *i = arg->preDefinedCompletionValues(); *i; ++i) {
1234  const char *const wordStart(i);
1235  const char *wordEnd(wordStart + 1);
1236  for (; *wordEnd && *wordEnd != ' '; ++wordEnd)
1237  ;
1238  ArgumentSuggestion(unknownArg, unknownArgSize, wordStart, static_cast<size_t>(wordEnd - wordStart), false).addTo(bestSuggestions, 2);
1239  i = wordEnd;
1240  }
1241  }
1242 
1243  // format suggestion
1244  string suggestionStr;
1245  if (const auto suggestionCount = bestSuggestions.size()) {
1246  // allocate memory
1247  size_t requiredSize = 15;
1248  for (const auto &suggestion : bestSuggestions) {
1249  requiredSize += suggestion.suggestionSize + 2;
1250  if (suggestion.hasDashPrefix) {
1251  requiredSize += 2;
1252  }
1253  }
1254  suggestionStr.reserve(requiredSize);
1255 
1256  // add each suggestion to end up with something like "Did you mean status (1), pause (3), cat (4), edit (5) or rescan-all (8)?"
1257  suggestionStr += "\nDid you mean ";
1258  size_t i = 0;
1259  for (const auto &suggestion : bestSuggestions) {
1260  if (++i == suggestionCount && suggestionCount != 1) {
1261  suggestionStr += " or ";
1262  } else if (i > 1) {
1263  suggestionStr += ", ";
1264  }
1265  if (suggestion.hasDashPrefix) {
1266  suggestionStr += "--";
1267  }
1268  suggestionStr.append(suggestion.suggestion, suggestion.suggestionSize);
1269  }
1270  suggestionStr += '?';
1271  }
1272  return suggestionStr;
1273 }
1274 
1280 void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader) const
1281 {
1282  // determine completion info and sort relevant arguments
1283  const auto completionInfo([&] {
1284  auto clutteredCompletionInfo(determineCompletionInfo(argc, argv, currentWordIndex, reader));
1285  clutteredCompletionInfo.relevantArgs.sort(compareArgs);
1286  return clutteredCompletionInfo;
1287  }());
1288 
1289  // read the "opening" (started but not finished argument denotation)
1290  const char *opening = nullptr;
1291  string compoundOpening;
1292  size_t openingLen = 0, compoundOpeningStartLen = 0;
1293  unsigned char openingDenotationType = Value;
1294  if (argc && completionInfo.nextArgumentOrValue) {
1295  if (currentWordIndex < static_cast<unsigned int>(argc)) {
1296  opening = argv[currentWordIndex];
1297  // For some reason completions for eg. "set --values disk=1 tag=a" are splitted so the
1298  // equation sign is an own argument ("set --values disk = 1 tag = a").
1299  // This is not how values are treated by the argument parser. Hence the opening
1300  // must be joined again. In this case only the part after the equation sign needs to be
1301  // provided for completion so compoundOpeningStartLen is set to number of characters to skip.
1302  const size_t minCurrentWordIndex = (completionInfo.lastDetectedArg ? completionInfo.lastDetectedArgIndex : 0);
1303  if (currentWordIndex > minCurrentWordIndex && !strcmp(opening, "=")) {
1304  compoundOpening.reserve(compoundOpeningStartLen = strlen(argv[--currentWordIndex]) + 1);
1305  compoundOpening = argv[currentWordIndex];
1306  compoundOpening += '=';
1307  } else if (currentWordIndex > (minCurrentWordIndex + 1) && !strcmp(argv[currentWordIndex - 1], "=")) {
1308  compoundOpening.reserve((compoundOpeningStartLen = strlen(argv[currentWordIndex -= 2]) + 1) + strlen(opening));
1309  compoundOpening = argv[currentWordIndex];
1310  compoundOpening += '=';
1311  compoundOpening += opening;
1312  }
1313  if (!compoundOpening.empty()) {
1314  opening = compoundOpening.data();
1315  }
1316  } else {
1317  opening = *completionInfo.lastSpecifiedArg;
1318  }
1319  *opening == '-' && (++opening, ++openingDenotationType) && *opening == '-' && (++opening, ++openingDenotationType);
1320  openingLen = strlen(opening);
1321  }
1322 
1323  // print "COMPREPLY" bash array
1324  cout << "COMPREPLY=(";
1325  // -> completions for parameter values
1326  bool noWhitespace = false;
1327  for (const Argument *const arg : completionInfo.relevantPreDefinedValues) {
1328  if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::InvokeCallback && arg->m_callbackFunction) {
1329  arg->m_callbackFunction(arg->isPresent() ? arg->m_occurrences.front() : ArgumentOccurrence(Argument::varValueCount));
1330  }
1331  if (!arg->preDefinedCompletionValues()) {
1332  continue;
1333  }
1334  const bool appendEquationSign = arg->valueCompletionBehaviour() & ValueCompletionBehavior::AppendEquationSign;
1335  if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1336  if (openingDenotationType != Value) {
1337  continue;
1338  }
1339  bool wordStart = true, ok = false, equationSignAlreadyPresent = false;
1340  size_t wordIndex = 0;
1341  for (const char *i = arg->preDefinedCompletionValues(), *end = opening + openingLen; *i;) {
1342  if (wordStart) {
1343  const char *i1 = i, *i2 = opening;
1344  for (; *i1 && i2 != end && *i1 == *i2; ++i1, ++i2)
1345  ;
1346  if ((ok = (i2 == end))) {
1347  cout << '\'';
1348  }
1349  wordStart = false;
1350  wordIndex = 0;
1351  } else if ((wordStart = (*i == ' ') || (*i == '\n'))) {
1352  equationSignAlreadyPresent = false;
1353  if (ok) {
1354  cout << '\'' << ' ';
1355  }
1356  ++i;
1357  continue;
1358  } else if (*i == '=') {
1359  equationSignAlreadyPresent = true;
1360  }
1361  if (!ok) {
1362  ++i;
1363  continue;
1364  }
1365  if (!compoundOpeningStartLen || wordIndex >= compoundOpeningStartLen) {
1366  if (*i == '\'') {
1367  cout << "'\"'\"'";
1368  } else {
1369  cout << *i;
1370  }
1371  }
1372  ++i, ++wordIndex;
1373  switch (*i) {
1374  case ' ':
1375  case '\n':
1376  case '\0':
1377  if (appendEquationSign && !equationSignAlreadyPresent) {
1378  cout << '=';
1379  noWhitespace = true;
1380  equationSignAlreadyPresent = false;
1381  }
1382  if (*i == '\0') {
1383  cout << '\'';
1384  }
1385  }
1386  }
1387  cout << ' ';
1388  } else if (const char *i = arg->preDefinedCompletionValues()) {
1389  bool equationSignAlreadyPresent = false;
1390  cout << '\'';
1391  while (*i) {
1392  if (*i == '\'') {
1393  cout << "'\"'\"'";
1394  } else {
1395  cout << *i;
1396  }
1397  switch (*(++i)) {
1398  case '=':
1399  equationSignAlreadyPresent = true;
1400  break;
1401  case ' ':
1402  case '\n':
1403  case '\0':
1404  if (appendEquationSign && !equationSignAlreadyPresent) {
1405  cout << '=';
1406  equationSignAlreadyPresent = false;
1407  }
1408  if (*i != '\0') {
1409  cout << '\'';
1410  if (*(++i)) {
1411  cout << ' ' << '\'';
1412  }
1413  }
1414  }
1415  }
1416  cout << '\'' << ' ';
1417  }
1418  }
1419  // -> completions for further arguments
1420  for (const Argument *const arg : completionInfo.relevantArgs) {
1421  if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1422  switch (openingDenotationType) {
1423  case Value:
1424  if (!arg->denotesOperation() || strncmp(arg->name(), opening, openingLen)) {
1425  continue;
1426  }
1427  break;
1428  case Abbreviation:
1429  break;
1430  case FullName:
1431  if (strncmp(arg->name(), opening, openingLen)) {
1432  continue;
1433  }
1434  }
1435  }
1436 
1437  if (opening && openingDenotationType == Abbreviation && !completionInfo.nextArgumentOrValue) {
1438  // TODO: add test for this case
1439  cout << '\'' << '-' << opening << arg->abbreviation() << '\'' << ' ';
1440  } else if (completionInfo.lastDetectedArg && reader.argDenotationType == Abbreviation && !completionInfo.nextArgumentOrValue) {
1441  if (reader.argv == reader.end) {
1442  cout << '\'' << *(reader.argv - 1) << '\'' << ' ';
1443  }
1444  } else if (arg->denotesOperation()) {
1445  cout << '\'' << arg->name() << '\'' << ' ';
1446  } else {
1447  cout << '\'' << '-' << '-' << arg->name() << '\'' << ' ';
1448  }
1449  }
1450  // -> completions for files and dirs
1451  // -> if there's already an "opening", determine the dir part and the file part
1452  string actualDir, actualFile;
1453  bool haveFileOrDirCompletions = false;
1454  if (argc && currentWordIndex == completionInfo.lastSpecifiedArgIndex && opening) {
1455  // the "opening" might contain escaped characters which need to be unescaped first (let's hope this covers all possible escapings)
1456  string unescapedOpening(opening);
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  findAndReplace<string>(unescapedOpening, "\\'", "'");
1465  findAndReplace<string>(unescapedOpening, "\\\"", "\"");
1466  findAndReplace<string>(unescapedOpening, "\\\\", "\\");
1467  // determine the "directory" part
1468  string dir = directory(unescapedOpening);
1469  if (dir.empty()) {
1470  actualDir = ".";
1471  } else {
1472  if (dir[0] == '\"' || dir[0] == '\'') {
1473  dir.erase(0, 1);
1474  }
1475  if (dir.size() > 1 && (dir[dir.size() - 2] == '\"' || dir[dir.size() - 2] == '\'')) {
1476  dir.erase(dir.size() - 2, 1);
1477  }
1478  actualDir = move(dir);
1479  }
1480  // determine the "file" part
1481  string file = fileName(unescapedOpening);
1482  if (file[0] == '\"' || file[0] == '\'') {
1483  file.erase(0, 1);
1484  }
1485  if (file.size() > 1 && (file[dir.size() - 2] == '\"' || dir[file.size() - 2] == '\'')) {
1486  file.erase(file.size() - 2, 1);
1487  }
1488  actualFile = move(file);
1489  }
1490 
1491  // -> completion for files and dirs
1493  if (completionInfo.completeFiles) {
1494  entryTypes |= DirectoryEntryType::File;
1495  }
1496  if (completionInfo.completeDirs) {
1497  entryTypes |= DirectoryEntryType::Directory;
1498  }
1499  if (entryTypes != DirectoryEntryType::None) {
1500  const string replace("'"), with("'\"'\"'");
1501  if (argc && currentWordIndex <= completionInfo.lastSpecifiedArgIndex && opening) {
1502  list<string> entries = directoryEntries(actualDir.c_str(), entryTypes);
1503  findAndReplace(actualDir, replace, with);
1504  for (string &dirEntry : entries) {
1505  if (!startsWith(dirEntry, actualFile)) {
1506  continue;
1507  }
1508  cout << '\'';
1509  if (actualDir != ".") {
1510  cout << actualDir;
1511  }
1512  findAndReplace(dirEntry, replace, with);
1513  cout << dirEntry << '\'' << ' ';
1514  haveFileOrDirCompletions = true;
1515  }
1516  } else {
1517  for (string &dirEntry : directoryEntries(".", entryTypes)) {
1518  findAndReplace(dirEntry, replace, with);
1519  cout << '\'' << dirEntry << '\'' << ' ';
1520  haveFileOrDirCompletions = true;
1521  }
1522  }
1523  }
1524  cout << ')';
1525 
1526  // ensure file or dir completions are formatted appropriately
1527  if (haveFileOrDirCompletions) {
1528  cout << "; compopt -o filenames";
1529  }
1530 
1531  // ensure trailing whitespace is ommitted
1532  if (noWhitespace) {
1533  cout << "; compopt -o nospace";
1534  }
1535 
1536  cout << endl;
1537 }
1538 
1544 {
1545  for (const Argument *arg : args) {
1546  const auto occurrences = arg->occurrences();
1547  if (arg->isParentPresent() && occurrences > arg->maxOccurrences()) {
1548  throw Failure(argsToString("The argument \"", arg->name(), "\" mustn't be specified more than ", arg->maxOccurrences(),
1549  (arg->maxOccurrences() == 1 ? " time." : " times.")));
1550  }
1551  if (arg->isParentPresent() && occurrences < arg->minOccurrences()) {
1552  throw Failure(argsToString("The argument \"", arg->name(), "\" must be specified at least ", arg->minOccurrences(),
1553  (arg->minOccurrences() == 1 ? " time." : " times.")));
1554  }
1555  Argument *conflictingArgument = nullptr;
1556  if (arg->isMainArgument()) {
1557  if (!arg->isCombinable() && arg->isPresent()) {
1558  conflictingArgument = firstPresentUncombinableArg(m_mainArgs, arg);
1559  }
1560  } else {
1561  conflictingArgument = arg->conflictsWithArgument();
1562  }
1563  if (conflictingArgument) {
1564  throw Failure(argsToString("The argument \"", conflictingArgument->name(), "\" can not be combined with \"", arg->name(), "\"."));
1565  }
1566  for (size_t i = 0; i != occurrences; ++i) {
1567  if (!arg->allRequiredValuesPresent(i)) {
1568  stringstream ss(stringstream::in | stringstream::out);
1569  ss << "Not all parameter for argument \"" << arg->name() << "\" ";
1570  if (i) {
1571  ss << " (" << (i + 1) << " occurrence) ";
1572  }
1573  ss << "provided. You have to provide the following parameter:";
1574  size_t valueNamesPrint = 0;
1575  for (const auto &name : arg->m_valueNames) {
1576  ss << ' ' << name, ++valueNamesPrint;
1577  }
1578  if (arg->m_requiredValueCount != Argument::varValueCount) {
1579  while (valueNamesPrint < arg->m_requiredValueCount) {
1580  ss << "\nvalue " << (++valueNamesPrint);
1581  }
1582  }
1583  throw Failure(ss.str());
1584  }
1585  }
1586 
1587  // check contraints of sub arguments recursively
1588  checkConstraints(arg->m_subArgs);
1589  }
1590 }
1591 
1600 {
1601  for (const Argument *arg : args) {
1602  // invoke the callback for each occurrence of the argument
1603  if (arg->m_callbackFunction) {
1604  for (const auto &occurrence : arg->m_occurrences) {
1605  arg->m_callbackFunction(occurrence);
1606  }
1607  }
1608  // invoke the callbacks for sub arguments recursively
1609  invokeCallbacks(arg->m_subArgs);
1610  }
1611 }
1612 
1623  : Argument("help", 'h', "shows this information")
1624 {
1625  setCallback([&parser](const ArgumentOccurrence &) {
1627  parser.printHelp(cout);
1628  });
1629 }
1630 
1661 NoColorArgument *NoColorArgument::s_instance = nullptr;
1662 
1668 #ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1669  : Argument("no-color", '\0', "disables formatted/colorized output")
1670 #else
1671  : Argument("enable-color", '\0', "enables formatted/colorized output")
1672 #endif
1673 {
1674  setCombinable(true);
1675 
1676  if (s_instance) {
1677  return;
1678  }
1679  s_instance = this;
1680 
1681  // set the environmentvariable: note that this is not directly used and just assigned for printing help
1682  setEnvironmentVariable("ENABLE_ESCAPE_CODES");
1683 
1684  // default-initialize EscapeCodes::enabled from environment variable
1685  const char *envValue = getenv(environmentVariable());
1686  if (!envValue) {
1687  return;
1688  }
1689  for (; *envValue; ++envValue) {
1690  switch (*envValue) {
1691  case '0':
1692  case ' ':
1693  break;
1694  default:
1695  // enable escape codes if ENABLE_ESCAPE_CODES contains anything else than spaces or zeros
1696  EscapeCodes::enabled = true;
1697  return;
1698  }
1699  }
1700  // disable escape codes if ENABLE_ESCAPE_CODES is empty or only contains spaces and zeros
1701  EscapeCodes::enabled = false;
1702 }
1703 
1708 {
1709  if (s_instance == this) {
1710  s_instance = nullptr;
1711  }
1712 }
1713 
1718 {
1719  if (NoColorArgument::s_instance && NoColorArgument::s_instance->isPresent()) {
1720 #ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT
1721  EscapeCodes::enabled = false;
1722 #else
1723  EscapeCodes::enabled = true;
1724 #endif
1725  }
1726 }
1727 
1731 void ValueConversion::Helper::ArgumentValueConversionError::throwFailure(const std::vector<Argument *> &argumentPath) const
1732 {
1733  throw Failure(argumentPath.empty()
1734  ? argsToString("Conversion of top-level value \"", valueToConvert, "\" to type \"", targetTypeName, "\" failed: ", errorMessage)
1735  : argsToString("Conversion of value \"", valueToConvert, "\" (for argument --", argumentPath.back()->name(), ") to type \"",
1736  targetTypeName, "\" failed: ", errorMessage));
1737 }
1738 
1742 void ArgumentOccurrence::throwNumberOfValuesNotSufficient(unsigned long valuesToConvert) const
1743 {
1744  throw Failure(path.empty()
1745  ? argsToString("Expected ", valuesToConvert, " top-level values to be present but only ", values.size(), " have been specified.")
1746  : argsToString("Expected ", valuesToConvert, " values for argument --", path.back()->name(), " to be present but only ", values.size(),
1747  " have been specified."));
1748 }
1749 
1750 } // 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.
#define IF_DEBUG_BUILD(x)
Wraps debug-only lines conveniently.
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.
std::vector< Argument * > ArgumentVector
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.
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.
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
std::initializer_list< Argument * > ArgumentInitializerList
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.
constexpr int i
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...
std::vector< Argument * > path
The "path" of the occurrence (the parent elements which have been specified before).
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...
Contains various utilities such as computing Damerau–Levenshtein distance and N-dimensional arrays.
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
void insertSiblings(const ArgumentVector &siblings, list< const Argument * > &target)
Inserts the specified siblings in the target list.
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.
std::vector< const char * > values
The parameter values which have been specified after the occurrence of the argument.