C++ Utilities  4.10.0
Common C++ classes and routines used by my applications 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 
11 #include <algorithm>
12 #include <cstdlib>
13 #include <cstring>
14 #include <iostream>
15 #include <sstream>
16 #include <string>
17 
18 using namespace std;
19 using namespace std::placeholders;
20 using namespace ConversionUtilities;
21 using namespace IoUtilities;
22 
27 namespace ApplicationUtilities {
28 
32 enum ArgumentDenotationType : unsigned char {
33  Value = 0,
35  FullName = 2
36 };
37 
50 ArgumentReader::ArgumentReader(ArgumentParser &parser, const char *const *argv, const char *const *end, bool completionMode)
51  : parser(parser)
52  , args(parser.m_mainArgs)
53  , index(0)
54  , argv(argv)
55  , end(end)
56  , lastArg(nullptr)
57  , argDenotation(nullptr)
58  , completionMode(completionMode)
59 {
60 }
61 
65 ArgumentReader &ArgumentReader::reset(const char *const *argv, const char *const *end)
66 {
67  this->argv = argv;
68  this->end = end;
69  index = 0;
70  lastArg = nullptr;
71  argDenotation = nullptr;
72  return *this;
73 }
74 
80 {
81  read(args);
82 }
83 
89 {
90  // method is called recursively for sub args to the last argument (which is nullptr in the initial call) is the current parent argument
91  Argument *const parentArg = lastArg;
92  // determine the current path
93  const vector<Argument *> &parentPath = parentArg ? parentArg->path(parentArg->occurrences() - 1) : vector<Argument *>();
94 
95  Argument *lastArgInLevel = nullptr;
96  vector<const char *> *values = nullptr;
97 
98  // iterate through all argument denotations; loop might exit earlier when an denotation is unknown
99  while (argv != end) {
100  if (values && lastArgInLevel->requiredValueCount() != static_cast<size_t>(-1) && values->size() < lastArgInLevel->requiredValueCount()) {
101  // there are still values to read
102  values->emplace_back(argDenotation ? argDenotation : *argv);
103  ++index, ++argv, argDenotation = nullptr;
104  } else {
105  // determine how denotation must be processed
106  bool abbreviationFound = false;
107  if (argDenotation) {
108  // continue reading childs for abbreviation denotation already detected
109  abbreviationFound = false;
111  } else {
112  // determine denotation type
113  argDenotation = *argv;
114  if (!*argDenotation && (!lastArgInLevel || values->size() >= lastArgInLevel->requiredValueCount())) {
115  // skip empty arguments
116  ++index, ++argv, argDenotation = nullptr;
117  continue;
118  }
119  abbreviationFound = false;
122  }
123 
124  // try to find matching Argument instance
125  Argument *matchingArg = nullptr;
126  size_t argDenotationLength;
127  if (argDenotationType != Value) {
128  const char *const equationPos = strchr(argDenotation, '=');
129  for (argDenotationLength = equationPos ? static_cast<size_t>(equationPos - argDenotation) : strlen(argDenotation);
130  argDenotationLength; matchingArg = nullptr) {
131  // search for arguments by abbreviation or name depending on the previously determined denotation type
133  for (Argument *arg : args) {
134  if (arg->abbreviation() && arg->abbreviation() == *argDenotation) {
135  matchingArg = arg;
136  abbreviationFound = true;
137  break;
138  }
139  }
140  } else {
141  for (Argument *arg : args) {
142  if (arg->name() && !strncmp(arg->name(), argDenotation, argDenotationLength)
143  && *(arg->name() + argDenotationLength) == '\0') {
144  matchingArg = arg;
145  break;
146  }
147  }
148  }
149 
150  if (matchingArg) {
151  // an argument matched the specified denotation so add an occurrence
152  matchingArg->m_occurrences.emplace_back(index, parentPath, parentArg);
153 
154  // prepare reading parameter values
155  values = &matchingArg->m_occurrences.back().values;
156  if (equationPos) {
157  values->push_back(equationPos + 1);
158  }
159 
160  // read sub arguments
161  ++index, ++parser.m_actualArgc, lastArg = lastArgInLevel = matchingArg, lastArgDenotation = argv;
162  if (argDenotationType != Abbreviation || (++argDenotation != equationPos)) {
164  // no further abbreviations follow -> read sub args for next argv
165  ++argv, argDenotation = nullptr;
166  read(lastArg->m_subArgs);
167  argDenotation = nullptr;
168  } else {
169  // further abbreviations follow -> don't increment argv, keep processing outstanding chars of argDenotation
170  read(lastArg->m_subArgs);
171  }
172  break;
173  } // else: another abbreviated argument follows (and it is not present in the sub args)
174  } else {
175  break;
176  }
177  }
178  }
179 
180  if (!matchingArg) {
181  // unknown argument might be a sibling of the parent element
182  if (argDenotationType != Value) {
183  for (auto parentArgument = parentPath.crbegin(), pathEnd = parentPath.crend();; ++parentArgument) {
184  for (Argument *sibling : (parentArgument != pathEnd ? (*parentArgument)->subArguments() : parser.m_mainArgs)) {
185  if (sibling->occurrences() < sibling->maxOccurrences()) {
186  if ((argDenotationType == Abbreviation && (sibling->abbreviation() && sibling->abbreviation() == *argDenotation))
187  || (sibling->name() && !strncmp(sibling->name(), argDenotation, argDenotationLength))) {
188  return;
189  }
190  }
191  }
192  if (parentArgument == pathEnd) {
193  break;
194  }
195  };
196  }
197 
198  // unknown argument might just be a parameter value of the last argument
199  if (lastArgInLevel && values->size() < lastArgInLevel->requiredValueCount()) {
200  values->emplace_back(abbreviationFound ? argDenotation : *argv);
201  ++index, ++argv, argDenotation = nullptr;
202  continue;
203  }
204 
205  // first value might denote "operation"
206  for (Argument *arg : args) {
207  if (arg->denotesOperation() && arg->name() && !strcmp(arg->name(), *argv)) {
208  (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
210  ++index, ++argv;
211  break;
212  }
213  }
214 
215  // use the first default argument which is not already present if there is still no match
216  if (!matchingArg && (!completionMode || (argv + 1 != end))) {
217  const bool uncombinableMainArgPresent = parentArg ? false : parser.isUncombinableMainArgPresent();
218  for (Argument *arg : args) {
219  if (arg->isImplicit() && !arg->isPresent() && !arg->wouldConflictWithArgument()
220  && (!uncombinableMainArgPresent || !arg->isMainArgument())) {
221  (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
222  break;
223  }
224  }
225  }
226 
227  if (matchingArg) {
228  // an argument matched the specified denotation
229  if (lastArgInLevel == matchingArg) {
230  break; // break required? -> TODO: add test for this condition
231  }
232 
233  // prepare reading parameter values
234  values = &matchingArg->m_occurrences.back().values;
235 
236  // read sub arguments
237  ++parser.m_actualArgc, lastArg = lastArgInLevel = matchingArg, argDenotation = nullptr;
238  read(lastArg->m_subArgs);
239  argDenotation = nullptr;
240  continue;
241  }
242 
243  // argument denotation is unknown -> handle error
244  if (parentArg) {
245  // continue with parent level
246  return;
247  }
248  if (completionMode) {
249  // ignore unknown denotation
250  ++index, ++argv, argDenotation = nullptr;
251  } else {
252  switch (parser.m_unknownArgBehavior) {
254  cerr << "The specified argument \"" << *argv << "\" is unknown and will be ignored." << endl;
255  FALLTHROUGH;
257  // ignore unknown denotation
258  ++index, ++argv, argDenotation = nullptr;
259  break;
261  throw Failure(argsToString("The specified argument \"", *argv, "\" is unknown."));
262  }
263  }
264  } // if(!matchingArg)
265  } // no values to read
266  } // while(argv != end)
267 }
268 
275 ostream &operator<<(ostream &os, const Wrapper &wrapper)
276 {
277  // determine max. number of columns
278  static const TerminalSize termSize(determineTerminalSize());
279  const auto maxColumns = termSize.columns ? termSize.columns : numeric_limits<unsigned short>::max();
280 
281  // print wrapped string considering indentation
282  unsigned short currentCol = wrapper.m_indentation.level;
283  for (const char *currentChar = wrapper.m_str; *currentChar; ++currentChar) {
284  const bool wrappingRequired = currentCol >= maxColumns;
285  if (wrappingRequired || *currentChar == '\n') {
286  // insert newline (TODO: wrap only at end of a word)
287  os << '\n';
288  // print indentation (if enough space)
289  if (wrapper.m_indentation.level < maxColumns) {
290  os << wrapper.m_indentation;
291  currentCol = wrapper.m_indentation.level;
292  } else {
293  currentCol = 0;
294  }
295  }
296  if (*currentChar != '\n' && (!wrappingRequired || *currentChar != ' ')) {
297  os << *currentChar;
298  ++currentCol;
299  }
300  }
301  return os;
302 }
303 
305 const char *applicationName = nullptr;
307 const char *applicationAuthor = nullptr;
309 const char *applicationVersion = nullptr;
311 const char *applicationUrl = nullptr;
313 std::initializer_list<const char *> dependencyVersions;
314 
319 void (*exitFunction)(int) = &exit;
320 
322 
323 inline bool notEmpty(const char *str)
324 {
325  return str && *str;
326 }
327 
329 
346 Argument::Argument(const char *name, char abbreviation, const char *description, const char *example)
347  : m_name(name)
348  , m_abbreviation(abbreviation)
349  , m_environmentVar(nullptr)
350  , m_description(description)
351  , m_example(example)
352  , m_minOccurrences(0)
353  , m_maxOccurrences(1)
354  , m_combinable(false)
355  , m_denotesOperation(false)
356  , m_requiredValueCount(0)
357  , m_implicit(false)
358  , m_isMainArg(false)
361  , m_preDefinedCompletionValues(nullptr)
362 {
363 }
364 
369 {
370 }
371 
379 const char *Argument::firstValue() const
380 {
381  if (!m_occurrences.empty() && !m_occurrences.front().values.empty()) {
382  return m_occurrences.front().values.front();
383  } else if (m_environmentVar) {
384  return getenv(m_environmentVar);
385  } else {
386  return nullptr;
387  }
388 }
389 
393 void Argument::printInfo(ostream &os, unsigned char indentation) const
394 {
395  Indentation ident(indentation);
396  os << ident;
398  if (notEmpty(name())) {
399  if (!denotesOperation()) {
400  os << '-' << '-';
401  }
402  os << name();
403  }
404  if (notEmpty(name()) && abbreviation()) {
405  os << ',' << ' ';
406  }
407  if (abbreviation()) {
408  os << '-' << abbreviation();
409  }
411  if (requiredValueCount()) {
412  unsigned int valueNamesPrint = 0;
413  for (auto i = valueNames().cbegin(), end = valueNames().cend(); i != end && valueNamesPrint < requiredValueCount(); ++i) {
414  os << ' ' << '[' << *i << ']';
415  ++valueNamesPrint;
416  }
418  os << " ...";
419  } else {
420  for (; valueNamesPrint < requiredValueCount(); ++valueNamesPrint) {
421  os << " [value " << (valueNamesPrint + 1) << ']';
422  }
423  }
424  }
425  ident.level += 2;
426  if (notEmpty(description())) {
427  os << '\n' << ident << Wrapper(description(), ident);
428  }
429  if (isRequired()) {
430  os << '\n' << ident << "particularities: mandatory";
431  if (!isMainArgument()) {
432  os << " if parent argument is present";
433  }
434  }
435  if (environmentVariable()) {
436  os << '\n' << ident << "default environment variable: " << Wrapper(environmentVariable(), ident + 30);
437  }
438  os << '\n';
439  for (const auto *arg : subArguments()) {
440  arg->printInfo(os, ident.level);
441  }
442  if (notEmpty(example())) {
443  if (ident.level == 2 && !subArguments().empty()) {
444  os << '\n';
445  }
446  os << ident << "example: " << Wrapper(example(), ident + 9);
447  os << '\n';
448  }
449 }
450 
457 {
458  for (Argument *arg : args) {
459  if (arg != except && arg->isPresent() && !arg->isCombinable()) {
460  return arg;
461  }
462  }
463  return nullptr;
464 }
465 
480 void Argument::setSubArguments(const ArgumentInitializerList &secondaryArguments)
481 {
482  // remove this argument from the parents list of the previous secondary arguments
483  for (Argument *arg : m_subArgs) {
484  arg->m_parents.erase(remove(arg->m_parents.begin(), arg->m_parents.end(), this), arg->m_parents.end());
485  }
486  // assign secondary arguments
487  m_subArgs.assign(secondaryArguments);
488  // add this argument to the parents list of the assigned secondary arguments
489  // and set the parser
490  for (Argument *arg : m_subArgs) {
491  if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
492  arg->m_parents.push_back(this);
493  }
494  }
495 }
496 
505 {
506  if (find(m_subArgs.cbegin(), m_subArgs.cend(), arg) == m_subArgs.cend()) {
507  m_subArgs.push_back(arg);
508  if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
509  arg->m_parents.push_back(this);
510  }
511  }
512 }
513 
519 {
520  if (isMainArgument()) {
521  return true;
522  }
523  for (const Argument *parent : m_parents) {
524  if (parent->isPresent()) {
525  return true;
526  }
527  }
528  return false;
529 }
530 
540 {
541  return isPresent() ? wouldConflictWithArgument() : nullptr;
542 }
543 
553 {
554  if (!isCombinable()) {
555  for (Argument *parent : m_parents) {
556  for (Argument *sibling : parent->subArguments()) {
557  if (sibling != this && sibling->isPresent() && !sibling->isCombinable()) {
558  return sibling;
559  }
560  }
561  }
562  }
563  return nullptr;
564 }
565 
571 {
572  for (Argument *arg : m_subArgs) {
573  if (arg->denotesOperation() && arg->isPresent()) {
574  return arg;
575  }
576  }
577  return nullptr;
578 }
579 
585 {
586  for (Argument *arg : m_subArgs) {
587  arg->resetRecursively();
588  }
589  reset();
590 }
591 
609  : m_actualArgc(0)
610  , m_executable(nullptr)
611  , m_unknownArgBehavior(UnknownArgumentBehavior::Fail)
612  , m_defaultArg(nullptr)
613 {
614 }
615 
626 {
627  if (mainArguments.size()) {
628  for (Argument *arg : mainArguments) {
629  arg->m_isMainArg = true;
630  }
631  m_mainArgs.assign(mainArguments);
632  if (!m_defaultArg) {
633  if (!(*mainArguments.begin())->requiredValueCount()) {
634  bool subArgsRequired = false;
635  for (const Argument *subArg : (*mainArguments.begin())->subArguments()) {
636  if (subArg->isRequired()) {
637  subArgsRequired = true;
638  break;
639  }
640  }
641  if (!subArgsRequired) {
642  m_defaultArg = *mainArguments.begin();
643  }
644  }
645  }
646  } else {
647  m_mainArgs.clear();
648  }
649 }
650 
658 {
659  argument->m_isMainArg = true;
660  m_mainArgs.push_back(argument);
661 }
662 
666 void ArgumentParser::printHelp(ostream &os) const
667 {
669  if (applicationName && *applicationName) {
670  os << applicationName;
671  if (applicationVersion && *applicationVersion) {
672  os << ',' << ' ';
673  }
674  }
675  if (applicationVersion && *applicationVersion) {
676  os << "version " << applicationVersion;
677  }
678  if (dependencyVersions.size()) {
679  if ((applicationName && *applicationName) || (applicationVersion && *applicationVersion)) {
680  os << '\n';
682  }
683  auto i = dependencyVersions.begin(), end = dependencyVersions.end();
684  os << "Linked against: " << *i;
685  for (++i; i != end; ++i) {
686  os << ',' << ' ' << *i;
687  }
688  }
689  if ((applicationName && *applicationName) || (applicationVersion && *applicationVersion) || dependencyVersions.size()) {
690  os << '\n' << '\n';
691  }
693  if (!m_mainArgs.empty()) {
694  os << "Available arguments:";
695  for (const Argument *arg : m_mainArgs) {
696  os << '\n';
697  arg->printInfo(os);
698  }
699  }
700  if (applicationUrl && *applicationUrl) {
701  os << "\nProject website: " << applicationUrl << endl;
702  }
703 }
704 
716 void ArgumentParser::parseArgs(int argc, const char *const *argv)
717 {
718  readArgs(argc, argv);
719  if (argc) {
720  checkConstraints(m_mainArgs);
721  invokeCallbacks(m_mainArgs);
722  }
723 }
724 
732 void ArgumentParser::parseArgsOrExit(int argc, const char *const *argv)
733 {
734  try {
735  parseArgs(argc, argv);
736  } catch (const Failure &failure) {
738  cerr << failure;
739  exit(1);
740  }
741 }
742 
754 void ArgumentParser::readArgs(int argc, const char *const *argv)
755 {
756  IF_DEBUG_BUILD(verifyArgs(m_mainArgs, std::vector<char>(), std::vector<const char *>());)
757  m_actualArgc = 0;
758  if (argc) {
759  // the first argument is the executable name
760  m_executable = *argv;
761 
762  // check for further arguments
763  if (--argc) {
764  // if the first argument (after executable name) is "--bash-completion-for", bash completion for the following arguments is requested
765  bool completionMode = !strcmp(*++argv, "--bash-completion-for");
766  unsigned int currentWordIndex;
767  if (completionMode) {
768  // the first argument after "--bash-completion-for" is the index of the current word
769  try {
770  currentWordIndex = (--argc ? stringToNumber<unsigned int, string>(*(++argv)) : 0);
771  if (argc) {
772  ++argv, --argc;
773  }
774  } catch (const ConversionException &) {
775  currentWordIndex = static_cast<unsigned int>(argc - 1);
776  }
777  }
778 
779  // read specified arguments
780  ArgumentReader reader(*this, argv,
781  argv + (completionMode ? min(static_cast<unsigned int>(argc), currentWordIndex + 1) : static_cast<unsigned int>(argc)),
782  completionMode);
783  try {
784  reader.read();
785  } catch (const Failure &) {
786  if (!completionMode) {
787  throw;
788  }
789  }
790 
791  if (completionMode) {
792  printBashCompletion(argc, argv, currentWordIndex, reader);
793  exitFunction(0); // prevent the applicaton to continue with the regular execution
794  }
795  } else {
796  // no arguments specified -> flag default argument as present if one is assigned
797  if (m_defaultArg) {
798  m_defaultArg->m_occurrences.emplace_back(0);
799  }
800  }
801  } else {
802  m_executable = nullptr;
803  }
804 }
805 
811 {
812  for (Argument *arg : m_mainArgs) {
813  arg->resetRecursively();
814  }
815  m_actualArgc = 0;
816 }
817 
824 {
825  for (Argument *arg : m_mainArgs) {
826  if (arg->denotesOperation() && arg->isPresent()) {
827  return arg;
828  }
829  }
830  return nullptr;
831 }
832 
837 {
838  for (const Argument *arg : m_mainArgs) {
839  if (!arg->isCombinable() && arg->isPresent()) {
840  return true;
841  }
842  }
843  return false;
844 }
845 
846 #ifdef DEBUG_BUILD
847 
863 void ApplicationUtilities::ArgumentParser::verifyArgs(const ArgumentVector &args, vector<char>, vector<const char *>)
864 {
865  vector<const Argument *> verifiedArgs;
866  verifiedArgs.reserve(args.size());
867  vector<char> abbreviations;
868  abbreviations.reserve(abbreviations.size() + args.size());
869  vector<const char *> names;
870  names.reserve(names.size() + args.size());
871  bool hasImplicit = false;
872  for (const Argument *arg : args) {
873  assert(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) == verifiedArgs.cend());
874  verifiedArgs.push_back(arg);
875  assert(!arg->isImplicit() || !hasImplicit);
876  hasImplicit |= arg->isImplicit();
877  assert(!arg->abbreviation() || find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) == abbreviations.cend());
878  abbreviations.push_back(arg->abbreviation());
879  assert(!arg->name() || find_if(names.cbegin(), names.cend(), [arg](const char *name) { return !strcmp(arg->name(), name); }) == names.cend());
880  assert(arg->requiredValueCount() == 0 || arg->subArguments().size() == 0);
881  names.emplace_back(arg->name());
882  }
883  for (const Argument *arg : args) {
884  verifyArgs(arg->subArguments(), vector<char>(), vector<const char *>());
885  }
886 }
887 #endif
888 
896 bool compareArgs(const Argument *arg1, const Argument *arg2)
897 {
898  if (arg1->denotesOperation() && !arg2->denotesOperation()) {
899  return true;
900  } else if (!arg1->denotesOperation() && arg2->denotesOperation()) {
901  return false;
902  } else {
903  return strcmp(arg1->name(), arg2->name()) < 0;
904  }
905 }
906 
911 void insertSiblings(const ArgumentVector &siblings, list<const Argument *> &target)
912 {
913  bool onlyCombinable = false;
914  for (const Argument *sibling : siblings) {
915  if (sibling->isPresent() && !sibling->isCombinable()) {
916  onlyCombinable = true;
917  break;
918  }
919  }
920  for (const Argument *sibling : siblings) {
921  if ((!onlyCombinable || sibling->isCombinable()) && sibling->occurrences() < sibling->maxOccurrences()) {
922  target.push_back(sibling);
923  }
924  }
925 }
926 
932 void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader)
933 {
934  // variables to store relevant completions (arguments, pre-defined values, files/dirs)
935  list<const Argument *> relevantArgs, relevantPreDefinedValues;
936  bool completeFiles = false, completeDirs = false, noWhitespace = false;
937 
938  // get the last argument the argument parser was able to detect successfully
939  const Argument *const lastDetectedArg = reader.lastArg;
940  size_t lastDetectedArgIndex;
941  vector<Argument *> lastDetectedArgPath;
942  if (lastDetectedArg) {
943  lastDetectedArgIndex = reader.lastArgDenotation - argv;
944  lastDetectedArgPath = lastDetectedArg->path(lastDetectedArg->occurrences() - 1);
945  }
946 
947  // determine last arg, omitting trailing empty args
948  const char *const *lastSpecifiedArg;
949  unsigned int lastSpecifiedArgIndex;
950  if (argc) {
951  lastSpecifiedArgIndex = static_cast<unsigned int>(argc) - 1;
952  lastSpecifiedArg = argv + lastSpecifiedArgIndex;
953  for (; lastSpecifiedArg >= argv && **lastSpecifiedArg == '\0'; --lastSpecifiedArg, --lastSpecifiedArgIndex)
954  ;
955  }
956 
957  // determine arguments relevant for completion
958  bool nextArgumentOrValue;
959  if (lastDetectedArg && lastDetectedArg->isPresent()) {
960  if ((nextArgumentOrValue = (currentWordIndex > lastDetectedArgIndex))) {
961  // define function to add parameter values of argument as possible completions
962  const auto addValueCompletionsForArg = [&relevantPreDefinedValues, &completeFiles, &completeDirs](const Argument *arg) {
963  if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::PreDefinedValues) {
964  relevantPreDefinedValues.push_back(arg);
965  }
966  if (!(arg->valueCompletionBehaviour() & ValueCompletionBehavior::FileSystemIfNoPreDefinedValues)
967  || !arg->preDefinedCompletionValues()) {
968  completeFiles = completeFiles || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Files;
969  completeDirs = completeDirs || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Directories;
970  }
971  };
972 
973  // detect number of specified values
974  auto currentValueCount = lastDetectedArg->values(lastDetectedArg->occurrences() - 1).size();
975  // ignore values which are specified after the current word
976  if (currentValueCount) {
977  const auto currentWordIndexRelativeToLastDetectedArg = currentWordIndex - lastDetectedArgIndex;
978  if (currentValueCount > currentWordIndexRelativeToLastDetectedArg) {
979  currentValueCount -= currentWordIndexRelativeToLastDetectedArg;
980  } else {
981  currentValueCount = 0;
982  }
983  }
984 
985  // add value completions for implicit child if there are no value specified and there are no values required by the
986  // last detected argument itself
987  if (!currentValueCount && !lastDetectedArg->requiredValueCount()) {
988  for (const Argument *child : lastDetectedArg->subArguments()) {
989  if (child->isImplicit() && child->requiredValueCount()) {
990  addValueCompletionsForArg(child);
991  break;
992  }
993  }
994  }
995 
996  // add value completions for last argument if there are further values required
997  if (lastDetectedArg->requiredValueCount() == Argument::varValueCount || (currentValueCount < lastDetectedArg->requiredValueCount())) {
998  addValueCompletionsForArg(lastDetectedArg);
999  }
1000 
1001  if (lastDetectedArg->requiredValueCount() == Argument::varValueCount
1002  || lastDetectedArg->values(lastDetectedArg->occurrences() - 1).size() >= lastDetectedArg->requiredValueCount()) {
1003  // sub arguments of the last arg are possible completions
1004  for (const Argument *subArg : lastDetectedArg->subArguments()) {
1005  if (subArg->occurrences() < subArg->maxOccurrences()) {
1006  relevantArgs.push_back(subArg);
1007  }
1008  }
1009 
1010  // siblings of parents are possible completions as well
1011  for (auto parentArgument = lastDetectedArgPath.crbegin(), end = lastDetectedArgPath.crend();; ++parentArgument) {
1012  insertSiblings(parentArgument != end ? (*parentArgument)->subArguments() : m_mainArgs, relevantArgs);
1013  if (parentArgument == end) {
1014  break;
1015  }
1016  }
1017  }
1018  } else {
1019  // since the argument could be detected (hopefully unambiguously?) just return it for "final completion"
1020  relevantArgs.push_back(lastDetectedArg);
1021  }
1022 
1023  } else {
1024  // no arguments detected -> just use main arguments for completion
1025  nextArgumentOrValue = true;
1026  insertSiblings(m_mainArgs, relevantArgs);
1027  }
1028 
1029  // read the "opening" (started but not finished argument denotation)
1030  const char *opening = nullptr;
1031  string compoundOpening;
1032  size_t openingLen, compoundOpeningStartLen = 0;
1033  unsigned char openingDenotationType = Value;
1034  if (argc && nextArgumentOrValue) {
1035  if (currentWordIndex < static_cast<unsigned int>(argc)) {
1036  opening = argv[currentWordIndex];
1037  // For some reason completions for eg. "set --values disk=1 tag=a" are splitted so the
1038  // equation sign is an own argument ("set --values disk = 1 tag = a").
1039  // This is not how values are treated by the argument parser. Hence the opening
1040  // must be joined again. In this case only the part after the equation sign needs to be
1041  // provided for completion so compoundOpeningStartLen is set to number of characters to skip.
1042  size_t minCurrentWordIndex = (lastDetectedArg ? lastDetectedArgIndex : 0);
1043  if (currentWordIndex > minCurrentWordIndex && !strcmp(opening, "=")) {
1044  compoundOpening.reserve(compoundOpeningStartLen = strlen(argv[--currentWordIndex]) + 1);
1045  compoundOpening = argv[currentWordIndex];
1046  compoundOpening += '=';
1047  } else if (currentWordIndex > (minCurrentWordIndex + 1) && !strcmp(argv[currentWordIndex - 1], "=")) {
1048  compoundOpening.reserve((compoundOpeningStartLen = strlen(argv[currentWordIndex -= 2]) + 1) + strlen(opening));
1049  compoundOpening = argv[currentWordIndex];
1050  compoundOpening += '=';
1051  compoundOpening += opening;
1052  }
1053  if (!compoundOpening.empty()) {
1054  opening = compoundOpening.data();
1055  }
1056  } else {
1057  opening = *lastSpecifiedArg;
1058  }
1059  *opening == '-' && (++opening, ++openingDenotationType) && *opening == '-' && (++opening, ++openingDenotationType);
1060  openingLen = strlen(opening);
1061  }
1062 
1063  relevantArgs.sort(compareArgs);
1064 
1065  // print "COMPREPLY" bash array
1066  cout << "COMPREPLY=(";
1067  // -> completions for parameter values
1068  for (const Argument *arg : relevantPreDefinedValues) {
1069  if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::InvokeCallback && arg->m_callbackFunction) {
1070  arg->m_callbackFunction(arg->isPresent() ? arg->m_occurrences.front() : ArgumentOccurrence(Argument::varValueCount));
1071  }
1072  if (arg->preDefinedCompletionValues()) {
1073  bool appendEquationSign = arg->valueCompletionBehaviour() & ValueCompletionBehavior::AppendEquationSign;
1074  if (argc && currentWordIndex <= lastSpecifiedArgIndex && opening) {
1075  if (openingDenotationType == Value) {
1076  bool wordStart = true, ok = false, equationSignAlreadyPresent = false;
1077  size_t wordIndex = 0;
1078  for (const char *i = arg->preDefinedCompletionValues(), *end = opening + openingLen; *i;) {
1079  if (wordStart) {
1080  const char *i1 = i, *i2 = opening;
1081  for (; *i1 && i2 != end && *i1 == *i2; ++i1, ++i2)
1082  ;
1083  if ((ok = (i2 == end))) {
1084  cout << '\'';
1085  }
1086  wordStart = false;
1087  wordIndex = 0;
1088  } else if ((wordStart = (*i == ' ') || (*i == '\n'))) {
1089  equationSignAlreadyPresent = false;
1090  if (ok) {
1091  cout << '\'' << ' ';
1092  }
1093  ++i;
1094  continue;
1095  } else if (*i == '=') {
1096  equationSignAlreadyPresent = true;
1097  }
1098  if (ok) {
1099  if (!compoundOpeningStartLen || wordIndex >= compoundOpeningStartLen) {
1100  if (*i == '\'') {
1101  cout << "'\"'\"'";
1102  } else {
1103  cout << *i;
1104  }
1105  }
1106  ++i, ++wordIndex;
1107  switch (*i) {
1108  case ' ':
1109  case '\n':
1110  case '\0':
1111  if (appendEquationSign && !equationSignAlreadyPresent) {
1112  cout << '=';
1113  noWhitespace = true;
1114  equationSignAlreadyPresent = false;
1115  }
1116  if (*i == '\0') {
1117  cout << '\'';
1118  }
1119  }
1120  } else {
1121  ++i;
1122  }
1123  }
1124  cout << ' ';
1125  }
1126  } else if (const char *i = arg->preDefinedCompletionValues()) {
1127  bool equationSignAlreadyPresent = false;
1128  cout << '\'';
1129  while (*i) {
1130  if (*i == '\'') {
1131  cout << "'\"'\"'";
1132  } else {
1133  cout << *i;
1134  }
1135  switch (*(++i)) {
1136  case '=':
1137  equationSignAlreadyPresent = true;
1138  break;
1139  case ' ':
1140  case '\n':
1141  case '\0':
1142  if (appendEquationSign && !equationSignAlreadyPresent) {
1143  cout << '=';
1144  equationSignAlreadyPresent = false;
1145  }
1146  if (*i != '\0') {
1147  cout << '\'';
1148  if (*(++i)) {
1149  cout << ' ' << '\'';
1150  }
1151  }
1152  }
1153  }
1154  cout << '\'' << ' ';
1155  }
1156  }
1157  }
1158  // -> completions for further arguments
1159  for (const Argument *arg : relevantArgs) {
1160  if (argc && currentWordIndex <= lastSpecifiedArgIndex && opening) {
1161  switch (openingDenotationType) {
1162  case Value:
1163  if (!arg->denotesOperation() || strncmp(arg->name(), opening, openingLen)) {
1164  continue;
1165  }
1166  break;
1167  case Abbreviation:
1168  break;
1169  case FullName:
1170  if (strncmp(arg->name(), opening, openingLen)) {
1171  continue;
1172  }
1173  }
1174  }
1175 
1176  if (opening && openingDenotationType == Abbreviation && !nextArgumentOrValue) {
1177  cout << '\'' << '-' << opening << arg->abbreviation() << '\'' << ' ';
1178  } else if (lastDetectedArg && reader.argDenotationType == Abbreviation && !nextArgumentOrValue) {
1179  if (reader.argv == reader.end) {
1180  cout << '\'' << *(reader.argv - 1) << '\'' << ' ';
1181  }
1182  } else if (arg->denotesOperation()) {
1183  cout << '\'' << arg->name() << '\'' << ' ';
1184  } else {
1185  cout << '\'' << '-' << '-' << arg->name() << '\'' << ' ';
1186  }
1187  }
1188  // -> completions for files and dirs
1189  // -> if there's already an "opening", determine the dir part and the file part
1190  string actualDir, actualFile;
1191  bool haveFileOrDirCompletions = false;
1192  if (argc && currentWordIndex == lastSpecifiedArgIndex && opening) {
1193  // the "opening" might contain escaped characters which need to be unescaped first (let's hope this covers all possible escapings)
1194  string unescapedOpening(opening);
1195  findAndReplace<string>(unescapedOpening, "\\ ", " ");
1196  findAndReplace<string>(unescapedOpening, "\\,", ",");
1197  findAndReplace<string>(unescapedOpening, "\\[", "[");
1198  findAndReplace<string>(unescapedOpening, "\\]", "]");
1199  findAndReplace<string>(unescapedOpening, "\\!", "!");
1200  findAndReplace<string>(unescapedOpening, "\\#", "#");
1201  findAndReplace<string>(unescapedOpening, "\\$", "$");
1202  findAndReplace<string>(unescapedOpening, "\\'", "'");
1203  findAndReplace<string>(unescapedOpening, "\\\"", "\"");
1204  findAndReplace<string>(unescapedOpening, "\\\\", "\\");
1205  // determine the "directory" part
1206  string dir = directory(unescapedOpening);
1207  if (dir.empty()) {
1208  actualDir = ".";
1209  } else {
1210  if (dir[0] == '\"' || dir[0] == '\'') {
1211  dir.erase(0, 1);
1212  }
1213  if (dir.size() > 1 && (dir[dir.size() - 2] == '\"' || dir[dir.size() - 2] == '\'')) {
1214  dir.erase(dir.size() - 2, 1);
1215  }
1216  actualDir = move(dir);
1217  }
1218  // determine the "file" part
1219  string file = fileName(unescapedOpening);
1220  if (file[0] == '\"' || file[0] == '\'') {
1221  file.erase(0, 1);
1222  }
1223  if (file.size() > 1 && (file[dir.size() - 2] == '\"' || dir[file.size() - 2] == '\'')) {
1224  file.erase(file.size() - 2, 1);
1225  }
1226  actualFile = move(file);
1227  }
1228 
1229  // -> completion for files and dirs
1231  if (completeFiles) {
1232  entryTypes |= DirectoryEntryType::File;
1233  }
1234  if (completeDirs) {
1235  entryTypes |= DirectoryEntryType::Directory;
1236  }
1237  if (entryTypes != DirectoryEntryType::None) {
1238  const string replace("'"), with("'\"'\"'");
1239  if (argc && currentWordIndex <= lastSpecifiedArgIndex && opening) {
1240  list<string> entries = directoryEntries(actualDir.c_str(), entryTypes);
1241  findAndReplace(actualDir, replace, with);
1242  for (string &dirEntry : entries) {
1243  if (startsWith(dirEntry, actualFile)) {
1244  cout << '\'';
1245  if (actualDir != ".") {
1246  cout << actualDir;
1247  }
1248  findAndReplace(dirEntry, replace, with);
1249  cout << dirEntry << '\'' << ' ';
1250  haveFileOrDirCompletions = true;
1251  }
1252  }
1253  } else {
1254  for (string &dirEntry : directoryEntries(".", entryTypes)) {
1255  findAndReplace(dirEntry, replace, with);
1256  cout << '\'' << dirEntry << '\'' << ' ';
1257  haveFileOrDirCompletions = true;
1258  }
1259  }
1260  }
1261  cout << ')';
1262 
1263  // ensure file or dir completions are formatted appropriately
1264  if (haveFileOrDirCompletions) {
1265  cout << "; compopt -o filenames";
1266  }
1267 
1268  // ensure trailing whitespace is ommitted
1269  if (noWhitespace) {
1270  cout << "; compopt -o nospace";
1271  }
1272 
1273  cout << endl;
1274 }
1275 
1281 {
1282  for (const Argument *arg : args) {
1283  const auto occurrences = arg->occurrences();
1284  if (arg->isParentPresent() && occurrences > arg->maxOccurrences()) {
1285  throw Failure(argsToString("The argument \"", arg->name(), "\" mustn't be specified more than ", arg->maxOccurrences(),
1286  (arg->maxOccurrences() == 1 ? " time." : " times.")));
1287  }
1288  if (arg->isParentPresent() && occurrences < arg->minOccurrences()) {
1289  throw Failure(argsToString("The argument \"", arg->name(), "\" must be specified at least ", arg->minOccurrences(),
1290  (arg->minOccurrences() == 1 ? " time." : " times.")));
1291  }
1292  Argument *conflictingArgument = nullptr;
1293  if (arg->isMainArgument()) {
1294  if (!arg->isCombinable() && arg->isPresent()) {
1295  conflictingArgument = firstPresentUncombinableArg(m_mainArgs, arg);
1296  }
1297  } else {
1298  conflictingArgument = arg->conflictsWithArgument();
1299  }
1300  if (conflictingArgument) {
1301  throw Failure(argsToString("The argument \"", conflictingArgument->name(), "\" can not be combined with \"", arg->name(), "\"."));
1302  }
1303  for (size_t i = 0; i != occurrences; ++i) {
1304  if (!arg->allRequiredValuesPresent(i)) {
1305  stringstream ss(stringstream::in | stringstream::out);
1306  ss << "Not all parameter for argument \"" << arg->name() << "\" ";
1307  if (i) {
1308  ss << " (" << (i + 1) << " occurrence) ";
1309  }
1310  ss << "provided. You have to provide the following parameter:";
1311  size_t valueNamesPrint = 0;
1312  for (const auto &name : arg->m_valueNames) {
1313  ss << ' ' << name, ++valueNamesPrint;
1314  }
1315  if (arg->m_requiredValueCount != static_cast<size_t>(-1)) {
1316  while (valueNamesPrint < arg->m_requiredValueCount) {
1317  ss << "\nvalue " << (++valueNamesPrint);
1318  }
1319  }
1320  throw Failure(ss.str());
1321  }
1322  }
1323 
1324  // check contraints of sub arguments recursively
1325  checkConstraints(arg->m_subArgs);
1326  }
1327 }
1328 
1337 {
1338  for (const Argument *arg : args) {
1339  // invoke the callback for each occurrence of the argument
1340  if (arg->m_callbackFunction) {
1341  for (const auto &occurrence : arg->m_occurrences) {
1342  arg->m_callbackFunction(occurrence);
1343  }
1344  }
1345  // invoke the callbacks for sub arguments recursively
1346  invokeCallbacks(arg->m_subArgs);
1347  }
1348 }
1349 
1360  : Argument("help", 'h', "shows this information")
1361 {
1362  setCallback([&parser](const ArgumentOccurrence &) {
1364  parser.printHelp(cout);
1365  });
1366 }
1367 
1378 } // 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 the operation.
void resetArgs()
Resets all Argument instances assigned as mainArguments() and sub arguments.
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.
std::ostream & operator<<(std::ostream &os, const Wrapper &wrapper)
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.
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...
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.
#define CMD_UTILS_START_CONSOLE
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.
ArgumentParser & parser
The associated ArgumentParser instance.
const ArgumentVector & subArguments() const
Returns the secondary arguments for this argument.
size_t index
An index which is incremented when an argument is encountered (the current index is stored in the occ...
Contains utility classes helping to read and write streams.
Definition: binaryreader.h:10
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...
const std::vector< const char * > & values(std::size_t occurrence=0) const
Returns the parameter values for the specified occurrence of argument.
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 determineTerminalSize()
Returns the current size of the terminal.
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.
ArgumentVector & args
The Argument instances to store the results. Sub arguments of args are considered as well...
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...
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.
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()).
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
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.
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...
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.
void read()
Reads the commands line arguments specified when constructing the object.
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