C++ Utilities  4.9.2
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 
44 ArgumentReader::ArgumentReader(ArgumentParser &parser, const char *const *argv, const char *const *end, bool completionMode)
45  : parser(parser)
46  , args(parser.m_mainArgs)
47  , index(0)
48  , argv(argv)
49  , end(end)
50  , lastArg(nullptr)
51  , argDenotation(nullptr)
52  , completionMode(completionMode)
53 {
54 }
55 
59 ArgumentReader &ArgumentReader::reset(const char *const *argv, const char *const *end)
60 {
61  this->argv = argv;
62  this->end = end;
63  index = 0;
64  lastArg = nullptr;
65  argDenotation = nullptr;
66  return *this;
67 }
68 
74 {
75  read(args);
76 }
77 
83 {
84  // method is called recursively for sub args to the last argument (which is nullptr in the initial call) is the current parent argument
85  Argument *const parentArg = lastArg;
86  // determine the current path
87  const vector<Argument *> &parentPath = parentArg ? parentArg->path(parentArg->occurrences() - 1) : vector<Argument *>();
88 
89  Argument *lastArgInLevel = nullptr;
90  vector<const char *> *values = nullptr;
91 
92  // iterate through all argument denotations; loop might exit earlier when an denotation is unknown
93  while (argv != end) {
94  if (values && lastArgInLevel->requiredValueCount() != static_cast<size_t>(-1) && values->size() < lastArgInLevel->requiredValueCount()) {
95  // there are still values to read
96  values->emplace_back(argDenotation ? argDenotation : *argv);
97  ++index, ++argv, argDenotation = nullptr;
98  } else {
99  // determine how denotation must be processed
100  bool abbreviationFound = false;
101  if (argDenotation) {
102  // continue reading childs for abbreviation denotation already detected
103  abbreviationFound = false;
105  } else {
106  // determine denotation type
107  argDenotation = *argv;
108  if (!*argDenotation && (!lastArgInLevel || values->size() >= lastArgInLevel->requiredValueCount())) {
109  // skip empty arguments
110  ++index, ++argv, argDenotation = nullptr;
111  continue;
112  }
113  abbreviationFound = false;
116  }
117 
118  // try to find matching Argument instance
119  Argument *matchingArg = nullptr;
120  size_t argDenotationLength;
121  if (argDenotationType != Value) {
122  const char *const equationPos = strchr(argDenotation, '=');
123  for (argDenotationLength = equationPos ? static_cast<size_t>(equationPos - argDenotation) : strlen(argDenotation);
124  argDenotationLength; matchingArg = nullptr) {
125  // search for arguments by abbreviation or name depending on the previously determined denotation type
127  for (Argument *arg : args) {
128  if (arg->abbreviation() && arg->abbreviation() == *argDenotation) {
129  matchingArg = arg;
130  abbreviationFound = true;
131  break;
132  }
133  }
134  } else {
135  for (Argument *arg : args) {
136  if (arg->name() && !strncmp(arg->name(), argDenotation, argDenotationLength)
137  && *(arg->name() + argDenotationLength) == '\0') {
138  matchingArg = arg;
139  break;
140  }
141  }
142  }
143 
144  if (matchingArg) {
145  // an argument matched the specified denotation so add an occurrence
146  matchingArg->m_occurrences.emplace_back(index, parentPath, parentArg);
147 
148  // prepare reading parameter values
149  values = &matchingArg->m_occurrences.back().values;
150  if (equationPos) {
151  values->push_back(equationPos + 1);
152  }
153 
154  // read sub arguments
155  ++index, ++parser.m_actualArgc, lastArg = lastArgInLevel = matchingArg, lastArgDenotation = argv;
156  if (argDenotationType != Abbreviation || (++argDenotation != equationPos)) {
158  // no further abbreviations follow -> read sub args for next argv
159  ++argv, argDenotation = nullptr;
160  read(lastArg->m_subArgs);
161  argDenotation = nullptr;
162  } else {
163  // further abbreviations follow -> don't increment argv, keep processing outstanding chars of argDenotation
164  read(lastArg->m_subArgs);
165  }
166  break;
167  } // else: another abbreviated argument follows (and it is not present in the sub args)
168  } else {
169  break;
170  }
171  }
172  }
173 
174  if (!matchingArg) {
175  // unknown argument might be a sibling of the parent element
176  if (argDenotationType != Value) {
177  for (auto parentArgument = parentPath.crbegin(), pathEnd = parentPath.crend();; ++parentArgument) {
178  for (Argument *sibling : (parentArgument != pathEnd ? (*parentArgument)->subArguments() : parser.m_mainArgs)) {
179  if (sibling->occurrences() < sibling->maxOccurrences()) {
180  if ((argDenotationType == Abbreviation && (sibling->abbreviation() && sibling->abbreviation() == *argDenotation))
181  || (sibling->name() && !strncmp(sibling->name(), argDenotation, argDenotationLength))) {
182  return;
183  }
184  }
185  }
186  if (parentArgument == pathEnd) {
187  break;
188  }
189  };
190  }
191 
192  // unknown argument might just be a parameter value of the last argument
193  if (lastArgInLevel && values->size() < lastArgInLevel->requiredValueCount()) {
194  values->emplace_back(abbreviationFound ? argDenotation : *argv);
195  ++index, ++argv, argDenotation = nullptr;
196  continue;
197  }
198 
199  // first value might denote "operation"
200  for (Argument *arg : args) {
201  if (arg->denotesOperation() && arg->name() && !strcmp(arg->name(), *argv)) {
202  (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
204  ++index, ++argv;
205  break;
206  }
207  }
208 
209  // use the first default argument which is not already present if there is still no match
210  if (!matchingArg && (!completionMode || (argv + 1 != end))) {
211  const bool uncombinableMainArgPresent = parentArg ? false : parser.isUncombinableMainArgPresent();
212  for (Argument *arg : args) {
213  if (arg->isImplicit() && !arg->isPresent() && !arg->wouldConflictWithArgument()
214  && (!uncombinableMainArgPresent || !arg->isMainArgument())) {
215  (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
216  break;
217  }
218  }
219  }
220 
221  if (matchingArg) {
222  // an argument matched the specified denotation
223  if (lastArgInLevel == matchingArg) {
224  break; // break required? -> TODO: add test for this condition
225  }
226 
227  // prepare reading parameter values
228  values = &matchingArg->m_occurrences.back().values;
229 
230  // read sub arguments
231  ++parser.m_actualArgc, lastArg = lastArgInLevel = matchingArg, argDenotation = nullptr;
232  read(lastArg->m_subArgs);
233  argDenotation = nullptr;
234  continue;
235  }
236 
237  // argument denotation is unknown -> handle error
238  if (parentArg) {
239  // continue with parent level
240  return;
241  }
242  if (completionMode) {
243  // ignore unknown denotation
244  ++index, ++argv, argDenotation = nullptr;
245  } else {
246  switch (parser.m_unknownArgBehavior) {
248  cerr << "The specified argument \"" << *argv << "\" is unknown and will be ignored." << endl;
249  FALLTHROUGH;
251  // ignore unknown denotation
252  ++index, ++argv, argDenotation = nullptr;
253  break;
255  throw Failure(argsToString("The specified argument \"", *argv, "\" is unknown."));
256  }
257  }
258  } // if(!matchingArg)
259  } // no values to read
260  } // while(argv != end)
261 }
262 
264 const char *applicationName = nullptr;
266 const char *applicationAuthor = nullptr;
268 const char *applicationVersion = nullptr;
270 const char *applicationUrl = nullptr;
272 std::initializer_list<const char *> dependencyVersions;
273 
278 void (*exitFunction)(int) = &exit;
279 
281 
282 inline bool notEmpty(const char *str)
283 {
284  return str && *str;
285 }
286 
288 
305 Argument::Argument(const char *name, char abbreviation, const char *description, const char *example)
306  : m_name(name)
307  , m_abbreviation(abbreviation)
308  , m_environmentVar(nullptr)
309  , m_description(description)
310  , m_example(example)
311  , m_minOccurrences(0)
312  , m_maxOccurrences(1)
313  , m_combinable(false)
314  , m_denotesOperation(false)
315  , m_requiredValueCount(0)
316  , m_implicit(false)
317  , m_isMainArg(false)
320  , m_preDefinedCompletionValues(nullptr)
321 {
322 }
323 
328 {
329 }
330 
338 const char *Argument::firstValue() const
339 {
340  if (!m_occurrences.empty() && !m_occurrences.front().values.empty()) {
341  return m_occurrences.front().values.front();
342  } else if (m_environmentVar) {
343  return getenv(m_environmentVar);
344  } else {
345  return nullptr;
346  }
347 }
348 
352 void Argument::printInfo(ostream &os, unsigned char indentation) const
353 {
354  os << Indentation(indentation);
356  if (notEmpty(name())) {
357  os << '-' << '-' << name();
358  }
359  if (notEmpty(name()) && abbreviation()) {
360  os << ',' << ' ';
361  }
362  if (abbreviation()) {
363  os << '-' << abbreviation();
364  }
366  if (requiredValueCount()) {
367  unsigned int valueNamesPrint = 0;
368  for (auto i = valueNames().cbegin(), end = valueNames().cend(); i != end && valueNamesPrint < requiredValueCount(); ++i) {
369  os << ' ' << '[' << *i << ']';
370  ++valueNamesPrint;
371  }
372  if (requiredValueCount() == static_cast<size_t>(-1)) {
373  os << " ...";
374  } else {
375  for (; valueNamesPrint < requiredValueCount(); ++valueNamesPrint) {
376  os << " [value " << (valueNamesPrint + 1) << ']';
377  }
378  }
379  }
380  indentation += 2;
381  if (notEmpty(description())) {
382  os << '\n' << Indentation(indentation) << description();
383  }
384  if (isRequired()) {
385  os << '\n' << Indentation(indentation) << "particularities: mandatory";
386  if (!isMainArgument()) {
387  os << " if parent argument is present";
388  }
389  }
390  if (environmentVariable()) {
391  os << '\n' << Indentation(indentation) << "default environment variable: " << environmentVariable();
392  }
393  if (notEmpty(example())) {
394  os << '\n' << Indentation(indentation) << "\nusage: " << example();
395  }
396  os << '\n';
397  for (const auto *arg : subArguments()) {
398  arg->printInfo(os, indentation);
399  }
400 }
401 
408 {
409  for (Argument *arg : args) {
410  if (arg != except && arg->isPresent() && !arg->isCombinable()) {
411  return arg;
412  }
413  }
414  return nullptr;
415 }
416 
431 void Argument::setSubArguments(const ArgumentInitializerList &secondaryArguments)
432 {
433  // remove this argument from the parents list of the previous secondary arguments
434  for (Argument *arg : m_subArgs) {
435  arg->m_parents.erase(remove(arg->m_parents.begin(), arg->m_parents.end(), this), arg->m_parents.end());
436  }
437  // assign secondary arguments
438  m_subArgs.assign(secondaryArguments);
439  // add this argument to the parents list of the assigned secondary arguments
440  // and set the parser
441  for (Argument *arg : m_subArgs) {
442  if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
443  arg->m_parents.push_back(this);
444  }
445  }
446 }
447 
456 {
457  if (find(m_subArgs.cbegin(), m_subArgs.cend(), arg) == m_subArgs.cend()) {
458  m_subArgs.push_back(arg);
459  if (find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
460  arg->m_parents.push_back(this);
461  }
462  }
463 }
464 
470 {
471  if (isMainArgument()) {
472  return true;
473  }
474  for (const Argument *parent : m_parents) {
475  if (parent->isPresent()) {
476  return true;
477  }
478  }
479  return false;
480 }
481 
491 {
492  return isPresent() ? wouldConflictWithArgument() : nullptr;
493 }
494 
504 {
505  if (!isCombinable()) {
506  for (Argument *parent : m_parents) {
507  for (Argument *sibling : parent->subArguments()) {
508  if (sibling != this && sibling->isPresent() && !sibling->isCombinable()) {
509  return sibling;
510  }
511  }
512  }
513  }
514  return nullptr;
515 }
516 
522 {
523  for (Argument *arg : m_subArgs) {
524  arg->resetRecursively();
525  }
526  reset();
527 }
528 
546  : m_actualArgc(0)
547  , m_executable(nullptr)
548  , m_unknownArgBehavior(UnknownArgumentBehavior::Fail)
549  , m_defaultArg(nullptr)
550 {
551 }
552 
563 {
564  if (mainArguments.size()) {
565  for (Argument *arg : mainArguments) {
566  arg->m_isMainArg = true;
567  }
568  m_mainArgs.assign(mainArguments);
569  if (!m_defaultArg) {
570  if (!(*mainArguments.begin())->requiredValueCount()) {
571  bool subArgsRequired = false;
572  for (const Argument *subArg : (*mainArguments.begin())->subArguments()) {
573  if (subArg->isRequired()) {
574  subArgsRequired = true;
575  break;
576  }
577  }
578  if (!subArgsRequired) {
579  m_defaultArg = *mainArguments.begin();
580  }
581  }
582  }
583  } else {
584  m_mainArgs.clear();
585  }
586 }
587 
595 {
596  argument->m_isMainArg = true;
597  m_mainArgs.push_back(argument);
598 }
599 
603 void ArgumentParser::printHelp(ostream &os) const
604 {
606  if (applicationName && *applicationName) {
607  os << applicationName;
608  if (applicationVersion && *applicationVersion) {
609  os << ',' << ' ';
610  }
611  }
612  if (applicationVersion && *applicationVersion) {
613  os << "version " << applicationVersion;
614  }
615  if (dependencyVersions.size()) {
616  if ((applicationName && *applicationName) || (applicationVersion && *applicationVersion)) {
617  os << '\n';
619  }
620  auto i = dependencyVersions.begin(), end = dependencyVersions.end();
621  os << "Linked against: " << *i;
622  for (++i; i != end; ++i) {
623  os << ',' << ' ' << *i;
624  }
625  }
626  if ((applicationName && *applicationName) || (applicationVersion && *applicationVersion) || dependencyVersions.size()) {
627  os << '\n' << '\n';
628  }
630  if (!m_mainArgs.empty()) {
631  os << "Available arguments:";
632  for (const Argument *arg : m_mainArgs) {
633  os << '\n';
634  arg->printInfo(os);
635  }
636  }
637  if (applicationUrl && *applicationUrl) {
638  os << "\nProject website: " << applicationUrl << endl;
639  }
640 }
641 
651 void ArgumentParser::parseArgs(int argc, const char *const *argv)
652 {
653  readArgs(argc, argv);
654  if (argc) {
655  checkConstraints(m_mainArgs);
656  invokeCallbacks(m_mainArgs);
657  }
658 }
659 
669 void ArgumentParser::readArgs(int argc, const char *const *argv)
670 {
671  IF_DEBUG_BUILD(verifyArgs(m_mainArgs, std::vector<char>(), std::vector<const char *>());)
672  m_actualArgc = 0;
673  if (argc) {
674  // the first argument is the executable name
675  m_executable = *argv;
676 
677  // check for further arguments
678  if (--argc) {
679  // if the first argument (after executable name) is "--bash-completion-for" bash completion for the following arguments is requested
680  bool completionMode = !strcmp(*++argv, "--bash-completion-for");
681  unsigned int currentWordIndex;
682  if (completionMode) {
683  // the first argument after "--bash-completion-for" is the index of the current word
684  try {
685  currentWordIndex = (--argc ? stringToNumber<unsigned int, string>(*(++argv)) : 0);
686  if (argc) {
687  ++argv, --argc;
688  }
689  } catch (const ConversionException &) {
690  currentWordIndex = static_cast<unsigned int>(argc - 1);
691  }
692  }
693 
694  // read specified arguments
695  ArgumentReader reader(*this, argv,
696  argv + (completionMode ? min(static_cast<unsigned int>(argc), currentWordIndex + 1) : static_cast<unsigned int>(argc)),
697  completionMode);
698  try {
699  reader.read();
700  } catch (const Failure &) {
701  if (!completionMode) {
702  throw;
703  }
704  }
705 
706  if (completionMode) {
707  printBashCompletion(argc, argv, currentWordIndex, reader);
708  exitFunction(0); // prevent the applicaton to continue with the regular execution
709  }
710  } else {
711  // no arguments specified -> flag default argument as present if one is assigned
712  if (m_defaultArg) {
713  m_defaultArg->m_occurrences.emplace_back(0);
714  }
715  }
716  } else {
717  m_executable = nullptr;
718  }
719 }
720 
726 {
727  for (Argument *arg : m_mainArgs) {
728  arg->resetRecursively();
729  }
730  m_actualArgc = 0;
731 }
732 
737 {
738  for (const Argument *arg : m_mainArgs) {
739  if (!arg->isCombinable() && arg->isPresent()) {
740  return true;
741  }
742  }
743  return false;
744 }
745 
746 #ifdef DEBUG_BUILD
747 
763 void ApplicationUtilities::ArgumentParser::verifyArgs(const ArgumentVector &args, vector<char>, vector<const char *>)
764 {
765  vector<const Argument *> verifiedArgs;
766  verifiedArgs.reserve(args.size());
767  vector<char> abbreviations;
768  abbreviations.reserve(abbreviations.size() + args.size());
769  vector<const char *> names;
770  names.reserve(names.size() + args.size());
771  bool hasImplicit = false;
772  for (const Argument *arg : args) {
773  assert(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) == verifiedArgs.cend());
774  verifiedArgs.push_back(arg);
775  assert(!arg->isImplicit() || !hasImplicit);
776  hasImplicit |= arg->isImplicit();
777  assert(!arg->abbreviation() || find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) == abbreviations.cend());
778  abbreviations.push_back(arg->abbreviation());
779  assert(!arg->name() || find_if(names.cbegin(), names.cend(), [arg](const char *name) { return !strcmp(arg->name(), name); }) == names.cend());
780  assert(arg->requiredValueCount() == 0 || arg->subArguments().size() == 0);
781  names.emplace_back(arg->name());
782  }
783  for (const Argument *arg : args) {
784  verifyArgs(arg->subArguments(), vector<char>(), vector<const char *>());
785  }
786 }
787 #endif
788 
796 bool compareArgs(const Argument *arg1, const Argument *arg2)
797 {
798  if (arg1->denotesOperation() && !arg2->denotesOperation()) {
799  return true;
800  } else if (!arg1->denotesOperation() && arg2->denotesOperation()) {
801  return false;
802  } else {
803  return strcmp(arg1->name(), arg2->name()) < 0;
804  }
805 }
806 
811 void insertSiblings(const ArgumentVector &siblings, list<const Argument *> &target)
812 {
813  bool onlyCombinable = false;
814  for (const Argument *sibling : siblings) {
815  if (sibling->isPresent() && !sibling->isCombinable()) {
816  onlyCombinable = true;
817  break;
818  }
819  }
820  for (const Argument *sibling : siblings) {
821  if ((!onlyCombinable || sibling->isCombinable()) && sibling->occurrences() < sibling->maxOccurrences()) {
822  target.push_back(sibling);
823  }
824  }
825 }
826 
832 void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader)
833 {
834  // variables to store relevant completions (arguments, pre-defined values, files/dirs)
835  list<const Argument *> relevantArgs, relevantPreDefinedValues;
836  bool completeFiles = false, completeDirs = false, noWhitespace = false;
837 
838  // get the last argument the argument parser was able to detect successfully
839  const Argument *const lastDetectedArg = reader.lastArg;
840  size_t lastDetectedArgIndex;
841  vector<Argument *> lastDetectedArgPath;
842  if (lastDetectedArg) {
843  lastDetectedArgIndex = reader.lastArgDenotation - argv;
844  lastDetectedArgPath = lastDetectedArg->path(lastDetectedArg->occurrences() - 1);
845  }
846 
847  // determine last arg, omitting trailing empty args
848  const char *const *lastSpecifiedArg;
849  unsigned int lastSpecifiedArgIndex;
850  if (argc) {
851  lastSpecifiedArgIndex = static_cast<unsigned int>(argc) - 1;
852  lastSpecifiedArg = argv + lastSpecifiedArgIndex;
853  for (; lastSpecifiedArg >= argv && **lastSpecifiedArg == '\0'; --lastSpecifiedArg, --lastSpecifiedArgIndex)
854  ;
855  }
856 
857  // determine arguments relevant for completion
858  bool nextArgumentOrValue;
859  if (lastDetectedArg && lastDetectedArg->isPresent()) {
860  if ((nextArgumentOrValue = (currentWordIndex > lastDetectedArgIndex))) {
861  // define function to add parameter values of argument as possible completions
862  const auto addValueCompletionsForArg = [&relevantPreDefinedValues, &completeFiles, &completeDirs](const Argument *arg) {
863  if (arg->valueCompletionBehaviour() & ValueCompletionBehavior::PreDefinedValues) {
864  relevantPreDefinedValues.push_back(arg);
865  }
866  if (!(arg->valueCompletionBehaviour() & ValueCompletionBehavior::FileSystemIfNoPreDefinedValues)
867  || !arg->preDefinedCompletionValues()) {
868  completeFiles = completeFiles || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Files;
869  completeDirs = completeDirs || arg->valueCompletionBehaviour() & ValueCompletionBehavior::Directories;
870  }
871  };
872 
873  // detect number of specified values
874  auto currentValueCount = lastDetectedArg->values(lastDetectedArg->occurrences() - 1).size();
875  // ignore values which are specified after the current word
876  if (currentValueCount) {
877  const auto currentWordIndexRelativeToLastDetectedArg = currentWordIndex - lastDetectedArgIndex;
878  if (currentValueCount > currentWordIndexRelativeToLastDetectedArg) {
879  currentValueCount -= currentWordIndexRelativeToLastDetectedArg;
880  } else {
881  currentValueCount = 0;
882  }
883  }
884 
885  // add value completions for implicit child if there are no value specified and there are no values required by the
886  // last detected argument itself
887  if (!currentValueCount && !lastDetectedArg->requiredValueCount()) {
888  for (const Argument *child : lastDetectedArg->subArguments()) {
889  if (child->isImplicit() && child->requiredValueCount()) {
890  addValueCompletionsForArg(child);
891  break;
892  }
893  }
894  }
895 
896  // add value completions for last argument if there are further values required
897  if (lastDetectedArg->requiredValueCount() == Argument::varValueCount || (currentValueCount < lastDetectedArg->requiredValueCount())) {
898  addValueCompletionsForArg(lastDetectedArg);
899  }
900 
901  if (lastDetectedArg->requiredValueCount() == Argument::varValueCount
902  || lastDetectedArg->values(lastDetectedArg->occurrences() - 1).size() >= lastDetectedArg->requiredValueCount()) {
903  // sub arguments of the last arg are possible completions
904  for (const Argument *subArg : lastDetectedArg->subArguments()) {
905  if (subArg->occurrences() < subArg->maxOccurrences()) {
906  relevantArgs.push_back(subArg);
907  }
908  }
909 
910  // siblings of parents are possible completions as well
911  for (auto parentArgument = lastDetectedArgPath.crbegin(), end = lastDetectedArgPath.crend();; ++parentArgument) {
912  insertSiblings(parentArgument != end ? (*parentArgument)->subArguments() : m_mainArgs, relevantArgs);
913  if (parentArgument == end) {
914  break;
915  }
916  }
917  }
918  } else {
919  // since the argument could be detected (hopefully unambiguously?) just return it for "final completion"
920  relevantArgs.push_back(lastDetectedArg);
921  }
922 
923  } else {
924  // no arguments detected -> just use main arguments for completion
925  nextArgumentOrValue = true;
926  insertSiblings(m_mainArgs, relevantArgs);
927  }
928 
929  // read the "opening" (started but not finished argument denotation)
930  const char *opening = nullptr;
931  string compoundOpening;
932  size_t openingLen, compoundOpeningStartLen = 0;
933  unsigned char openingDenotationType = Value;
934  if (argc && nextArgumentOrValue) {
935  if (currentWordIndex < static_cast<unsigned int>(argc)) {
936  opening = argv[currentWordIndex];
937  // For some reason completions for eg. "set --values disk=1 tag=a" are splitted so the
938  // equation sign is an own argument ("set --values disk = 1 tag = a").
939  // This is not how values are treated by the argument parser. Hence the opening
940  // must be joined again. In this case only the part after the equation sign needs to be
941  // provided for completion so compoundOpeningStartLen is set to number of characters to skip.
942  size_t minCurrentWordIndex = (lastDetectedArg ? lastDetectedArgIndex : 0);
943  if (currentWordIndex > minCurrentWordIndex && !strcmp(opening, "=")) {
944  compoundOpening.reserve(compoundOpeningStartLen = strlen(argv[--currentWordIndex]) + 1);
945  compoundOpening = argv[currentWordIndex];
946  compoundOpening += '=';
947  } else if (currentWordIndex > (minCurrentWordIndex + 1) && !strcmp(argv[currentWordIndex - 1], "=")) {
948  compoundOpening.reserve((compoundOpeningStartLen = strlen(argv[currentWordIndex -= 2]) + 1) + strlen(opening));
949  compoundOpening = argv[currentWordIndex];
950  compoundOpening += '=';
951  compoundOpening += opening;
952  }
953  if (!compoundOpening.empty()) {
954  opening = compoundOpening.data();
955  }
956  } else {
957  opening = *lastSpecifiedArg;
958  }
959  *opening == '-' && (++opening, ++openingDenotationType) && *opening == '-' && (++opening, ++openingDenotationType);
960  openingLen = strlen(opening);
961  }
962 
963  relevantArgs.sort(compareArgs);
964 
965  // print "COMPREPLY" bash array
966  cout << "COMPREPLY=(";
967  // -> completions for parameter values
968  for (const Argument *arg : relevantPreDefinedValues) {
969  if (arg->preDefinedCompletionValues()) {
970  bool appendEquationSign = arg->valueCompletionBehaviour() & ValueCompletionBehavior::AppendEquationSign;
971  if (argc && currentWordIndex <= lastSpecifiedArgIndex && opening) {
972  if (openingDenotationType == Value) {
973  bool wordStart = true, ok = false, equationSignAlreadyPresent = false;
974  size_t wordIndex = 0;
975  for (const char *i = arg->preDefinedCompletionValues(), *end = opening + openingLen; *i;) {
976  if (wordStart) {
977  const char *i1 = i, *i2 = opening;
978  for (; *i1 && i2 != end && *i1 == *i2; ++i1, ++i2)
979  ;
980  if ((ok = (i2 == end))) {
981  cout << '\'';
982  }
983  wordStart = false;
984  wordIndex = 0;
985  } else if ((wordStart = (*i == ' ') || (*i == '\n'))) {
986  equationSignAlreadyPresent = false;
987  if (ok) {
988  cout << '\'' << ' ';
989  }
990  ++i;
991  continue;
992  } else if (*i == '=') {
993  equationSignAlreadyPresent = true;
994  }
995  if (ok) {
996  if (!compoundOpeningStartLen || wordIndex >= compoundOpeningStartLen) {
997  if (*i == '\'') {
998  cout << "'\"'\"'";
999  } else {
1000  cout << *i;
1001  }
1002  }
1003  ++i, ++wordIndex;
1004  switch (*i) {
1005  case ' ':
1006  case '\n':
1007  case '\0':
1008  if (appendEquationSign && !equationSignAlreadyPresent) {
1009  cout << '=';
1010  noWhitespace = true;
1011  equationSignAlreadyPresent = false;
1012  }
1013  if (*i == '\0') {
1014  cout << '\'';
1015  }
1016  }
1017  } else {
1018  ++i;
1019  }
1020  }
1021  cout << ' ';
1022  }
1023  } else if (const char *i = arg->preDefinedCompletionValues()) {
1024  bool equationSignAlreadyPresent = false;
1025  cout << '\'';
1026  while (*i) {
1027  if (*i == '\'') {
1028  cout << "'\"'\"'";
1029  } else {
1030  cout << *i;
1031  }
1032  switch (*(++i)) {
1033  case '=':
1034  equationSignAlreadyPresent = true;
1035  break;
1036  case ' ':
1037  case '\n':
1038  case '\0':
1039  if (appendEquationSign && !equationSignAlreadyPresent) {
1040  cout << '=';
1041  equationSignAlreadyPresent = false;
1042  }
1043  if (*i != '\0') {
1044  cout << '\'';
1045  if (*(++i)) {
1046  cout << ' ' << '\'';
1047  }
1048  }
1049  }
1050  }
1051  cout << '\'' << ' ';
1052  }
1053  }
1054  }
1055  // -> completions for further arguments
1056  for (const Argument *arg : relevantArgs) {
1057  if (argc && currentWordIndex <= lastSpecifiedArgIndex && opening) {
1058  switch (openingDenotationType) {
1059  case Value:
1060  if (!arg->denotesOperation() || strncmp(arg->name(), opening, openingLen)) {
1061  continue;
1062  }
1063  break;
1064  case Abbreviation:
1065  break;
1066  case FullName:
1067  if (strncmp(arg->name(), opening, openingLen)) {
1068  continue;
1069  }
1070  }
1071  }
1072 
1073  if (opening && openingDenotationType == Abbreviation && !nextArgumentOrValue) {
1074  cout << '\'' << '-' << opening << arg->abbreviation() << '\'' << ' ';
1075  } else if (lastDetectedArg && reader.argDenotationType == Abbreviation && !nextArgumentOrValue) {
1076  if (reader.argv == reader.end) {
1077  cout << '\'' << *(reader.argv - 1) << '\'' << ' ';
1078  }
1079  } else if (arg->denotesOperation()) {
1080  cout << '\'' << arg->name() << '\'' << ' ';
1081  } else {
1082  cout << '\'' << '-' << '-' << arg->name() << '\'' << ' ';
1083  }
1084  }
1085  // -> completions for files and dirs
1086  // -> if there's already an "opening", determine the dir part and the file part
1087  string actualDir, actualFile;
1088  bool haveFileOrDirCompletions = false;
1089  if (argc && currentWordIndex == lastSpecifiedArgIndex && opening) {
1090  // the "opening" might contain escaped characters which need to be unescaped first (let's hope this covers all possible escapings)
1091  string unescapedOpening(opening);
1092  findAndReplace<string>(unescapedOpening, "\\ ", " ");
1093  findAndReplace<string>(unescapedOpening, "\\,", ",");
1094  findAndReplace<string>(unescapedOpening, "\\[", "[");
1095  findAndReplace<string>(unescapedOpening, "\\]", "]");
1096  findAndReplace<string>(unescapedOpening, "\\!", "!");
1097  findAndReplace<string>(unescapedOpening, "\\#", "#");
1098  findAndReplace<string>(unescapedOpening, "\\$", "$");
1099  findAndReplace<string>(unescapedOpening, "\\'", "'");
1100  findAndReplace<string>(unescapedOpening, "\\\"", "\"");
1101  findAndReplace<string>(unescapedOpening, "\\\\", "\\");
1102  // determine the "directory" part
1103  string dir = directory(unescapedOpening);
1104  if (dir.empty()) {
1105  actualDir = ".";
1106  } else {
1107  if (dir[0] == '\"' || dir[0] == '\'') {
1108  dir.erase(0, 1);
1109  }
1110  if (dir.size() > 1 && (dir[dir.size() - 2] == '\"' || dir[dir.size() - 2] == '\'')) {
1111  dir.erase(dir.size() - 2, 1);
1112  }
1113  actualDir = move(dir);
1114  }
1115  // determine the "file" part
1116  string file = fileName(unescapedOpening);
1117  if (file[0] == '\"' || file[0] == '\'') {
1118  file.erase(0, 1);
1119  }
1120  if (file.size() > 1 && (file[dir.size() - 2] == '\"' || dir[file.size() - 2] == '\'')) {
1121  file.erase(file.size() - 2, 1);
1122  }
1123  actualFile = move(file);
1124  }
1125 
1126  // -> completion for files and dirs
1128  if (completeFiles) {
1129  entryTypes |= DirectoryEntryType::File;
1130  }
1131  if (completeDirs) {
1132  entryTypes |= DirectoryEntryType::Directory;
1133  }
1134  if (entryTypes != DirectoryEntryType::None) {
1135  const string replace("'"), with("'\"'\"'");
1136  if (argc && currentWordIndex <= lastSpecifiedArgIndex && opening) {
1137  list<string> entries = directoryEntries(actualDir.c_str(), entryTypes);
1138  findAndReplace(actualDir, replace, with);
1139  for (string &dirEntry : entries) {
1140  if (startsWith(dirEntry, actualFile)) {
1141  cout << '\'';
1142  if (actualDir != ".") {
1143  cout << actualDir;
1144  }
1145  findAndReplace(dirEntry, replace, with);
1146  cout << dirEntry << '\'' << ' ';
1147  haveFileOrDirCompletions = true;
1148  }
1149  }
1150  } else {
1151  for (string &dirEntry : directoryEntries(".", entryTypes)) {
1152  findAndReplace(dirEntry, replace, with);
1153  cout << '\'' << dirEntry << '\'' << ' ';
1154  haveFileOrDirCompletions = true;
1155  }
1156  }
1157  }
1158  cout << ')';
1159 
1160  // ensure file or dir completions are formatted appropriately
1161  if (haveFileOrDirCompletions) {
1162  cout << "; compopt -o filenames";
1163  }
1164 
1165  // ensure trailing whitespace is ommitted
1166  if (noWhitespace) {
1167  cout << "; compopt -o nospace";
1168  }
1169 
1170  cout << endl;
1171 }
1172 
1178 {
1179  for (const Argument *arg : args) {
1180  const auto occurrences = arg->occurrences();
1181  if (arg->isParentPresent() && occurrences > arg->maxOccurrences()) {
1182  throw Failure(argsToString("The argument \"", arg->name(), "\" mustn't be specified more than ", arg->maxOccurrences(),
1183  (arg->maxOccurrences() == 1 ? " time." : " times.")));
1184  }
1185  if (arg->isParentPresent() && occurrences < arg->minOccurrences()) {
1186  throw Failure(argsToString("The argument \"", arg->name(), "\" must be specified at least ", arg->minOccurrences(),
1187  (arg->minOccurrences() == 1 ? " time." : " times.")));
1188  }
1189  Argument *conflictingArgument = nullptr;
1190  if (arg->isMainArgument()) {
1191  if (!arg->isCombinable() && arg->isPresent()) {
1192  conflictingArgument = firstPresentUncombinableArg(m_mainArgs, arg);
1193  }
1194  } else {
1195  conflictingArgument = arg->conflictsWithArgument();
1196  }
1197  if (conflictingArgument) {
1198  throw Failure(argsToString("The argument \"", conflictingArgument->name(), "\" can not be combined with \"", arg->name(), "\"."));
1199  }
1200  for (size_t i = 0; i != occurrences; ++i) {
1201  if (!arg->allRequiredValuesPresent(i)) {
1202  stringstream ss(stringstream::in | stringstream::out);
1203  ss << "Not all parameter for argument \"" << arg->name() << "\" ";
1204  if (i) {
1205  ss << " (" << (i + 1) << " occurrence) ";
1206  }
1207  ss << "provided. You have to provide the following parameter:";
1208  size_t valueNamesPrint = 0;
1209  for (const auto &name : arg->m_valueNames) {
1210  ss << ' ' << name, ++valueNamesPrint;
1211  }
1212  if (arg->m_requiredValueCount != static_cast<size_t>(-1)) {
1213  while (valueNamesPrint < arg->m_requiredValueCount) {
1214  ss << "\nvalue " << (++valueNamesPrint);
1215  }
1216  }
1217  throw Failure(ss.str());
1218  }
1219  }
1220 
1221  // check contraints of sub arguments recursively
1222  checkConstraints(arg->m_subArgs);
1223  }
1224 }
1225 
1234 {
1235  for (const Argument *arg : args) {
1236  // invoke the callback for each occurrence of the argument
1237  if (arg->m_callbackFunction) {
1238  for (const auto &occurrence : arg->m_occurrences) {
1239  arg->m_callbackFunction(occurrence);
1240  }
1241  }
1242  // invoke the callbacks for sub arguments recursively
1243  invokeCallbacks(arg->m_subArgs);
1244  }
1245 }
1246 
1257  : Argument("help", 'h', "shows this information")
1258 {
1259  setCallback([&parser](const ArgumentOccurrence &) {
1261  parser.printHelp(cout);
1262  });
1263 }
1264 
1275 }
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.
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...
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.
#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.
CPP_UTILITIES_EXPORT void(* exitFunction)(int)
Specifies a function quit the application.
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()).
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:11
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).
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
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.
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