C++ Utilities  4.6.1
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/stringconversion.h"
7 #include "../conversion/stringbuilder.h"
8 #include "../io/path.h"
9 #include "../io/ansiescapecodes.h"
10 
11 #include <algorithm>
12 #include <iostream>
13 #include <string>
14 #include <sstream>
15 #include <cstring>
16 #include <cstdlib>
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 
58 ArgumentReader &ArgumentReader::reset(const char *const *argv, const char *const *end)
59 {
60  this->argv = argv;
61  this->end = end;
62  index = 0;
63  lastArg = nullptr;
64  argDenotation = nullptr;
65  return *this;
66 }
67 
73 {
74  read(args);
75 }
76 
82 {
83  // method is called recursively for sub args to the last argument (which is nullptr in the initial call) is the current parent argument
84  Argument *const parentArg = lastArg;
85  // determine the current path
86  const vector<Argument *> &parentPath = parentArg ? parentArg->path(parentArg->occurrences() - 1) : vector<Argument *>();
87 
88  Argument *lastArgInLevel = nullptr;
89  vector<const char *> *values = nullptr;
90 
91  // iterate through all argument denotations; loop might exit earlier when an denotation is unknown
92  while(argv != end) {
93  if(values && lastArgInLevel->requiredValueCount() != static_cast<size_t>(-1) && values->size() < lastArgInLevel->requiredValueCount()) {
94  // there are still values to read
95  values->emplace_back(argDenotation ? argDenotation : *argv);
96  ++index, ++argv, argDenotation = nullptr;
97  } else {
98  // determine how denotation must be processed
99  bool abbreviationFound = false;
100  if(argDenotation) {
101  // continue reading childs for abbreviation denotation already detected
102  abbreviationFound = false;
104  } else {
105  // determine denotation type
106  argDenotation = *argv;
107  if(!*argDenotation && (!lastArgInLevel || values->size() >= lastArgInLevel->requiredValueCount())) {
108  // skip empty arguments
109  ++index, ++argv, argDenotation = nullptr;
110  continue;
111  }
112  abbreviationFound = false;
115  && *argDenotation == '-' && (++argDenotation, ++argDenotationType);
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); argDenotationLength; matchingArg = nullptr) {
124  // search for arguments by abbreviation or name depending on the previously determined denotation type
126  for(Argument *arg : args) {
127  if(arg->abbreviation() && arg->abbreviation() == *argDenotation) {
128  matchingArg = arg;
129  abbreviationFound = true;
130  break;
131  }
132  }
133  } else {
134  for(Argument *arg : args) {
135  if(arg->name() && !strncmp(arg->name(), argDenotation, argDenotationLength) && *(arg->name() + argDenotationLength) == '\0') {
136  matchingArg = arg;
137  break;
138  }
139  }
140  }
141 
142  if(matchingArg) {
143  // an argument matched the specified denotation so add an occurrence
144  matchingArg->m_occurrences.emplace_back(index, parentPath, parentArg);
145 
146  // prepare reading parameter values
147  values = &matchingArg->m_occurrences.back().values;
148  if(equationPos) {
149  values->push_back(equationPos + 1);
150  }
151 
152  // read sub arguments
153  ++index, ++parser.m_actualArgc, lastArg = lastArgInLevel = matchingArg, lastArgDenotation = argv;
154  if(argDenotationType != Abbreviation || (++argDenotation != equationPos)) {
156  // no further abbreviations follow -> read sub args for next argv
157  ++argv, argDenotation = nullptr;
158  read(lastArg->m_subArgs);
159  argDenotation = nullptr;
160  } else {
161  // further abbreviations follow -> don't increment argv, keep processing outstanding chars of argDenotation
162  read(lastArg->m_subArgs);
163  }
164  break;
165  } // else: another abbreviated argument follows (and it is not present in the sub args)
166  } else {
167  break;
168  }
169  }
170  }
171 
172  if(!matchingArg) {
173  // unknown argument might be a sibling of the parent element
174  if(argDenotationType != Value) {
175  for(auto parentArgument = parentPath.crbegin(), pathEnd = parentPath.crend(); ; ++parentArgument) {
176  for(Argument *sibling : (parentArgument != pathEnd ? (*parentArgument)->subArguments() : parser.m_mainArgs)) {
177  if(sibling->occurrences() < sibling->maxOccurrences()) {
178  if((argDenotationType == Abbreviation && (sibling->abbreviation() && sibling->abbreviation() == *argDenotation))
179  || (sibling->name() && !strncmp(sibling->name(), argDenotation, argDenotationLength))) {
180  return;
181  }
182  }
183  }
184  if(parentArgument == pathEnd) {
185  break;
186  }
187  };
188  }
189 
190  // unknown argument might just be a parameter value of the last argument
191  if(lastArgInLevel && values->size() < lastArgInLevel->requiredValueCount()) {
192  values->emplace_back(abbreviationFound ? argDenotation : *argv);
193  ++index, ++argv, argDenotation = nullptr;
194  continue;
195  }
196 
197  // first value might denote "operation"
198  if(!index) {
199  for(Argument *arg : args) {
200  if(arg->denotesOperation() && arg->name() && !strcmp(arg->name(), *argv)) {
201  (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
203  ++index, ++argv;
204  break;
205  }
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() && (!uncombinableMainArgPresent || !arg->isMainArgument())) {
214  (matchingArg = arg)->m_occurrences.emplace_back(index, parentPath, parentArg);
215  break;
216  }
217  }
218  }
219 
220  if(matchingArg) {
221  // an argument matched the specified denotation
222  if(lastArgInLevel == matchingArg) {
223  break; // break required? -> TODO: add test for this condition
224  }
225 
226  // prepare reading parameter values
227  values = &matchingArg->m_occurrences.back().values;
228 
229  // read sub arguments
230  ++parser.m_actualArgc, lastArg = lastArgInLevel = matchingArg, argDenotation = nullptr;
231  read(lastArg->m_subArgs);
232  argDenotation = nullptr;
233  continue;
234  }
235 
236  // argument denotation is unknown -> handle error
237  if(parentArg) {
238  // continue with parent level
239  return;
240  }
241  if(completionMode) {
242  // ignore unknown denotation
243  ++index, ++argv, argDenotation = nullptr;
244  } else {
245  switch(parser.m_unknownArgBehavior) {
247  cerr << "The specified argument \"" << *argv << "\" is unknown and will be ignored." << endl;
248  FALLTHROUGH;
250  // ignore unknown denotation
251  ++index, ++argv, argDenotation = nullptr;
252  break;
254  throw Failure("The specified argument \""s % *argv + "\" is unknown and will be ignored."s);
255  }
256  }
257  } // if(!matchingArg)
258  } // no values to read
259  } // while(argv != end)
260 }
261 
263 const char *applicationName = nullptr;
265 const char *applicationAuthor = nullptr;
267 const char *applicationVersion = nullptr;
269 const char *applicationUrl = nullptr;
270 
275 void(*exitFunction)(int) = &exit;
276 
278 
279 inline bool notEmpty(const char *str)
280 {
281  return str && *str;
282 }
283 
285 
302 Argument::Argument(const char *name, char abbreviation, const char *description, const char *example) :
303  m_name(name),
304  m_abbreviation(abbreviation),
305  m_environmentVar(nullptr),
306  m_description(description),
307  m_example(example),
308  m_minOccurrences(0),
309  m_maxOccurrences(1),
310  m_combinable(false),
311  m_denotesOperation(false),
312  m_requiredValueCount(0),
313  m_implicit(false),
314  m_isMainArg(false),
316  m_preDefinedCompletionValues(nullptr)
317 {}
318 
323 {}
324 
332 const char *Argument::firstValue() const
333 {
334  if(!m_occurrences.empty() && !m_occurrences.front().values.empty()) {
335  return m_occurrences.front().values.front();
336  } else if(m_environmentVar) {
337  return getenv(m_environmentVar);
338  } else {
339  return nullptr;
340  }
341 }
342 
346 void Argument::printInfo(ostream &os, unsigned char indentation) const
347 {
348  os << Indentation(indentation);
350  if(notEmpty(name())) {
351  os << '-' << '-' << name();
352  }
353  if(notEmpty(name()) && abbreviation()) {
354  os << ',' << ' ';
355  }
356  if(abbreviation()) {
357  os << '-' << abbreviation();
358  }
360  if(requiredValueCount()) {
361  unsigned int valueNamesPrint = 0;
362  for(auto i = valueNames().cbegin(), end = valueNames().cend(); i != end && valueNamesPrint < requiredValueCount(); ++i) {
363  os << ' ' << '[' << *i << ']';
364  ++valueNamesPrint;
365  }
366  if(requiredValueCount() == static_cast<size_t>(-1)) {
367  os << " ...";
368  } else {
369  for(; valueNamesPrint < requiredValueCount(); ++valueNamesPrint) {
370  os << " [value " << (valueNamesPrint + 1) << ']';
371  }
372  }
373  }
374  indentation += 2;
375  if(notEmpty(description())) {
376  os << '\n' << Indentation(indentation) << description();
377  }
378  if(isRequired()) {
379  os << '\n' << Indentation(indentation) << "particularities: mandatory";
380  if(!isMainArgument()) {
381  os << " if parent argument is present";
382  }
383  }
384  if(environmentVariable()) {
385  os << '\n' << Indentation(indentation) << "default environment variable: " << environmentVariable();
386  }
387  if(notEmpty(example())) {
388  os << '\n' << Indentation(indentation) << "\nusage: " << example();
389  }
390  os << '\n';
391  for(const auto *arg : subArguments()) {
392  arg->printInfo(os, indentation);
393  }
394 }
395 
402 {
403  for(Argument *arg : args) {
404  if(arg != except && arg->isPresent() && !arg->isCombinable()) {
405  return arg;
406  }
407  }
408  return nullptr;
409 }
410 
425 void Argument::setSubArguments(const ArgumentInitializerList &secondaryArguments)
426 {
427  // remove this argument from the parents list of the previous secondary arguments
428  for(Argument *arg : m_subArgs) {
429  arg->m_parents.erase(remove(arg->m_parents.begin(), arg->m_parents.end(), this), arg->m_parents.end());
430  }
431  // assign secondary arguments
432  m_subArgs.assign(secondaryArguments);
433  // add this argument to the parents list of the assigned secondary arguments
434  // and set the parser
435  for(Argument *arg : m_subArgs) {
436  if(find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
437  arg->m_parents.push_back(this);
438  }
439  }
440 }
441 
450 {
451  if(find(m_subArgs.cbegin(), m_subArgs.cend(), arg) == m_subArgs.cend()) {
452  m_subArgs.push_back(arg);
453  if(find(arg->m_parents.cbegin(), arg->m_parents.cend(), this) == arg->m_parents.cend()) {
454  arg->m_parents.push_back(this);
455  }
456  }
457 }
458 
464 {
465  if(isMainArgument()) {
466  return true;
467  }
468  for(const Argument *parent : m_parents) {
469  if(parent->isPresent()) {
470  return true;
471  }
472  }
473  return false;
474 }
475 
485 {
486  return isPresent() ? wouldConflictWithArgument() : nullptr;
487 }
488 
498 {
499  if(!isCombinable()) {
500  for(Argument *parent : m_parents) {
501  for(Argument *sibling : parent->subArguments()) {
502  if(sibling != this && sibling->isPresent() && !sibling->isCombinable()) {
503  return sibling;
504  }
505  }
506  }
507  }
508  return nullptr;
509 }
510 
516 {
517  for(Argument *arg : m_subArgs) {
518  arg->resetRecursively();
519  }
520  reset();
521 }
522 
540  m_actualArgc(0),
541  m_executable(nullptr),
542  m_unknownArgBehavior(UnknownArgumentBehavior::Fail),
543  m_defaultArg(nullptr)
544 {}
545 
556 {
557  if(mainArguments.size()) {
558  for(Argument *arg : mainArguments) {
559  arg->m_isMainArg = true;
560  }
561  m_mainArgs.assign(mainArguments);
562  if(!m_defaultArg) {
563  if(!(*mainArguments.begin())->requiredValueCount()) {
564  bool subArgsRequired = false;
565  for(const Argument *subArg : (*mainArguments.begin())->subArguments()) {
566  if(subArg->isRequired()) {
567  subArgsRequired = true;
568  break;
569  }
570  }
571  if(!subArgsRequired) {
572  m_defaultArg = *mainArguments.begin();
573  }
574  }
575  }
576  } else {
577  m_mainArgs.clear();
578  }
579 }
580 
588 {
589  argument->m_isMainArg = true;
590  m_mainArgs.push_back(argument);
591 }
592 
596 void ArgumentParser::printHelp(ostream &os) const
597 {
599  if(applicationName && *applicationName) {
600  os << applicationName;
601  if(applicationVersion && *applicationVersion) {
602  os << ',' << ' ';
603  }
604  }
605  if(applicationVersion && *applicationVersion) {
606  os << "version " << applicationVersion;
607  }
608  if((applicationName && *applicationName) || (applicationVersion && *applicationVersion)) {
609  os << '\n' << '\n';
610  }
612  if(!m_mainArgs.empty()) {
613  os << "Available arguments:";
614  for(const Argument *arg : m_mainArgs) {
615  os << '\n';
616  arg->printInfo(os);
617  }
618  }
619  if(applicationUrl && *applicationUrl) {
620  os << "\nProject website: " << applicationUrl << endl;
621  }
622 }
623 
633 void ArgumentParser::parseArgs(int argc, const char *const *argv)
634 {
635  readArgs(argc, argv);
636  if(argc) {
637  checkConstraints(m_mainArgs);
638  invokeCallbacks(m_mainArgs);
639  }
640 }
641 
651 void ArgumentParser::readArgs(int argc, const char * const *argv)
652 {
653  IF_DEBUG_BUILD(verifyArgs(m_mainArgs, std::vector<char>(), std::vector<const char *>());)
654  m_actualArgc = 0;
655  if(argc) {
656  // the first argument is the executable name
657  m_executable = *argv;
658 
659  // check for further arguments
660  if(--argc) {
661  // if the first argument (after executable name) is "--bash-completion-for" bash completion for the following arguments is requested
662  bool completionMode = !strcmp(*++argv, "--bash-completion-for");
663  unsigned int currentWordIndex;
664  if(completionMode) {
665  // the first argument after "--bash-completion-for" is the index of the current word
666  try {
667  currentWordIndex = (--argc ? stringToNumber<unsigned int, string>(*(++argv)) : 0);
668  ++argv, --argc;
669  } catch(const ConversionException &) {
670  currentWordIndex = static_cast<unsigned int>(argc - 1);
671  }
672  }
673 
674  // read specified arguments
675  ArgumentReader reader(*this, argv, argv + (completionMode ? min(static_cast<unsigned int>(argc), currentWordIndex + 1) : static_cast<unsigned int>(argc)), completionMode);
676  try {
677  reader.read();
678  } catch(const Failure &) {
679  if(!completionMode) {
680  throw;
681  }
682  }
683 
684  if(completionMode) {
685  printBashCompletion(argc, argv, currentWordIndex, reader);
686  exitFunction(0); // prevent the applicaton to continue with the regular execution
687  }
688  } else {
689  // no arguments specified -> flag default argument as present if one is assigned
690  if(m_defaultArg) {
691  m_defaultArg->m_occurrences.emplace_back(0);
692  }
693  }
694  } else {
695  m_executable = nullptr;
696  }
697 }
698 
704 {
705  for(Argument *arg : m_mainArgs) {
706  arg->resetRecursively();
707  }
708  m_actualArgc = 0;
709 }
710 
715 {
716  for(const Argument *arg : m_mainArgs) {
717  if(!arg->isCombinable() && arg->isPresent()) {
718  return true;
719  }
720  }
721  return false;
722 }
723 
724 #ifdef DEBUG_BUILD
725 
739 void ApplicationUtilities::ArgumentParser::verifyArgs(const ArgumentVector &args, vector<char> abbreviations, vector<const char *> names)
740 {
741  vector<const Argument *> verifiedArgs;
742  verifiedArgs.reserve(args.size());
743  abbreviations.reserve(abbreviations.size() + args.size());
744  names.reserve(names.size() + args.size());
745  bool hasImplicit = false;
746  for(const Argument *arg : args) {
747  assert(find(verifiedArgs.cbegin(), verifiedArgs.cend(), arg) == verifiedArgs.cend());
748  verifiedArgs.push_back(arg);
749  assert(arg->isMainArgument() || !arg->denotesOperation());
750  assert(!arg->isImplicit() || !hasImplicit);
751  hasImplicit |= arg->isImplicit();
752  assert(!arg->abbreviation() || find(abbreviations.cbegin(), abbreviations.cend(), arg->abbreviation()) == abbreviations.cend());
753  abbreviations.push_back(arg->abbreviation());
754  assert(!arg->name() || find_if(names.cbegin(), names.cend(), [arg] (const char *name) { return !strcmp(arg->name(), name); }) == names.cend());
755  assert(arg->requiredValueCount() == 0 || arg->subArguments().size() == 0);
756  names.emplace_back(arg->name());
757  }
758  for(const Argument *arg : args) {
759  verifyArgs(arg->subArguments(), abbreviations, names);
760  }
761 }
762 #endif
763 
771 bool compareArgs(const Argument *arg1, const Argument *arg2)
772 {
773  if(arg1->denotesOperation() && !arg2->denotesOperation()) {
774  return true;
775  } else if(!arg1->denotesOperation() && arg2->denotesOperation()) {
776  return false;
777  } else {
778  return strcmp(arg1->name(), arg2->name()) < 0;
779  }
780 }
781 
786 void insertSiblings(const ArgumentVector &siblings, list<const Argument *> &target)
787 {
788  bool onlyCombinable = false;
789  for(const Argument *sibling : siblings) {
790  if(sibling->isPresent() && !sibling->isCombinable()) {
791  onlyCombinable = true;
792  break;
793  }
794  }
795  for(const Argument *sibling : siblings) {
796  if((!onlyCombinable || sibling->isCombinable()) && sibling->occurrences() < sibling->maxOccurrences()) {
797  target.push_back(sibling);
798  }
799  }
800 }
801 
807 void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsigned int currentWordIndex, const ArgumentReader &reader)
808 {
809  // variables to store relevant completions (arguments, pre-defined values, files/dirs)
810  list<const Argument *> relevantArgs, relevantPreDefinedValues;
811  bool completeFiles = false, completeDirs = false, noWhitespace = false;
812 
813  // get the last argument the argument parser was able to detect successfully
814  const Argument *const lastDetectedArg = reader.lastArg;
815  size_t lastDetectedArgIndex;
816  vector<Argument *> lastDetectedArgPath;
817  if(lastDetectedArg) {
818  lastDetectedArgIndex = reader.lastArgDenotation - argv;
819  lastDetectedArgPath = lastDetectedArg->path(lastDetectedArg->occurrences() - 1);
820  }
821 
822  // determine last arg, omitting trailing empty args
823  const char *const *lastSpecifiedArg;
824  unsigned int lastSpecifiedArgIndex;
825  if(argc) {
826  lastSpecifiedArgIndex = static_cast<unsigned int>(argc) - 1;
827  lastSpecifiedArg = argv + lastSpecifiedArgIndex;
828  for(; lastSpecifiedArg >= argv && **lastSpecifiedArg == '\0'; --lastSpecifiedArg, --lastSpecifiedArgIndex);
829  }
830 
831  // determine arguments relevant for completion
832  bool nextArgumentOrValue;
833  if(lastDetectedArg && lastDetectedArg->isPresent()) {
834  if((nextArgumentOrValue = (currentWordIndex > lastDetectedArgIndex))) {
835  // parameter values of the last arg are possible completions
836  auto currentValueCount = lastDetectedArg->values(lastDetectedArg->occurrences() - 1).size();
837  if(currentValueCount) {
838  currentValueCount -= (currentWordIndex - lastDetectedArgIndex);
839  }
840  if(lastDetectedArg->requiredValueCount() == static_cast<size_t>(-1) || (currentValueCount < lastDetectedArg->requiredValueCount())) {
842  relevantPreDefinedValues.push_back(lastDetectedArg);
843  }
845  completeFiles = completeFiles || lastDetectedArg->valueCompletionBehaviour() & ValueCompletionBehavior::Files;
846  completeDirs = completeDirs || lastDetectedArg->valueCompletionBehaviour() & ValueCompletionBehavior::Directories;
847  }
848  }
849 
850  if(lastDetectedArg->requiredValueCount() == static_cast<size_t>(-1) || lastDetectedArg->values(lastDetectedArg->occurrences() - 1).size() >= lastDetectedArg->requiredValueCount()) {
851  // sub arguments of the last arg are possible completions
852  for(const Argument *subArg : lastDetectedArg->subArguments()) {
853  if(subArg->occurrences() < subArg->maxOccurrences()) {
854  relevantArgs.push_back(subArg);
855  }
856  }
857 
858  // siblings of parents are possible completions as well
859  for(auto parentArgument = lastDetectedArgPath.crbegin(), end = lastDetectedArgPath.crend(); ; ++parentArgument) {
860  insertSiblings(parentArgument != end ? (*parentArgument)->subArguments() : m_mainArgs, relevantArgs);
861  if(parentArgument == end) {
862  break;
863  }
864  }
865  }
866  } else {
867  // since the argument could be detected (hopefully unambiguously?) just return it for "final completion"
868  relevantArgs.push_back(lastDetectedArg);
869  }
870 
871  } else {
872  // no arguments detected -> just use main arguments for completion
873  nextArgumentOrValue = true;
874  insertSiblings(m_mainArgs, relevantArgs);
875  }
876 
877  // read the "opening" (started but not finished argument denotation)
878  const char *opening = nullptr;
879  string compoundOpening;
880  size_t openingLen, compoundOpeningStartLen = 0;
881  unsigned char openingDenotationType = Value;
882  if(argc && nextArgumentOrValue) {
883  if(currentWordIndex < static_cast<unsigned int>(argc)) {
884  opening = argv[currentWordIndex];
885  // For some reason completions for eg. "set --values disk=1 tag=a" are splitted so the
886  // equation sign is an own argument ("set --values disk = 1 tag = a").
887  // This is not how values are treated by the argument parser. Hence the opening
888  // must be joined again. In this case only the part after the equation sign needs to be
889  // provided for completion so compoundOpeningStartLen is set to number of characters to skip.
890  size_t minCurrentWordIndex = (lastDetectedArg ? lastDetectedArgIndex : 0);
891  if(currentWordIndex > minCurrentWordIndex && !strcmp(opening, "=")) {
892  compoundOpening.reserve(compoundOpeningStartLen = strlen(argv[--currentWordIndex]) + 1);
893  compoundOpening = argv[currentWordIndex];
894  compoundOpening += '=';
895  } else if(currentWordIndex > (minCurrentWordIndex + 1) && !strcmp(argv[currentWordIndex - 1], "=")) {
896  compoundOpening.reserve((compoundOpeningStartLen = strlen(argv[currentWordIndex -= 2]) + 1) + strlen(opening));
897  compoundOpening = argv[currentWordIndex];
898  compoundOpening += '=';
899  compoundOpening += opening;
900  }
901  if(!compoundOpening.empty()) {
902  opening = compoundOpening.data();
903  }
904  } else {
905  opening = *lastSpecifiedArg;
906  }
907  *opening == '-' && (++opening, ++openingDenotationType)
908  && *opening == '-' && (++opening, ++openingDenotationType);
909  openingLen = strlen(opening);
910  }
911 
912  relevantArgs.sort(compareArgs);
913 
914  // print "COMPREPLY" bash array
915  cout << "COMPREPLY=(";
916  // -> completions for parameter values
917  for(const Argument *arg : relevantPreDefinedValues) {
918  if(arg->preDefinedCompletionValues()) {
919  bool appendEquationSign = arg->valueCompletionBehaviour() & ValueCompletionBehavior::AppendEquationSign;
920  if(argc && currentWordIndex <= lastSpecifiedArgIndex && opening) {
921  if(openingDenotationType == Value) {
922  bool wordStart = true, ok = false, equationSignAlreadyPresent = false;
923  size_t wordIndex = 0;
924  for(const char *i = arg->preDefinedCompletionValues(), *end = opening + openingLen; *i;) {
925  if(wordStart) {
926  const char *i1 = i, *i2 = opening;
927  for(; *i1 && i2 != end && *i1 == *i2; ++i1, ++i2);
928  if((ok = (i2 == end))) {
929  cout << '\'';
930  }
931  wordStart = false;
932  wordIndex = 0;
933  } else if((wordStart = (*i == ' ') || (*i == '\n'))) {
934  equationSignAlreadyPresent = false;
935  if(ok) {
936  cout << '\'' << ' ';
937  }
938  ++i;
939  continue;
940  } else if(*i == '=') {
941  equationSignAlreadyPresent = true;
942  }
943  if(ok) {
944  if(!compoundOpeningStartLen || wordIndex >= compoundOpeningStartLen) {
945  if(*i == '\'') {
946  cout << "'\"'\"'";
947  } else {
948  cout << *i;
949  }
950  }
951  ++i, ++wordIndex;
952  switch(*i) {
953  case ' ': case '\n': case '\0':
954  if(appendEquationSign && !equationSignAlreadyPresent) {
955  cout << '=';
956  noWhitespace = true;
957  equationSignAlreadyPresent = false;
958  }
959  if(*i == '\0') {
960  cout << '\'';
961  }
962  }
963  } else {
964  ++i;
965  }
966  }
967  cout << ' ';
968  }
969  } else if(const char *i = arg->preDefinedCompletionValues()) {
970  bool equationSignAlreadyPresent = false;
971  cout << '\'';
972  while(*i) {
973  if(*i == '\'') {
974  cout << "'\"'\"'";
975  } else {
976  cout << *i;
977  }
978  switch(*(++i)) {
979  case '=':
980  equationSignAlreadyPresent = true;
981  break;
982  case ' ': case '\n': case '\0':
983  if(appendEquationSign && !equationSignAlreadyPresent) {
984  cout << '=';
985  equationSignAlreadyPresent = false;
986  }
987  if(*i != '\0') {
988  cout << '\'';
989  if(*(++i)) {
990  cout << ' ' << '\'';
991  }
992  }
993  }
994  }
995  cout << '\'' << ' ';
996  }
997  }
998  }
999  // -> completions for further arguments
1000  for(const Argument *arg : relevantArgs) {
1001  if(argc && currentWordIndex <= lastSpecifiedArgIndex && opening) {
1002  switch(openingDenotationType) {
1003  case Value:
1004  if(!arg->denotesOperation() || strncmp(arg->name(), opening, openingLen)) {
1005  continue;
1006  }
1007  break;
1008  case Abbreviation:
1009  break;
1010  case FullName:
1011  if(strncmp(arg->name(), opening, openingLen)) {
1012  continue;
1013  }
1014  }
1015  }
1016 
1017  if(opening && openingDenotationType == Abbreviation && !nextArgumentOrValue) {
1018  cout << '\'' << '-' << opening << arg->abbreviation() << '\'' << ' ';
1019  } else if(lastDetectedArg && reader.argDenotationType == Abbreviation && !nextArgumentOrValue) {
1020  if(reader.argv == reader.end) {
1021  cout << '\'' << *(reader.argv - 1) << '\'' << ' ';
1022  }
1023  } else if(arg->denotesOperation() && (!actualArgumentCount() || (currentWordIndex == 0 && (!lastDetectedArg || (lastDetectedArg->isPresent() && lastDetectedArgIndex == 0))))) {
1024  cout << '\'' << arg->name() << '\'' << ' ';
1025  } else {
1026  cout << '\'' << '-' << '-' << arg->name() << '\'' << ' ';
1027  }
1028  }
1029  // -> completions for files and dirs
1030  // -> if there's already an "opening", determine the dir part and the file part
1031  string actualDir, actualFile;
1032  bool haveFileOrDirCompletions = false;
1033  if(argc && currentWordIndex == lastSpecifiedArgIndex && opening) {
1034  // the "opening" might contain escaped characters which need to be unescaped first (let's hope this covers all possible escapings)
1035  string unescapedOpening(opening);
1036  findAndReplace<string>(unescapedOpening, "\\ ", " ");
1037  findAndReplace<string>(unescapedOpening, "\\,", ",");
1038  findAndReplace<string>(unescapedOpening, "\\[", "[");
1039  findAndReplace<string>(unescapedOpening, "\\]", "]");
1040  findAndReplace<string>(unescapedOpening, "\\!", "!");
1041  findAndReplace<string>(unescapedOpening, "\\#", "#");
1042  findAndReplace<string>(unescapedOpening, "\\$", "$");
1043  findAndReplace<string>(unescapedOpening, "\\'", "'");
1044  findAndReplace<string>(unescapedOpening, "\\\"", "\"");
1045  findAndReplace<string>(unescapedOpening, "\\\\", "\\");
1046  // determine the "directory" part
1047  string dir = directory(unescapedOpening);
1048  if(dir.empty()) {
1049  actualDir = ".";
1050  } else {
1051  if(dir[0] == '\"' || dir[0] == '\'') {
1052  dir.erase(0, 1);
1053  }
1054  if(dir.size() > 1 && (dir[dir.size() - 2] == '\"' || dir[dir.size() - 2] == '\'')) {
1055  dir.erase(dir.size() - 2, 1);
1056  }
1057  actualDir = move(dir);
1058  }
1059  // determine the "file" part
1060  string file = fileName(unescapedOpening);
1061  if(file[0] == '\"' || file[0] == '\'') {
1062  file.erase(0, 1);
1063  }
1064  if(file.size() > 1 && (file[dir.size() - 2] == '\"' || dir[file.size() - 2] == '\'')) {
1065  file.erase(file.size() - 2, 1);
1066  }
1067  actualFile = move(file);
1068  }
1069 
1070  // -> completion for files and dirs
1072  if(completeFiles) {
1073  entryTypes |= DirectoryEntryType::File;
1074  }
1075  if(completeDirs) {
1076  entryTypes |= DirectoryEntryType::Directory;
1077  }
1078  if(entryTypes != DirectoryEntryType::None) {
1079  const string replace("'"), with("'\"'\"'");
1080  if(argc && currentWordIndex <= lastSpecifiedArgIndex && opening) {
1081  list<string> entries = directoryEntries(actualDir.c_str(), entryTypes);
1082  findAndReplace(actualDir, replace, with);
1083  for(string &dirEntry : entries) {
1084  if(startsWith(dirEntry, actualFile)) {
1085  cout << '\'';
1086  if(actualDir != ".") {
1087  cout << actualDir;
1088  }
1089  findAndReplace(dirEntry, replace, with);
1090  cout << dirEntry << '\'' << ' ';
1091  haveFileOrDirCompletions = true;
1092  }
1093  }
1094  } else {
1095  for(string &dirEntry : directoryEntries(".", entryTypes)) {
1096  findAndReplace(dirEntry, replace, with);
1097  cout << '\'' << dirEntry << '\'' << ' ';
1098  haveFileOrDirCompletions = true;
1099  }
1100  }
1101  }
1102  cout << ')';
1103 
1104  // ensure file or dir completions are formatted appropriately
1105  if(haveFileOrDirCompletions) {
1106  cout << "; compopt -o filenames";
1107  }
1108 
1109  // ensure trailing whitespace is ommitted
1110  if(noWhitespace) {
1111  cout << "; compopt -o nospace";
1112  }
1113 
1114  cout << endl;
1115 }
1116 
1122 {
1123  for(const Argument *arg : args) {
1124  const auto occurrences = arg->occurrences();
1125  if(arg->isParentPresent() && occurrences > arg->maxOccurrences()) {
1126  throw Failure("The argument \""s % arg->name() % "\" mustn't be specified more than "s % arg->maxOccurrences() + (arg->maxOccurrences() == 1 ? " time."s : " times."s));
1127  }
1128  if(arg->isParentPresent() && occurrences < arg->minOccurrences()) {
1129  throw Failure("The argument \""s % arg->name() % "\" must be specified at least "s % arg->minOccurrences() + (arg->minOccurrences() == 1 ? " time."s : " times."s));
1130  }
1131  Argument *conflictingArgument = nullptr;
1132  if(arg->isMainArgument()) {
1133  if(!arg->isCombinable() && arg->isPresent()) {
1134  conflictingArgument = firstPresentUncombinableArg(m_mainArgs, arg);
1135  }
1136  } else {
1137  conflictingArgument = arg->conflictsWithArgument();
1138  }
1139  if(conflictingArgument) {
1140  throw Failure("The argument \""s % conflictingArgument->name() % "\" can not be combined with \""s + arg->name() + "\"."s);
1141  }
1142  for(size_t i = 0; i != occurrences; ++i) {
1143  if(!arg->allRequiredValuesPresent(i)) {
1144  stringstream ss(stringstream::in | stringstream::out);
1145  ss << "Not all parameter for argument \"" << arg->name() << "\" ";
1146  if(i) {
1147  ss << " (" << (i + 1) << " occurrence) ";
1148  }
1149  ss << "provided. You have to provide the following parameter:";
1150  size_t valueNamesPrint = 0;
1151  for(const auto &name : arg->m_valueNames) {
1152  ss << ' ' << name, ++valueNamesPrint;
1153  }
1154  if(arg->m_requiredValueCount != static_cast<size_t>(-1)) {
1155  while(valueNamesPrint < arg->m_requiredValueCount) {
1156  ss << "\nvalue " << (++valueNamesPrint);
1157  }
1158  }
1159  throw Failure(ss.str());
1160  }
1161  }
1162 
1163  // check contraints of sub arguments recursively
1164  checkConstraints(arg->m_subArgs);
1165  }
1166 }
1167 
1176 {
1177  for(const Argument *arg : args) {
1178  // invoke the callback for each occurrence of the argument
1179  if(arg->m_callbackFunction) {
1180  for(const auto &occurrence : arg->m_occurrences) {
1181  arg->m_callbackFunction(occurrence);
1182  }
1183  }
1184  // invoke the callbacks for sub arguments recursively
1185  invokeCallbacks(arg->m_subArgs);
1186  }
1187 }
1188 
1199  Argument("help", 'h', "shows this information")
1200 {
1201  setCallback([&parser] (const ArgumentOccurrence &) {
1203  parser.printHelp(cout);
1204  });
1205 }
1206 
1218 }
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 ...
const char * preDefinedCompletionValues() const
Returns the assigned values used when generating completion for the values.
bool denotesOperation() const
Returns whether the argument denotes the operation.
void resetArgs()
Resets all Argument instances assigned as mainArguments() and sub arguments.
ArgumentVector & args
The Argument instances to store the results. Sub arguments of args are considered as well...
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.
const char * argDenotation
The currently processed abbreviation denotation (should be substring of one of the args in argv)...
bool isParentPresent() const
Returns whether at least one parent argument is present.
ArgumentParser()
Constructs a new ArgumentParser.
ValueCompletionBehavior valueCompletionBehaviour() const
Returns the items to be considered when generating completion for the values.
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.
const char *const * argv
Points to the first argument denotation and will be incremented when a denotation has been processed...
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.
~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.
HelpArgument(ArgumentParser &parser)
Constructs a new help argument for the specified parser.
unsigned char argDenotationType
The type of the currently processed abbreviation denotation. Unspecified if argDenotation is not set...
const ArgumentVector & subArguments() const
Returns the secondary arguments for this argument.
Contains utility classes helping to read and write streams.
Definition: binaryreader.h:10
ArgumentParser & parser
The associated ArgumentParser instance.
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.
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.
void read()
Reads the commands line arguments specified when constructing the object.
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.
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.
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...
const char *const * lastArgDenotation
Points to the element in argv where lastArg was encountered. Unspecified if lastArg is not set...
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.
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.
bool completionMode
Whether completion mode is enabled. In this case reading args will be continued even if an denotation...
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).
const char *const * end
Points to the end of the argv array.
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
size_t index
An index which is incremented when an argument is encountered (the current index is stored in the occ...
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.
Argument * lastArg
The last Argument instance which could be detected. Set to nullptr in the initial call...
void setSubArguments(const ArgumentInitializerList &subArguments)
Sets the secondary arguments for this arguments.
unsigned int actualArgumentCount() const
Returns the actual number of arguments that could be found when parsing.
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